装饰器初探

什么是装饰器。

装饰器是 Python 的一种语法糖,形式为 @xxxx,在计时、记录log 的时候比较常用。
下面是一个记录函数运行时间的装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time
def runtimedec(func):
def wrapper(*args,**kwargs):
print("start : ---{}---".format(time.strftime("%H:%M:%S",time.localtime())))
func(*args,**kwargs)
print("end : ---{}---".format(time.strftime("%H:%M:%S",time.localtime())))
return wrapper

@runtimedec
def sleeping(n):
print('func exceuting...')
time.sleep(n)

sleeping(10)

'''输出:
start : ---14:01:02---
func exceuting...
end : ---14:01:12---
'''

这个例子中的装饰器是@runtimedec ,可以看到它本身是一个参数为函数的装饰器,返回闭包wrapper 函数。像是做早餐,一开始放进去吐司,往吐司里加点七七八八的东西,最后吐出来一个三明治。
如果不用装饰器方法调用的话,上面执行sleeping 函数应当是这样:

1
2
wrapper = runtimedec(sleeping)
wrapper(10)

这种执行方法就很像functools.partial(),事实上partial 也是一种实现装饰器的方法。本质上说,装饰器的定义是:在不改变原函数的同时为它添加额外的功能。

如何实现装饰器

装饰器除了函数外也有其他实现形式,例如上一段所说的partial。实现装饰器的核心在于callable ,通过两个方法:__init__()__call()__,前者初始化,后者令其可调用 ,就可以实现装饰器功能。

接下来将第一个例子中的@runtimedec 改写成类实现:

1
2
3
4
5
6
7
8
class runtimedec:
def __init__(self, func):
self.func=func

def __call__(self, *args, **kwargs):
print("start : ---{}---".format(time.strftime("%H:%M:%S",time.localtime())))
self.func(*args,**kwargs)
print("end : ---{}---".format(time.strftime("%H:%M:%S",time.localtime())))

和函数类型的runtimedec 相比,类类型基本上是没有差别的,也是传入被装饰的函数,然后执行内容。比较不一样的地方是类不需要返回闭包函数wrapper,这一步由类的__init__ 代劳了。

partial 的实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import partial
import time

def runtimedec(func, *args, **kwargs):

def wrapper(*args,**kwargs):
#code
return wrapper

def pa():
return partial(runtimedec)
@pa()
def sleeping(n):
print('func exceuting...')
time.sleep(n)
sleeping(10)

虽然可以实现功能,但是这种运行方式显得特别多此一举,这也不是使用partial 的初衷。一般情况下,在装饰器中使用partial 的都是装饰器工厂,装饰器工厂是带参数的装饰器,它在装饰器函数外层再套一层函数用来传参。

装饰器工厂

装饰器工厂可以向装饰器传参,一般用来限制装饰器,比如说超时退出、超出次数退出之类的。
下面是对运行次数限制的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def timedeco(time):
def decorate(func):
innerv=1
def wrapper(*args, **kwargs):
try:
nonlocal innerv
print('execute time = ', innerv)

if innerv > time:
raise Exception
innerv += 1
except:
print("现在是第{}次,超过{}了!".format(innerv,time))
return wrapper
return decorate

@timedeco(2)
def exe():
pass
exe()
exe()
exe()
'''输出:
execute time = 1
execute time = 2
execute time = 3
现在是第3次,超过2了!
'''

之前说的partial 就可以用在这里:

1
2
3
4
5
def pa():
return partial(runtimedec,2)
@pa()
def exe():
pass

这是参数少的情况,如果参数多的话可以省很多代码量。

后记

这篇文章算是对装饰器从 定义、实现方法、使用方法 的初探,主要阐述了什么情况下使用、怎么用、和怎么自己拼装一个装饰器的内容。个人感觉比较重要的环节是搞清楚“当前执行的是什么” 和 “谁是参数”,这样程序逻辑会清晰一些。