正如其名,装饰器的作用是为已经存在的对象增加额外功能(装饰),由此可使已有函数在无需代码改动的情况下增加额外功能;装饰器的本质是嵌套的函数且返回函数对象,即闭包。有关闭包的概念,可参考《理解Python中的闭包》一文。
二 装饰器应用场景举例
在介绍装饰器之前,我们思考下遇到如下场景时的解决思路,然后在此基础上,描述装饰器的意义和旨在解决哪些问题。
假如我们的func函数已经在使用,而且工作的挺好,func函数如下:
def func(): print("执行func代码块")
然而,某天针对该函数有新需求提出,暂时命名为需求1:为func函数增加性能度量,即测量函数的执行耗时。
针对需求1,一种可以简单理解的实现形式如下:
#实现1 增加执行耗时统计 def func(): start_time = time.time() print("执行func代码块") end_time = time.time() print("执行耗时:%s"%(end_time-start_time))
从实现来看,满足了需求1,但如果其余的函数也提出了类似的需求,如func1、func2都需要增加耗时测量,按上面的实现方式,func1、func2也需要在原功能前后增加time.time()代码,这样就造成了代码重复和冗余。
针对代码重复的问题,另一种实现方式是,定义一个专用的耗时测量函数,当需要测量某个函数时,直接将被测函数作为该测量函数的入参,实现形式如下:
# 实现2将公共代码抽离,定义专用函数 def count_time(func_name): start_time = time.time() func_name() end_time = time.time() print("执行耗时:%s"%(end_time-start_time))
从上面实现来看,功能耗时统计的代码被抽离并定义在共用函数count_time里,当需要测量某个函数时,直接调用该count_time函数即可。
如:count_time(func) #对func函数进行耗时统计 如:count_time(func1) #对func1函数进行耗时统计
但这种实现也是有问题的,需求是希望直接调用func()函数即可完成对功能耗时的测量,但目前的实现方式,需要使用另外一个函数count_time,显然改变了函数的调用方式。
三 装饰器为原有函数增加额外功能
针对上面场景描述及问题分析,我们使用装饰器解决该问题,首先定义装饰器函数如下:
#定义装饰器函数 def timer(func_name): def wrapper(*args,**kwargs): start_time = time.time() func_name() end_time = time.time() print("执行耗时:%s" % (end_time - start_time)) return wrapper
从装饰器的定义来看,其实就是一个闭包实现,满足了在上篇对闭包三个条件的定义:
1) timer函数内部定义了wrapper函数,满足函数嵌套;
2) 内部函数wrapper使用了外部函数 timer的变量func_name;
3) 返回是一个内部函数的引用;
因此,装饰器本质是函数,是闭包的应用。
现在,我们用装饰器timer实现对func函数所提出的需求,即增加函数耗时测量功能。
使用装饰器的方法如下:
func = timer(func)#1 func() #2
使用上面2步,调用func()时,虽然函数名相同,原func()函数也未做改动,但输出结果已经增加了耗时统计功能。如果不理解,请继续看上篇《理解Python中的闭包》中关于闭包的讲解J。
另外,python的装饰器有个更简洁的表示方式,即使用“语法糖”@,如下:
@timer #@语法糖 相当于 test1 = timer(test1) 只能放在定义函数的上面 def func(): print("执行func代码块")
#使用装饰器语法糖@的调用方式
func()#直接调用即可
四 装饰器的作用
从上文分析,可以了解到,装饰器主要用于为已存在的函数对象附件额外的功能,而原先的函数的内部实现可以不做改动,调用方式也保持不变,而这些附加的功能是可以抽离出来作为共用的,避免了相似场景下代码的冗余。
其他资源分享:
关于装饰器:请参看http://i.youku.com/weiworld521 第21节;
原文地址:http://blog.51cto.com/2681882/2118493