Skip to main content
Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Functools -- wraps

✅ 1. functools.wraps 是什么?

wraps 是一个装饰器,用于 编写装饰器时保持被包装函数的元数据,比如:

  • __name__
  • __doc__
  • __module__
  • __annotations__
  • __wrapped__
  • __qualname__

否则,使用装饰器会导致函数被替换为内部 wrapper,丢失原信息。

❌ 不使用 wraps 的问题

例子:

def logger(func):
    def wrapper(*args, **kwargs):
        print("call", func.__name__)
        return func(*args, **kwargs)
    return wrapper


@logger
def add(a, b):
    """加法函数"""
    return a + b

print(add.__name__)
print(add.__doc__)

输出:

wrapper
None

原函数 add 的名字和文档都丢失了。

✅ 使用 functools.wraps 的正确方式

from functools import wraps

def logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("call", func.__name__)
        return func(*args, **kwargs)
    return wrapper


@logger
def add(a, b):
    """加法函数"""
    return a + b

print(add.__name__)
print(add.__doc__)

输出:

add
加法函数

📌 2. wraps 本质上做了什么?

相当于:

  • 把原函数的名称、注释、文档写回包装函数
  • 给包装函数增加 __wrapped__ = func,支持 inspect 解包
  • 让 IDE / 文档工具继续识别真实信息

底层等价于:

def wraps(func):
    def decorator(wrapper):
        wrapper.__name__ = func.__name__
        wrapper.__doc__ = func.__doc__
        wrapper.__module__ = func.__module__
        wrapper.__annotations__ = func.__annotations__
        wrapper.__wrapped__ = func
        return wrapper
    return decorator

🟦 3. 最佳实践:通用装饰器模板

你今后写所有装饰器都用这个结构:

from functools import wraps

def my_decorator(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        # 前置逻辑
        result = fn(*args, **kwargs)
        # 后置逻辑
        return result
    return wrapper

🟩 4. 常见应用场景

① 日志装饰器

def log_call(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        print(f"[CALL] {fn.__name__}")
        return fn(*args, **kwargs)
    return wrapper

② 缓存装饰器(你项目可用)

def cache(fn):
    memo = {}
    @wraps(fn)
    def wrapper(x):
        if x not in memo:
            memo[x] = fn(x)
        return memo[x]
    return wrapper

③ 权限检查(适合 FastAPI)

def require_admin(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        user = kwargs.get("user")
        if not user or user.role != "admin":
            raise PermissionError("需要管理员权限")
        return fn(*args, **kwargs)
    return wrapper

🟥 5. 不使用 wraps 会导致的真实问题

FastAPI 无法正确显示文档

装饰器后的函数 metadata 丢失,导致路径参数错误。

functools.lru_cache 无法工作

cache 会识别 wrapper,而不是原函数。

调试困难

traceback 显示 wrapper,而不是真实函数名称。

IDE 无法提示参数类型

函数注解丢失。

🟪 6. 专业级:wraps 还能传入参数

默认复制:

wraps(func)

你可以选择复制哪些属性:

wraps(func, assigned=("__name__", "__doc__"), updated=())

⭐ 7. 一屏速查表(记住这张就够了)

functools.wraps 用于编写装饰器。

作用:

  • 保留原函数的 __name__
  • 保留原函数的 __doc__
  • 保留 __annotations__
  • 保留 __module__
  • 添加 __wrapped__,方便 inspect 解包

用法:

from functools import wraps

def deco(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

不使用的后果:

  • debug 和堆栈变难
  • IDE 无法提示参数
  • FastAPI 文档不准确