在学会使用装饰器之后,我们可能时不时地在心理暗示下使用它,下面是我个人总结的一些代码实践中遇到的问题,主要参考 这一篇 中关于装饰器链式调用中讨论的问题。
最佳实践
- 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 版本。
请神容易送神难
装饰器一旦被使用,就很难调用没有装饰器的原函数。
改善建议
- 在9.3 解除一个装饰器 | Python-cookbook,源码(dabeaz/python-cookbook)中,作者提到一个方法:
from functools import wraps
def deco_it(func):
"""
定义装饰器
:param func:
:return:
"""
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 foo():
print('Hello,World!')
if __name__ == '__main__':
foo() # 被装饰的调用
foo.__wrapped__() # 解除装饰的调用需要注意的是,这种方法有两个缺陷:----inner of wrapper before func----
Hello,World!
----inner of wrapper after func--
# 原函数
Hello,World!- 装饰器函数内部必须被
functools.wraps
装饰或者直接设置了__wrapped__
属性; - 多个装饰器时,返回结果是不可预知的;请看下例:返回结果:
def another_deco_it(func):
"""
定义另一个装饰器
:param func:
:return:
"""
print('This is another deco.')
def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return wrapper
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--返回的结果又是符合我们预期的😯:
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:
"""
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:
"""
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
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.')
def wrapper(*args, **kwargs):
print('-----inner of wrapper.----')
time.sleep(5)
ret = func(*args, **kwargs)
time.sleep(3)
return ret
return wrapper
def do_func():
print('hello world')
return 0deco_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
,装饰器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 将装饰器定义为类 ;参考 源码此时我们在装饰器定义模块中执行返回:
# 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
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 orderimport sys
from functools import wraps
def check_has_set(func):
"""
是否进行了模块配置
:param func:
:return:
"""
print(f'out of func: {sys._getframe().f_code.co_name}')
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}')
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}')
def wrapper(*args, **kwargs):
print(f'inner of func check_has_execute.')
ret = func(*args, **kwargs)
return ret
return wrapper
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_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 Overflow2018 update 章节处所示的写法。
总结
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 文档