深入理解 Python 的 __wrapped__ 属性

在 Python 的装饰器(decorator)体系中,函数包装(wrapping)是一种非常常见的技术。然而,函数一旦被装饰,原始函数的元信息(如名称、文档字符串、签名等)往往会被覆盖。为了解决这个问题,Python 引入了一个非常关键但容易被忽视的属性:__wrapped__

本文将系统介绍 __wrapped__ 的作用、原理以及实际应用场景。


一、什么是 __wrapped__

__wrapped__ 是一个函数属性,用于指向被装饰前的原始函数。

简单来说:

wrapped_function.__wrapped__ == original_function

这个属性通常由标准库中的 functools.wraps 自动设置。


二、为什么需要 __wrapped__

考虑一个最简单的装饰器:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before call")
        return func(*args, **kwargs)
    return wrapper

使用它:

@my_decorator
def add(a, b):
    return a + b

此时:

print(add.__name__)  # 输出: wrapper

问题出现了:原函数 add 的信息丢失了。


三、functools.wraps 的作用

为了解决这个问题,我们通常这样写:

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Before call")
        return func(*args, **kwargs)
    return wrapper

@wraps(func) 做了几件关键的事情:

  1. 复制元信息(如 __name__, __doc__
  2. 设置 __wrapped__ 属性

等价于:

wrapper.__wrapped__ = func

四、__wrapped__ 的核心用途

1. 访问原始函数

print(add.__wrapped__)  # <function add at ...>

甚至可以直接调用原始函数:

result = add.__wrapped__(2, 3)
print(result)  # 5(跳过装饰器)

2. 支持函数签名检查(inspect)

Python 的 inspect 模块会使用 __wrapped__ 来获取真实函数签名:

import inspect

print(inspect.signature(add))

如果没有 __wrapped__,签名会变成:

(*args, **kwargs)

而不是:

(a, b)

3. 多层装饰器链

当有多个装饰器时:

@decorator1
@decorator2
def func():
    pass

会形成链式结构:

func -> wrapper1 -> wrapper2 -> original

可以通过不断访问:

func.__wrapped__.__wrapped__

逐层回溯到最初的函数。


4. 调试和工具支持

很多工具(如:

  • 调试器
  • 文档生成工具
  • 类型检查工具

)都会依赖 __wrapped__ 来“还原”真实函数。


五、实现原理简析

functools.wraps 本质上是一个装饰器工厂:

def wraps(wrapped):
    def decorator(wrapper):
        wrapper.__wrapped__ = wrapped
        return wrapper
    return decorator

实际实现中还会复制:

  • __module__
  • __name__
  • __qualname__
  • __doc__
  • __annotations__

六、最佳实践

✅ 总是使用 functools.wraps

from functools import wraps

这是推荐的标准写法,否则:

  • 调试困难
  • introspection 失效
  • 工具链支持变差

❗ 不要手动忽略 __wrapped__

如果你写自定义装饰器但不设置 __wrapped__,可能导致:

  • inspect 失效
  • FastAPI / Click 等框架行为异常

七、总结

__wrapped__ 虽然是一个小属性,但在 Python 生态中扮演着关键角色:

  • 保留函数原始引用
  • 支持 introspection(自省)
  • 让工具链正常工作
  • 提升调试体验

一句话总结:

__wrapped__ 是装饰器世界中的“回溯指针”。

更多推荐