什么是装饰器。
装饰器是 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): 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
|
这是参数少的情况,如果参数多的话可以省很多代码量。
后记
这篇文章算是对装饰器从 定义、实现方法、使用方法 的初探,主要阐述了什么情况下使用、怎么用、和怎么自己拼装一个装饰器的内容。个人感觉比较重要的环节是搞清楚“当前执行的是什么” 和 “谁是参数”,这样程序逻辑会清晰一些。