python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python @wraps()

python @wraps()装饰器的使用

作者:Yorlen_Zhang

functools.wraps是Python装饰器的关键工具,用于保留被装饰函数的元信息,本文就来详细的介绍一下@wraps()装饰器的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

“它让装饰器不‘穿帮’,被装饰的函数还能假装自己是原来的函数。”

下面从 0 到源码,带你彻底看懂。

一、为什么需要 wraps?

装饰器会“偷梁换柱”

def dec(f):
    def wrapper(*a, **kw):
        return f(*a, **kw)
    return wrapper

@dec
def add(x, y):
    """add two numbers"""
    return x + y

print(add.__name__)   # wrapper   ← 名字被换掉了!
print(add.__doc__)    # None      ← 文档串也没了

结果调试、IDE 提示、Sphinx 文档生成全都认错人。

functools.wraps 的作用
把原函数的 名字、文档、参数表、注解、模块名、属性字典 等关键元信息一次性拷贝到包装器上,解决“穿帮”问题。

二、最简正确姿势

from functools import wraps
def dec(f):
    @wraps(f)            # 就加这一行
    def wrapper(*a, **kw):
        """wrapper doc"""
        print('before')
        return f(*a, **kw)
    return wrapper
@dec
def add(x, y):
    """add two numbers"""
    return x + y
print(add.__name__)      # add
print(add.__doc__)       # add two numbers
print(add.__annotations__)  # 原函数注解也保留

三、wraps 其实是个“偏函数”

源码等价于:

def wraps(wrapped,
          assigned=WRAPPER_ASSIGNMENTS,      # 要拷贝的七大属性
          updated=WRAPPER_UPDATES):          # __dict__ 如何合并
    return partial(update_wrapper,          # 返回一个“等待 wrapper 作为参数”的函数
                   wrapped=wrapped,
                   assigned=assigned,
                   updated=updated)

所以
@wraps(func) 先制造一个“待装饰器”,再把真正的 wrapper 传进去,完成 update_wrapper(wrapper, func)

四、七大默认拷贝属性(WRAPPER_ASSIGNMENTS)

五、自定义要保留/跳过的属性

MY_ATTRS = ('__name__', '__doc__', '__my_flag__')

def my_wraps(f):
    return wraps(f, assigned=MY_ATTRS)

# 或者只想忽略某个属性
from functools import WRAPPER_ASSIGNMENTS
skip_annotation = tuple(a for a in WRAPPER_ASSIGNMENTS if a != '__annotations__')

def dec(f):
    @wraps(f, assigned=skip_annotation)
    def wrapper(*a, **kw):
        ...

六、保留 wrapper 自己的属性(叠加)

有时既想保留原函数,又想给 wrapper 加新属性,用 updated 参数:

def dec(f):
    @wraps(f, updated=())   # 不合并 __dict__,避免覆盖
    def wrapper(*a, **kw):
        ...
    wrapper.decorator = 'my_dec'   # 新增标记
    return wrapper

七、带参数的装饰器 + wraps

from functools import wraps

def repeat(n):                      # ← 这一层接收参数
    def dec(f):
        @wraps(f)
        def wrapper(*a, **kw):
            for _ in range(n):
                result = f(*a, **kw)
            return result
        return wrapper
    return dec

@repeat(3)
def hello():
    """say hello"""
    print('hi')

hello()
print(hello.__name__)   # hello
print(hello.__doc__)    # say hello

关键点:@wraps(f) 一定要放在最内层 wrapper 上,而不是 dec 本身。

八、类装饰器 & 方法也能用

def count_calls(cls):
    orig_init = cls.__init__

    @wraps(orig_init)          # 保留原 __init__ 签名
    def new_init(self, *a, **kw):
        self._cnt = 0
        orig_init(self, *a, **kw)

    cls.__init__ = new_init
    return cls

九、调试技巧:一眼看穿“真身”

>>> hello.__wrapped__        # wraps 自动加的秘密属性
<function hello at 0x...>    # 指向原始函数
>>> inspect.signature(hello) # 拿到的是原签名,不会看到 wrapper 的 *a, **kw

十、常见坑速查

现象原因解决
TypeError: wraps() missing 1 required positional argument把 @wraps 写成了 @wraps()正确写 @wraps(func)
签名还是 wrapper 的 (*args, **kwargs)用了 wraps 但 IDE 仍显示通用签名IDE 缓存 / 需 inspect.signature 重新提取
保留不了自定义属性默认 assigned 不包含传自定义列表

十一、一句话总结

写装饰器就两步:

到此这篇关于python @wraps()装饰器的使用的文章就介绍到这了,更多相关python @wraps()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文