在学会使用装饰器之后,我们可能时不时地在心理暗示下使用它,下面是我个人总结的一些代码实践中遇到的问题,主要参考 这一篇 中关于装饰器链式调用中讨论的问题。

最佳实践

  • 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.)
  1. 以下演示基于 Python3.7 讨论,测试代码时请检查你的 Python 版本。
  2. 于2025年6月做了部分补充

请神容易送神难

装饰器一旦被使用,就很难调用没有装饰器的原函数。

改善建议

  1. 9.3 解除一个装饰器 | Python-cookbook,源码(dabeaz/python-cookbook)中,作者提到一个方法:
from functools import wraps


def 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 wraps

def 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

# Python 3.8+中可通过__wrapped__逐层解包
print(target.__wrapped__ is deco2(target)) # True
print(target.__wrapped__.__wrapped__ is target) # True

需要注意的是,这种方法有两个缺陷:

  1. 装饰器函数内部必须被functools.wraps装饰或者直接设置了__wrapped__属性;
  2. 多个装饰器时,返回结果是不可预知的;请看下例:

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!
  1. 鉴于上面说到的缺陷,我们尝试使用 这里 提到的方式继续改善;
    我们说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

# deco_module
import time
from functools import wraps


def 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

# foo
from deco_module import do_func


def invoke_deco_func():
do_func()
do_other()
return 0


def do_other():
pass


def do_foo():
pass


if __name__ == '__main__':
do_foo()

此时,不管我们是否真正调用被装饰的函数do_func,装饰器decowrapper外面的函数都会被调用①。假如它是一个耗时的操作呢?😕
第三个模块中,我们导入foo模块:

# main.py
from foo import do_foo


def main():
do_foo()


if __name__ == '__main__':
main()

同样地,只要我们从foo中进行导入,不管我们是否在main中调用invoke_deco_func,上面提到的消耗①都会被执行。😣

有同学会举手发言了:嗳,你这装饰器写的不对啊,我所看到装饰器的逻辑都是写在wrapper函数里面的呀!陈独秀同学请坐,你在第一层,我在第二层。
有时候,我们为了装饰器更加灵活,既需要它可以不传参数,又需要它可以传递参数,这个时候就需要使用偏函数的定义。
请看下面的链接:

  1. Python decorator best practice, using a class vs a function - Stack Overflow
  2. 9.6 定义带可选参数的装饰器 | Python-cookbook
  3. 9.7 利用装饰器强制函数上的类型检查 | Python-cookbook

当然,我们都在期许第五层的老师能给我们指导释疑。

改善建议

  1. 装饰器wrapper外面不要写函数;如果非要写,应该是很小的操作。一个函数一旦被装饰,它就不是出走半生归来仍是少年的模样了。所以,尽量不要把这种含有wrapper外做逻辑处理的装饰器到处导入。
  2. 写进类里面;
    如果你类装饰器写得比较少,可以参考下面的链接:
  3. 9.8 将装饰器定义为类的一部分 — python3-cookbook 3.0.0 文档;参考 源码
  4. 9.9 将装饰器定义为类 ;参考 源码
# deco_module
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_bar


def do_foo():
pass


if __name__ == '__main__':
do_foo()

此时,如果不调用被装饰函数,则可以避免其被连带导入;当然,如果你导入它,那肯定还是会执行。🤓

执行顺序和调用顺序不一样

调用时从上到下,逻辑执行时从下到上。参考此文:Python decorator order

import sys
from functools import wraps


def 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__':
# foo()
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_enablecheck_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 Overflow2018 update 章节处所示的写法。

补充

一、装饰器与函数元数据的深度管理

1. functools.wraps的必要性与原理

上文已提及@wraps,但未明确其对元数据的保护机制:
当装饰器未使用wraps时,被装饰函数的__name____doc____annotations__等属性会被wrapper函数覆盖,导致调试工具(如pdb)、文档生成工具(如sphinx)或反射机制(如inspect模块)无法正确识别原函数信息。

示例说明

from functools import wraps

# 未使用wraps的装饰器
def deco(func):
def wrapper():
"""Wrapper docstring"""
return func()
return wrapper

@deco
def foo():
"""Original function docstring"""
pass

print(foo.__name__) # 输出: wrapper
print(foo.__doc__) # 输出: Wrapper docstring

# 使用wraps的装饰器
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__) # 输出: bar
print(bar.__doc__) # 输出: Original function docstring

2. Python 3.8+的__wrapped__标准化(PEP 487)支持

当装饰器使用@wraps时,被装饰函数的__wrapped__属性会自动指向原函数,支持多层装饰器链式访问(如func.__wrapped__.__wrapped__)。

  • 示例
from functools import wraps

def 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

# Python 3.8+中可通过__wrapped__逐层解包
print(target.__wrapped__ is deco2(target)) # True
print(target.__wrapped__.__wrapped__ is target) # True

二、现代 Python 特性增强装饰器能力

1. functools.decorator(Python 3.6+)

wraps更简洁的装饰器定义方式,自动处理元数据和__wrapped__

from functools import decorator

# 传统方式
def deco_traditional(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

# 使用decorator简化
@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

三、多装饰器场景的系统性解决方案

1. 使用functools.reduce管理装饰器链

当需要动态组合多个装饰器时,可通过reduce函数按顺序应用:

from functools import reduce, wraps

def 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() # 输出顺序: deco1 before → deco2 before → target running → deco2 after → deco1 after

2. 第三方库decorator简化多层装饰

pip install 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:.4f}s")
return result

@timer
@debug
def compute(x, y):
return x * y

compute(10, 20) # 输出: Calling compute with (10, 20), {} → Time: 0.0000s

四、异步装饰器与类方法装饰器

1. 异步装饰器的正确实现

import asyncio
from functools import wraps

# 异步装饰器(处理async def函数)
def 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()) # 输出: Async decorator before → Async task running → Async decorator after

2. 类方法装饰器的优先级处理

from functools import wraps

# 装饰器需兼容类方法(@classmethod)
def 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() # 输出: Decorating class method class_method of MyClass → Class method running

五、装饰器在类中的进阶应用

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() # 不输出日志

六、补充总结:装饰器使用原则升级

  1. 元数据优先:始终使用@wrapsfunctools.decorator保留原函数信息,避免调试和反射问题。
  2. 现代特性加持:Python 3.8+中利用标准化的__wrapped__解包装饰器,或使用functools.decorator简化代码。
  3. 复杂场景封装:多装饰器场景优先使用reduce或第三方库管理,异步场景注意async/await的正确嵌套。
  4. 类与方法适配:装饰器需兼容@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 文档