Python 装饰器的底层实现原理

3次阅读

装饰器本质是函数套函数,利用函数为一等对象特性,通过@语法糖将被装饰函数传入装饰器并替换原函数名;带参装饰器需三层嵌套;functools.wraps必须使用以保留原函数元信息;类装饰器需实现__call__并手动同步属性。

Python 装饰器的底层实现原理

装饰器本质是函数套函数

python 装饰器没有魔法,它只是利用了函数是一等对象(first-class Object)这一特性:函数可以被赋值给变量、作为参数传入其他函数、在函数里定义并返回。所谓 @decorator 语法糖,底层就是把被装饰函数当作参数传给 decorator,再用返回值替换原函数名。

比如:

def my_decorator(func):     def wrapper(*args, **kwargs):         print("before")         result = func(*args, **kwargs)         print("after")         return result     return wrapper 

@my_decorator def say_hello(): print("hello")

等价于:

def say_hello():     print("hello") say_hello = my_decorator(say_hello)

关键点在于:my_decorator 必须返回一个可调用对象(通常是函数),且该对象要能接收和转发 say_hello 原本的参数 —— 否则调用会出错。

立即学习Python免费学习笔记(深入)”;

带参数的装饰器需要三层嵌套

当你写 @decorator(arg=1) 时,Python 要求 decorator(arg=1) 的结果本身是一个装饰器(即接收函数并返回新函数)。所以必须多包一层:

  • 最外层函数接收装饰器参数(如 arg),返回真正的装饰器
  • 中间层是标准装饰器,接收被装饰函数,返回包装函数
  • 最内层是实际执行逻辑的 wrapper

常见错误是漏掉某一层,导致 TypeError: 'function' object is not callablemissing 1 required positional argument: 'func'

示例:

def repeat(times):     def decorator(func):  # ← 这才是真正的装饰器         def wrapper(*args, **kwargs):             for _ in range(times):                 result = func(*args, **kwargs)             return result         return wrapper     return decorator  # ← 返回装饰器,不是 wrapper

functools.wraps 不是可选的,是必须的

如果不加 @functools.wraps(func),被装饰后的函数会丢失原始函数的元信息:__name__ 变成 'wrapper'__doc__ 为空,help() 查不到原说明,调试和反射类工具(如 flask 路由注册、pytest 参数提取)会出问题。

正确写法:

from functools import wraps 

def my_decorator(func): @wraps(func) # ← 必须加这一行 def wrapper(*args, *kwargs): return func(args, **kwargs) return wrapper

它本质上是把 func__name____doc____module__ 等属性复制到 wrapper 上。不加的话,所有基于函数签名的自动化流程都可能静默失败。

类装饰器依赖 __call__ 方法

类也可以当装饰器用,前提是实现了 __call__。实例化时传入被装饰函数,调用时行为像函数:

class CountCalls:     def __init__(self, func):         self.func = func         self.count = 0         # 注意:这里仍需 wraps,否则 __name__ 丢失         from functools import wraps         self.__wrapped__ = func         self.__name__ = func.__name__         self.__doc__ = func.__doc__ 
def __call__(self, *args, **kwargs):     self.count += 1     return self.func(*args, **kwargs)

但要注意:类装饰器无法直接用 @functools.wraps(它是为函数设计的),得手动同步属性,或继承 functools.update_wrapper 的逻辑。否则 ide 提示、文档生成工具会识别不到原始函数信息。

装饰器的底层其实很直白,难的是在保持语义清晰的同时,不破坏函数的身份特征 —— 尤其是在组合多个装饰器、配合类型检查(mypy)或异步上下文时,元信息丢失和参数透传问题会立刻暴露。

text=ZqhQzanResources