在学会使用装饰器之后,我们可能时不时地在心理暗示下使用它,下面是我个人总结的一些代码实践中遇到的问题,主要参考 这一篇 中关于装饰器链式调用中讨论的问题。
最佳实践
Decorators were introduced in Python 2.4, so be sure your code will be run on >= 2.4.
Decorators slow down the function call. Keep that in mind.
You cannot un-decorate a function. (There are hacks to create decorators that can be removed, but nobody uses them.) So once a function is decorated, it’s decorated for all the code.
Decorators wrap functions, which can make them hard to debug. (This gets better from Python >= 2.5; see below.)
以下演示基于 Python3.7 讨论,测试代码时请检查你的 Python 版本。
于2025年6月做了部分补充
请神容易送神难 装饰器一旦被使用,就很难调用没有装饰器的原函数。
改善建议
在9.3 解除一个装饰器 | Python-cookbook ,源码(dabeaz/python-cookbook )中,作者提到一个方法:
from functools import wrapsdef deco_it (func ): """ 定义装饰器 :param func: :return: """ @wraps(func ) def wrapper (*args, **kwargs ): print ('----inner of wrapper before func----' ) ret = func(*args, **kwargs) print ('----inner of wrapper after func--' ) return ret return wrapper @deco_it def foo (): print ('Hello,World!' ) if __name__ == '__main__' : foo() foo.__wrapped__()
----inner of wrapper before func---- Hello,World! ----inner of wrapper after func-- # 原函数 Hello,World!
Python 3.8+的__wrapped__标准化(PEP 487)支持:
当装饰器使用@wraps时,被装饰函数的__wrapped__属性会自动指向原函数,支持多层装饰器链式访问(如func.__wrapped__.__wrapped__)。
from functools import wrapsdef deco1 (func ): @wraps(func ) def wrapper (): return func() return wrapper def deco2 (func ): @wraps(func ) def wrapper (): return func() return wrapper @deco1 @deco2 def target (): pass print (target.__wrapped__ is deco2(target)) print (target.__wrapped__.__wrapped__ is target)
需要注意的是,这种方法有两个缺陷:
装饰器函数内部必须被functools.wraps装饰或者直接设置了__wrapped__属性;
多个装饰器时,返回结果是不可预知的;请看下例:
def another_deco_it (func ): """ 定义另一个装饰器 :param func: :return: """ print ('This is another deco.' ) @wraps(func ) def wrapper (*args, **kwargs ): ret = func(*args, **kwargs) return ret return wrapper @another_deco_it @deco_it def foo (): print ('Hello,World!' ) if __name__ == '__main__' : foo() print ('origin func under below.' ) foo.__wrapped__()
返回结果:
This is another deco. ----inner of wrapper before func---- Hello,World! ----inner of wrapper after func-- origin func under below. # 原函数定义(实际上,此时并没有解除掉@deco_it装饰器) ----inner of wrapper before func---- Hello,World! ----inner of wrapper after func--
甚至,如果我们调换装饰器的位置
@deco_it @another_deco_it def foo (): print ('Hello,World!' )
返回的结果又是符合我们预期的😯:
This is another deco. ----inner of wrapper before func---- Hello,World! ----inner of wrapper after func-- origin func under below. # 原函数定义 Hello,World!
鉴于上面说到的缺陷,我们尝试使用 这里 提到的方式继续改善; 我们说Python中一切都是对象,所以可以将我们的装饰器是否使用通过 flag 传递给调用函数名:
def enable_deco (func ): """ 使能装饰器 :param func: :return: """ @wraps(func ) def wrapper (*args, **kwargs ): print ('----inner of wrapper before func----' ) ret = func(*args, **kwargs) print ('----inner of wrapper after func--' ) return ret return wrapper def disable_deco (func ): """ 禁用装饰器 :param func: :return: """ @wraps(func ) def wrapper (*args, **kwargs ): ret = func(*args, **kwargs) return ret return wrapper GLOBAL_ENABLE_FLAG = True depend_with_flag = enable_deco if GLOBAL_ENABLE_FLAG else disable_deco @depend_with_flag def foo (): print ('Hello,World!' )
我们通过GLOBAL_ENABLE_FLAG分别定义的bool值可以将装饰器启用或者禁用。这个方式也适用于多个装饰器的时候,只不过我们需要定义更多的 bool 变量来控制而已。 实践代码可以参考此处Leveraging the power of decorators in Python’s unit-test framework | by Vivek Shrivastava | Medium ,注意文中说到的 second point 。
凡有导入,必留痕迹 调用被装饰函数会导致装饰器函数外部逻辑的执行。示例代码如下: 我们在第一个模块中编写一个装饰器deco:
import timefrom functools import wrapsdef deco (func ): print ('out of real wrapper.' ) @wraps(func ) def wrapper (*args, **kwargs ): print ('-----inner of wrapper.----' ) time.sleep(5 ) ret = func(*args, **kwargs) time.sleep(3 ) return ret return wrapper @deco def do_func (): print ('hello world' ) return 0
在第二个模块中,我们导入deco_module:
from deco_module import do_funcdef invoke_deco_func (): do_func() do_other() return 0 def do_other (): pass def do_foo (): pass if __name__ == '__main__' : do_foo()
此时,不管我们是否真正调用被装饰的函数do_func,装饰器deco中wrapper外面的函数都会被调用①。假如它是一个耗时的操作呢?😕 第三个模块中,我们导入foo模块:
# main.py from foo import do_foo def main(): do_foo() if __name__ == '__main__': main()
同样地,只要我们从foo中进行导入,不管我们是否在main中调用invoke_deco_func,上面提到的消耗①都会被执行。😣
改善建议
装饰器wrapper外面不要写函数;如果非要写,应该是很小的操作。一个函数一旦被装饰,它就不是出走半生归来仍是少年的模样了。所以,尽量不要把这种含有wrapper外做逻辑处理的装饰器到处导入。
写进类里面; 如果你类装饰器写得比较少,可以参考下面的链接:
9.8 将装饰器定义为类的一部分 — python3-cookbook 3.0.0 文档 ;参考 源码
9.9 将装饰器定义为类 ;参考 源码
class Deco : def __init__ (self, func ): self .func = func def __call__ (self, *args, **kwargs ): print ('out of real wrapper.' ) ret = self .func(*args, **kwargs) return ret @Deco def do_func (): print ('hello world' ) return 0
此时我们在装饰器定义模块中执行返回:
out of real wrapper. hello world
在不包含装饰器的模块中导入上述模块:
from deco_module import do_bardef do_foo (): pass if __name__ == '__main__' : do_foo()
此时,如果不调用被装饰函数,则可以避免其被连带导入;当然,如果你导入它,那肯定还是会执行。🤓
执行顺序和调用顺序不一样 调用时从上到下,逻辑执行时从下到上。参考此文:Python decorator order
import sysfrom functools import wrapsdef check_has_set (func ): """ 是否进行了模块配置 :param func: :return: """ print (f'out of func: {sys._getframe().f_code.co_name} ' ) @wraps(func ) def wrapper (*args, **kwargs ): print (f'inner of func check_has_set.' ) ret = func(*args, **kwargs) return ret return wrapper def check_has_enable (func ): """ 模块是否启用 :param func: :return: """ print (f'out of func: {sys._getframe().f_code.co_name} ' ) @wraps(func ) def wrapper (*args, **kwargs ): print (f'inner of func check_has_enable.' ) ret = func(*args, **kwargs) return ret return wrapper def check_has_execute (func ): """ 模块是否执行,这是一个耗时操作,并且需要一定的条件才可以执行 :param func: :return: """ time.sleep(5 ) print (f'out of func: {sys._getframe().f_code.co_name} ' ) @wraps(func ) def wrapper (*args, **kwargs ): print (f'inner of func check_has_execute.' ) ret = func(*args, **kwargs) return ret return wrapper @check_has_execute @check_has_enable @check_has_set def foo (): print ('I can do it only when has_set and has_enable and before has_execute' ) def bar (): pass if __name__ == '__main__' : bar()
我们执行上述函数,得到返回结果:
out of func: check_has_set out of func: check_has_enable out of func: check_has_execute
这个符合我们的预期,因为装饰器类似于这种写法:check_has_execute(check_has_enable(check_has_set(foo)))。
接下来,我们真正调用被装饰函数,即放开注释,将无关的调用bar()注释:
out of func: check_has_set out of func: check_has_enable out of func: check_has_execute inner of func check_has_execute. inner of func check_has_enable. inner of func check_has_set.
注意看返回结果中的前三行和后三行。此时先去执行了check_has_execute中的操作,而后才会执行check_has_enable和check_has_set!此时我们做的提前返回的限制就无效了。
改善建议 如果只是调用顺序导致的问题,底层函数不会有特别耗时的操作,我们可以按照条件的先后去装饰foo()函数,让其按照预定的顺序检查: 即if check_has_set >> if check_has_enable >> if not check_has_execute,只有条件都满足采取真正执行。
@check_has_set @check_has_enable @check_has_execute def foo(): pass
局限性:check_has_execute中长耗时会执行,此时会拖慢你的程序!如此处Python decorator best practice, using a class vs a function - Stack Overflow 2018 update 章节处所示的写法。
补充 一、装饰器与函数元数据的深度管理 上文已提及@wraps,但未明确其对元数据的保护机制: 当装饰器未使用wraps时,被装饰函数的__name__、__doc__、__annotations__等属性会被wrapper函数覆盖,导致调试工具(如pdb)、文档生成工具(如sphinx)或反射机制(如inspect模块)无法正确识别原函数信息。
示例说明 :
from functools import wrapsdef deco (func ): def wrapper (): """Wrapper docstring""" return func() return wrapper @deco def foo (): """Original function docstring""" pass print (foo.__name__) print (foo.__doc__) def deco_with_wraps (func ): @wraps(func ) def wrapper (): """Wrapper docstring""" return func() return wrapper @deco_with_wraps def bar (): """Original function docstring""" pass print (bar.__name__) print (bar.__doc__)
2. Python 3.8+的__wrapped__标准化(PEP 487)支持 当装饰器使用@wraps时,被装饰函数的__wrapped__属性会自动指向原函数,支持多层装饰器链式访问(如func.__wrapped__.__wrapped__)。
from functools import wrapsdef deco1 (func ): @wraps(func ) def wrapper (): return func() return wrapper def deco2 (func ): @wraps(func ) def wrapper (): return func() return wrapper @deco1 @deco2 def target (): pass print (target.__wrapped__ is deco2(target)) print (target.__wrapped__.__wrapped__ is target)
二、现代 Python 特性增强装饰器能力 比wraps更简洁的装饰器定义方式,自动处理元数据和__wrapped__:
from functools import decoratordef deco_traditional (func ): @wraps(func ) def wrapper (*args, **kwargs ): return func(*args, **kwargs) return wrapper @decorator def deco_modern (func, *args, **kwargs ): return func(*args, **kwargs)
2. 装饰器类的__init__与__call__优化 上文提到类装饰器,补充__init__参数化设计:
class ParametricDeco : def __init__ (self, prefix ): self .prefix = prefix def __call__ (self, func ): @wraps(func ) def wrapper (*args, **kwargs ): print (f"[{self.prefix} ] Decorated" ) return func(*args, **kwargs) return wrapper @ParametricDeco(prefix="DEBUG" ) def work (): pass
三、多装饰器场景的系统性解决方案 当需要动态组合多个装饰器时,可通过reduce函数按顺序应用:
from functools import reduce, wrapsdef deco1 (func ): @wraps(func ) def wrapper (): print ("deco1 before" ) result = func() print ("deco1 after" ) return result return wrapper def deco2 (func ): @wraps(func ) def wrapper (): print ("deco2 before" ) result = func() print ("deco2 after" ) return result return wrapper def apply_decorators (func, decorators ): return reduce(lambda f, d: d(f), decorators, func) @apply_decorators(decorators=[deco1, deco2] ) def target (): print ("target running" ) target()
2. 第三方库decorator简化多层装饰
from decorator import decorator@decorator def debug (func, *args, **kwargs ): print (f"Calling {func.__name__} with {args} , {kwargs} " ) return func(*args, **kwargs) @decorator def timer (func, *args, **kwargs ): import time start = time.time() result = func(*args, **kwargs) print (f"Time: {time.time() - start:.4 f} s" ) return result @timer @debug def compute (x, y ): return x * y compute(10 , 20 )
四、异步装饰器与类方法装饰器 1. 异步装饰器的正确实现 import asynciofrom functools import wrapsdef async_deco (func ): @wraps(func ) async def wrapper (*args, **kwargs ): print ("Async decorator before" ) result = await func(*args, **kwargs) print ("Async decorator after" ) return result return wrapper @async_deco async def async_task (): print ("Async task running" ) await asyncio.sleep(0.1 ) return "Done" asyncio.run(async_task())
2. 类方法装饰器的优先级处理 from functools import wrapsdef class_method_deco (func ): @wraps(func ) def wrapper (cls, *args, **kwargs ): print (f"Decorating class method {func.__name__} of {cls.__name__} " ) return func(cls, *args, **kwargs) return wrapper class MyClass : @class_method_deco @classmethod def class_method (cls ): print ("Class method running" ) MyClass.class_method()
五、装饰器在类中的进阶应用 1. 装饰器作为类属性(动态控制) class FeatureController : ENABLE_LOG = True @staticmethod def log_deco (func ): @wraps(func ) def wrapper (*args, **kwargs ): if FeatureController.ENABLE_LOG: print (f"Logging: calling {func.__name__} " ) return func(*args, **kwargs) return wrapper class MyService : @FeatureController.log_deco def process_data (self ): print ("Processing data..." ) FeatureController.ENABLE_LOG = False MyService().process_data()
六、补充总结:装饰器使用原则升级
元数据优先 :始终使用@wraps或functools.decorator保留原函数信息,避免调试和反射问题。
现代特性加持 :Python 3.8+中利用标准化的__wrapped__解包装饰器,或使用functools.decorator简化代码。
复杂场景封装 :多装饰器场景优先使用reduce或第三方库管理,异步场景注意async/await的正确嵌套。
类与方法适配 :装饰器需兼容@classmethod/@staticmethod,或通过类属性动态控制装饰逻辑。
总结 To be continue.
推荐阅读 PythonDecorators - Python Wiki Python Decorators: A Step-By-Step Introduction – dbader.org Primer on Python Decorators – Real Python Python decorators vs inheritance - Anselmos Blog python - How to make a chain of function decorators? - Stack Overflow 第九章:元编程 — python3-cookbook 3.0.0 文档