一言一概之:装饰器是对函数的函数。
在Python中,一切皆对象,函数func本身也可被视为一个对象。而装饰器decorator就是将其他函数func视为自身input的特殊函数。即decorator( func() )
装饰器使得我们不需要破坏性地直接改变函数代码,而是通过添加装饰器,来达到改变/增加函数功能的作用。
装饰器的基本结构
def decorator(func):
print('前置步骤')
def wrapper(*args,**wargs):
print('func前执行的步骤')
func(*args,**wargs)
print('func后执行的步骤')
return wrapper
@decorator
def func(a,b):
pass
return
装饰器的难点
1.多个装饰器
这里引用python装饰器详解给出的例子
def dec1(func):
print("1111")
def wrapper1():
print("2222")
func()
print("3333")
return wrapper1
def dec2(func):
print("aaaa")
def wrapper2():
print("bbbb")
func()
print("cccc")
return wrapper2
@dec1
@dec2
def test():
print("test test")
test()
输出为:
aaaa
1111
2222
bbbb
test test
cccc
3333
多个装饰器的运行逻辑如下:
- 运行
dec1(dec2(test()))
(最“上方”的装饰器在最外侧运行) test
作为对象,传参至dec2
内。先执行dec2
dec2
的装饰器前置步骤被执行。输出aaaa
dec2
装饰器return wrapper2
,并将后者传参之dec1
内dec1
的装饰器前置步骤被执行。输出1111
dec1
装饰器return wrapper1
。至此所有传参已经完成,开始依序执行wrapper1
- 输出
2222
。执行wrapper2
(wrapper1
中的func
实则为wrapper2
) - 输出
bbbb
,执行func
- 依次输出
cccc
与3333
概括来说,要点就是:
- 装饰器由上至下,执行顺序由外至内。
- 装饰器前置代码(wrapper前的代码块)会在传参前被执行。顺序为由内至外。
- 装饰器wrapper内函数执行顺序为:外中内func内中外
2.closure
理解closure前先要理解scope
在Python中,变量范围被划分为global、non-local、local三种。
#global
def parent():
#non-local
def child():
#local
对于装饰器而言,在non-local中创建的变量会被储存至child.__closure__中。
这意味着在如下代码中:
def decorator(func):
num = []
def wrapper(*args,**wargs):
if func(*args,**wargs) == 1:
num.append( (*args,**wargs) )
return wrapper
@decorator
def func1():
pass
@decorator
def func2():
pass
虽然我们是分别对func1和func2添加了装饰器,从直觉上两个装饰器应该相互独立。但事实上,列表num
始终储存在装饰器的closure中,并实时更新。从而num
列表同时记录了func1
与func2
的相关数据。
3.装饰器导致的metadata缺失
假如我们有如下函数:
def func():
'''
This is a function.
'''
pass
return
print(func.__doc__)
将会输出This is a function.
我们增加了装饰器dec
@dec
def func():
'''
This is a function.
'''
pass
return
print(func.__doc__)
将会输出None
print(func.__name__)
将会输出wrapper
意识到问题了!装饰器将导致原函数的被替换为了包含了原函数的wrapper,使得metadata被覆盖
解决的办法是使用functoosl
的wraps
并添加至装饰器内部
from functools import wraps
def dec(func):
@wraps(func)
def wrapper():
pass
return wrapper()
@dec
def func():
'''
This is a function.
'''
pass
return
4.可传入变量的装饰器
假如我们编写一个装饰器sleep_for(n)
它可以根据我们传入的参数n
,来让目标函数延后n
秒执行。
如何实现?一言以蔽之:
将sleep_for(n)
编写为输出一个装饰器的函数
代码如下:
def sleep_for(n):
def decorator(func):
def wrapper(*args,**wargs):
time.sleep(n)
func(*args,**wargs)
return wrapper
return decorator
@sleep_for(3)
def func1():
pass