理解python中的装饰器

一 什么是装饰器?

        正如其名,装饰器的作用是为已经存在的对象增加额外功能(装饰),由此可使已有函数在无需代码改动的情况下增加额外功能;装饰器的本质是嵌套的函数且返回函数对象,即闭包。有关闭包的概念,可参考《理解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,但如果其余的函数也提出了类似的需求,如func1func2都需要增加耗时测量,按上面的实现方式,func1func2也需要在原功能前后增加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节;




猜你喜欢

转载自blog.51cto.com/2681882/2118493