编写函数装饰器
本节主要介绍编写函数装饰器的相关内容。
跟踪调用
如下代码定义并应用一个函数装饰器,来统计对装饰的函数的调用次数,并且针对每一次调用打印跟踪信息。
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编写函数装饰器相关知识小编就给大家介绍到这里,希望对大家有所帮助!

Python和C 各有优势,选择应基于项目需求。1)Python适合快速开发和数据处理,因其简洁语法和动态类型。2)C 适用于高性能和系统编程,因其静态类型和手动内存管理。

选择Python还是C 取决于项目需求:1)如果需要快速开发、数据处理和原型设计,选择Python;2)如果需要高性能、低延迟和接近硬件的控制,选择C 。

通过每天投入2小时的Python学习,可以有效提升编程技能。1.学习新知识:阅读文档或观看教程。2.实践:编写代码和完成练习。3.复习:巩固所学内容。4.项目实践:应用所学于实际项目中。这样的结构化学习计划能帮助你系统掌握Python并实现职业目标。

在两小时内高效学习Python的方法包括:1.回顾基础知识,确保熟悉Python的安装和基本语法;2.理解Python的核心概念,如变量、列表、函数等;3.通过使用示例掌握基本和高级用法;4.学习常见错误与调试技巧;5.应用性能优化与最佳实践,如使用列表推导式和遵循PEP8风格指南。

Python适合初学者和数据科学,C 适用于系统编程和游戏开发。1.Python简洁易用,适用于数据科学和Web开发。2.C 提供高性能和控制力,适用于游戏开发和系统编程。选择应基于项目需求和个人兴趣。

Python更适合数据科学和快速开发,C 更适合高性能和系统编程。1.Python语法简洁,易于学习,适用于数据处理和科学计算。2.C 语法复杂,但性能优越,常用于游戏开发和系统编程。

每天投入两小时学习Python是可行的。1.学习新知识:用一小时学习新概念,如列表和字典。2.实践和练习:用一小时进行编程练习,如编写小程序。通过合理规划和坚持不懈,你可以在短时间内掌握Python的核心概念。

Python更易学且易用,C 则更强大但复杂。1.Python语法简洁,适合初学者,动态类型和自动内存管理使其易用,但可能导致运行时错误。2.C 提供低级控制和高级特性,适合高性能应用,但学习门槛高,需手动管理内存和类型安全。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3汉化版
中文版,非常好用

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。