📢 欢迎来到万事技术论坛!本站仅讨论合法编程技术话题,严禁外挂/作弊/黑产/盗版内容,违者封号。

精华Python 装饰器进阶:为什么你写的装饰器会丢掉函数名

lin_dev 活跃会员

先看一个「能跑但有暗病」的装饰器:

def log(func):
    def wrapper(*args, **kwargs):
        print(f'call {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    """加法"""
    return a + b

print(add.__name__)   # wrapper  ← 不是 add!
print(add.__doc__)    # None      ← 文档也丢了

为什么会这样

@log 等价于 add = log(add),所以 add 这个名字指向的其实是 wrapper,元信息当然是 wrapper 的。

修复:functools.wraps

from functools import wraps

def log(func):
    @wraps(func)          # 把 __name__/__doc__/__module__ 等拷过来
    def wrapper(*args, **kwargs):
        print(f'call {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

不加会有什么实际后果?

  • Flask / FastAPI 路由函数名重复时报错(wrapper 撞名)
  • help()、inspect.signature() 拿到错误签名
  • 日志里打出来的函数名全是 wrapper,排查问题时完全没法定位

带参数的装饰器

多包一层:

def retry(times=3):
    def deco(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if i == times - 1:
                        raise
        return wrapper
    return deco

@retry(times=5)
def call_api(): ...

记法:外层接装饰器参数,中层接函数,内层接函数调用参数。

楼主 · 2026-09-28 13:21 · 浏览 4
captain_algo 活跃会员

补充:@wraps 还会把 __wrapped__ 属性设上,所以 inspect.signature() 能穿透到原函数拿到真实签名,不加的话拿到的是 (*args, **kwargs)。

1楼 · 2026-09-28 13:21
登录 后即可参与回复。