python之装饰器详解
这几天翻看python语法,看到装饰器这里着实卡了一阵,最初认为也就是个函数指针的用法,但仔细研究后发现,不止这么简单。
首先很多资料将装饰器定义为AOP的范畴,也就是Aspect Oriented Programming面向切面编程的概念,不懂AOP不要紧,只要有函数指针的概念,又有嵌套函数的基础知识,看懂此文一点压力都没有。
先说说为什么要有装饰器这么个东西存在吧,这是一种设计模式,较为经典的有插入日志、性能测试、事务处理等等。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
关于嵌套函数,C/C++程序员可能会觉得一头雾水,因为谭老爷子的书明确指出函数不可以嵌套定义,在C++ primer也仅仅有嵌套类的讲解,并不存在嵌套函数一说,但是在一些高级语言中,比如Object C,swift都存在嵌套函数主题,感兴趣的同学可以去翻一下,尤其是当下热门swift,嵌套函数和闭包密不可分,可以方便的实现很多逻辑,比如返回一个函数给调用者。
书归正传,所谓装饰器,顾名思义,就是用来装饰的神器,你有一个函数A(),觉得它不够漂亮,但是又不想重写它,咋办呢?装饰器(Decorator)这时候就派上用场了。
为了方便大家的理解,我先写一个简单的嵌套函数:
def funcA(): print("i am funcA()!") def funcB(): print("i am funcB()") return funcB callSth=funcA()//调用funcA得到返回的funcB callSth()//相当于调用funcB
值得说明一点,在python里只要你不怕蛋疼,你可以任意嵌套,哪怕你嵌套个百十层直到栈溢出都没问题。
如下:
def ppp(): print("return vlaue") def first(): a=10// a被嵌套内的函数捕获! print("the first layer") def second(): print("the second layer %d" % a) def third(): print("the third layer %d " % a) def forth(): print("the forth layer") return ppp() return forth return third return second aaa=first()()()//得到forth() aaa()
关于嵌套函数是这样执行的,先执行最外层的first(),返回second然后发现后面跟着(),那继续返回third,以此类推,聪明如你,肯定明白了。嵌套函数就这样,简单到爆。如果到这里诸君理解了,装饰器基本上也就理解了。
下面正式开始装饰器的概念(开启摘抄模式):
装饰器的定义很是抽象,我们来看一个小例子。
def foo(): print 'in foo()'
foo()
这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:
import time def foo(): start = time.clock() print 'in foo()' end = time.clock() print 'used:', end - start foo()
很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。
怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?
以不变应万变:还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!
import time def foo(): print 'in foo()' def timeit(func): start = time.clock() func() end =time.clock() print 'used:', end - start timeit(foo)
看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。
最大限度地少改动!
既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!
一个真正意义上的装饰器诞生了:
#-*- coding: UTF-8 -*- import time def foo(): print 'in foo()' # 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法 def timeit(func): # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装 def wrapper(): start = time.clock() func() end =time.clock() print 'used:', end - start # 将包装后的函数返回 return wrapper foo = timeit(foo) foo()
样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。
语法糖
上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。
import time def timeit(func): def wrapper(): start = time.clock() func() end =time.clock() print 'used:', end - start return wrapper @timeit// syntax sugar 是也,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。 def foo(): print 'in foo()' foo()
给出原文链接:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
相关资料:http://blog.csdn.net/thy38/article/details/4471421
http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html