浅析Python编写函数装饰器
Jun 10, 2016 pm 03:05 PM编写函数装饰器
本节主要介绍编写函数装饰器的相关内容。
跟踪调用
如下代码定义并应用一个函数装饰器,来统计对装饰的函数的调用次数,并且针对每一次调用打印跟踪信息。
class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args): self.calls += 1 print('call %s to %s' %(self.calls, self.func.__name__)) self.func(*args) @tracer def spam(a, b, c): print(a + b + c)
这是一个通过类装饰的语法写成的装饰器,测试如下:
>>> spam(1,2,3) call 1 to spam 6 >>> spam('a','b','c') call 2 to spam abc >>> spam.calls 2 >>> spam <__main__.tracer object at 0x03098410>
运行的时候,tracer类和装饰的函数分开保存,并且拦截对装饰的函数的随后的调用,以便添加一个逻辑层来统计和打印每次调用。
装饰之后,spam实际上是tracer类的一个实例。
@装饰器语法避免了直接地意外调用最初的函数。考虑如下所示的非装饰器的对等代码:
calls = 0 def tracer(func,*args): global calls calls += 1 print('call %s to %s'%(calls,func.__name__)) func(*args) def spam(a,b,c): print(a+b+c)
测试如下:
? 1 2 3 4 5 >>> spam(1,2,3) 6 >>> tracer(spam,1,2,3) call 1 to spam 6
这一替代方法可以用在任何函数上,且不需要特殊的@语法,但是和装饰器版本不同,它在代码中调用函数的每个地方都需要额外的语法。尽管装饰器不是必需的,但是它们通常是最为方便的。
扩展——支持关键字参数
下述代码时前面例子的扩展版本,添加了对关键字参数的支持:
class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args,**kargs): self.calls += 1 print('call %s to %s' %(self.calls, self.func.__name__)) self.func(*args,**kargs) @tracer def spam(a, b, c): print(a + b + c) @tracer def egg(x,y): print(x**y)
测试如下:
>>> spam(1,2,3) call 1 to spam 6 >>> spam(a=4,b=5,c=6) call 2 to spam 15 >>> egg(2,16) call 1 to egg 65536 >>> egg(4,y=4) call 2 to egg 256
也可以看到,这里的代码同样使用【类实例属性】来保存状态,即调用的次数self.calls。包装的函数和调用计数器都是针对每个实例的信息。
使用def函数语法写装饰器
使用def定义装饰器函数也可以实现相同的效果。但是有一个问题,我们也需要封闭作用域中的一个计数器,它随着每次调用而更改。我们可以很自然地想到全局变量,如下:
calls = 0 def tracer(func): def wrapper(*args,**kargs): global calls calls += 1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) @tracer def egg(x,y): print(x**y)
这里calls定义为全局变量,它是跨程序的,是属于整个模块的,而不是针对每个函数的,这样的话,对于任何跟踪的函数调用,计数器都会递增,如下测试:
>>> spam(1,2,3) call 1 to spam 6 >>> spam(a=4,b=5,c=6) call 2 to spam 15 >>> egg(2,16) call 3 to egg 65536 >>> egg(4,y=4) call 4 to egg 256
可以看到针对spam函数和egg函数,程序用的是同一个计数器。
那么如何实现针对每一个函数的计数器呢,我们可以使用Python3中新增的nonlocal语句,如下:
def tracer(func): calls = 0 def wrapper(*args,**kargs): nonlocal calls calls += 1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) @tracer def egg(x,y): print(x**y) spam(1,2,3) spam(a=4,b=5,c=6) egg(2,16) egg(4,y=4)
运行如下:
call 1 to spam 6 call 2 to spam 15 call 1 to egg 65536 call 2 to egg 256
这样,将calls变量定义在tracer函数内部,使之存在于一个封闭的函数作用域中,之后通过nonlocal语句来修改这个作用域,修改这个calls变量。如此便可以实现我们所需求的功能。
陷阱:装饰类方法
【注意,使用类编写的装饰器不能用于装饰某一类中带self参数的的函数,这一点在Python装饰器基础中介绍过】
即如果装饰器是如下使用类编写的:
class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args,**kargs): self.calls += 1 print('call %s to %s'%(self.calls,self.func.__name__)) return self.func(*args,**kargs)
当它装饰如下在类中的方法时:
class Person: def __init__(self,name,pay): self.name = name self.pay = pay @tracer def giveRaise(self,percent): self.pay *= (1.0 + percent)
这时程序肯定会出错。问题的根源在于,tracer类的__call__方法的self——它是一个tracer实例,当我们用__call__把装饰方法名重绑定到一个类实例对象的时候,Python只向self传递了tracer实例,它根本没有在参数列表中传递Person主体。此外,由于tracer不知道我们要用方法调用处理的Person实例的任何信息,没有办法创建一个带有一个实例的绑定的方法,所以也就没有办法正确地分配调用。
这时我们只能通过嵌套函数的方法来编写装饰器。
计时调用
下面这个装饰器将对一个装饰的函数的调用进行计时——既有针对一次调用的时间,也有所有调用的总的时间。
import time class timer: def __init__(self,func): self.func = func self.alltime = 0 def __call__(self,*args,**kargs): start = time.clock() result = self.func(*args,**kargs) elapsed = time.clock()- start self.alltime += elapsed print('%s:%.5f,%.5f'%(self.func.__name__,elapsed,self.alltime)) return result @timer def listcomp(N): return [x*2 for x in range(N)] @timer def mapcall(N): return list(map((lambda x :x*2),range(N))) result = listcomp(5) listcomp(50000) listcomp(500000) listcomp(1000000) print(result) print('allTime = %s'%listcomp.alltime) print('') result = mapcall(5) mapcall(50000) mapcall(500000) mapcall(1000000) print(result) print('allTime = %s'%mapcall.alltime) print('map/comp = %s '% round(mapcall.alltime/listcomp.alltime,3))
运行结果如下:
listcomp:0.00001,0.00001 listcomp:0.00885,0.00886 listcomp:0.05935,0.06821 listcomp:0.11445,0.18266 [0, 2, 4, 6, 8] allTime = 0.18266365607537918 mapcall:0.00002,0.00002 mapcall:0.00689,0.00690 mapcall:0.08348,0.09038 mapcall:0.16906,0.25944 [0, 2, 4, 6, 8] allTime = 0.2594409060462425 map/comp = 1.42
这里要注意的是,map操作在Python3中返回一个迭代器,所以它的map操作不能和一个列表解析的工作直接对应,即实际上它并不花时间。所以要使用list(map())来迫使它像列表解析那样构建一个列表
添加装饰器参数
有时我们需要装饰器来做一个额外的工作,比如提供一个输出标签并且可以打开或关闭跟踪消息。这就需要用到装饰器参数了,我们可以使用装饰器参数来制定配置选项,这些选项可以根据每个装饰的函数而编码。例如,像下面这样添加标签:
def timer(label = ''): def decorator(func): def onCall(*args): ... print(label,...) return onCall return decorator @timer('==>') def listcomp(N):...
我们可以将这样的结果用于计时器中,来允许在装饰的时候传入一个标签和一个跟踪控制标志。比如,下面这段代码:
import time def timer(label= '', trace=True): class Timer: def __init__(self,func): self.func = func self.alltime = 0 def __call__(self,*args,**kargs): start = time.clock() result = self.func(*args,**kargs) elapsed = time.clock() - start self.alltime += elapsed if trace: ft = '%s %s:%.5f,%.5f' values = (label,self.func.__name__,elapsed,self.alltime) print(format % value) return result return Timer
这个计时函数装饰器可以用于任何函数,在模块中和交互模式下都可以。我们可以在交互模式下测试,如下:
>>> @timer(trace = False) def listcomp(N): return [x * 2 for x in range(N)] >>> x = listcomp(5000) >>> x = listcomp(5000) >>> x = listcomp(5000) >>> listcomp <__main__.timer.<locals>.Timer object at 0x036DCC10> >>> listcomp.alltime 0.0011475424533080223 >>> >>> @timer(trace=True,label='\t=>') def listcomp(N): return [x * 2 for x in range(N)] >>> x = listcomp(5000) => listcomp:0.00036,0.00036 >>> x = listcomp(5000) => listcomp:0.00034,0.00070 >>> x = listcomp(5000) => listcomp:0.00034,0.00104 >>> listcomp.alltime 0.0010432902706075842</locals>
有关Python编写函数装饰器相关知识小编就给大家介绍到这里,希望对大家有所帮助!

Article chaud

Outils chauds Tags

Article chaud

Tags d'article chaud

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Comment utiliser la belle soupe pour analyser HTML?

Comment utiliser Python pour trouver la distribution ZIPF d'un fichier texte

Comment effectuer l'apprentissage en profondeur avec TensorFlow ou Pytorch?

Introduction à la programmation parallèle et simultanée dans Python

Sérialisation et désérialisation des objets Python: partie 1

Comment implémenter votre propre structure de données dans Python

Modules mathématiques en python: statistiques
