Python有许多出色的语言特性,装饰器(Decorator)便是其中一朵奇葩。先来看看一段代码:
def deco1(f): print 'decorate 1' return f def deco2(f): print 'decorate 2' return f
@deco1
@deco2 def foo(): return 'hello'
保存并执行上面的代码,你会看到如下输出:
decorate 2
decorate 1
函数foo没有被调用,但是deco1,deco2被按照一个顺序被调用了。deco1和deco2就是装饰器。从上面的输出可以看出两点:
1 装饰器是在代码装载时被调用的
2 调用的顺是从下到上
现在来说说装配器到底做了什么。看代码:
def deco1(f): print 'decorate 1',f return f def deco2(f): print 'decorate 2',f return f @deco1 @deco2 def foo(): return 'hello'
这段代码的执行结果为:
decorate 2 <function foo at 0x00000000021F79E8>
decorate 1 <function foo at 0x00000000021F79E8>
装饰器函数的参数‘f’被打印出来,而且就是我们的函数‘foo’,可见装饰器的参数就是被装饰的函数。真的是这样吗?再来看代码:
def wraper(n,f): def foo1(): return '%s(%s)'%(n,f())return foo1 def deco1(f): print 'decorate 1',f return wraper('decorate 1',f) def deco2(f): print 'decorate 2',f return wraper('decorate 2',f) @deco1 @deco2 def foo(): return 'hello'
这段代码的执行结果为:
decorate 2 <function foo at 0x0000000002147A58>
decorate 1 <function foo1 at 0x0000000002147AC8>
我觉得我现在可以总结一下了:
1 装饰器在代码装载时被调用;
2 调用顺序是从下到上的;
3 被装饰函数‘foo’作为参数传递给第一个装饰器‘deco2’,返回值将作为参数传递给第二个装饰器‘deco1’,然后依次向上直到最顶端的装饰器;
4 最顶端的装饰器的返回值就是被装饰以后的函数,我们暂时称之为,也就是我们将来要执行的那个‘foo’。
下面,来我们说说装饰后的函数,被调用会有什么结果,看代码:
def wraper(n,f): def foo1(): return '%s(%s)'%(n,f()) return foo1 def deco1(f): print 'decorate 1',f return wraper('decorate 1',f) def deco2(f): print 'decorate 2',f return wraper('decorate 2',f) @deco1 @deco2 def foo(): return 'hello' print foo()
这段代码的执行结果为:
decorate 2 <function foo at 0x0000000002127A58> decorate 1 <function foo1 at 0x0000000002127AC8> decorate 1(decorate 2(hello))
不难看出,调用顺序与装饰顺序刚好相反,最终函数也就是最顶端的装饰器返回的函数最先被调用,然后依次调用到最初那个函数‘foo’。我在这里故意规避了一个基本的事实,就是:其实最后被调用的就是顶端装饰返回的那个函数,你必须手动在这个函数中调用前面的函数,见这里:
return '%s(%s)'%(n,f())
,才会产生调用连的效果。所以装饰器并不会帮你完成所有的事情,他给了你充分的自由。说的这里,你们要问了(如果你有足够的好奇心):‘装饰器能带参数吗’。答案是能,见下面的代码:
def wraper(n,f): def foo1(): return '%s(%s)'%(n,f()) return foo1 def deco(n): def deco1(f): print n,f return wraper(n,f) return deco1 @deco('decorate 1') @deco('decorate 2') def foo(): return 'hello' print foo()
这段代码的执行结果为:
decorate 2 <function foo at 0x0000000002117AC8> decorate 1 <function foo1 at 0x0000000002117B38> decorate 1(decorate 2(hello))
哇,和之前的结果一模一样,代码还被简化了不少。这是怎么回事呢,我来简单解释下,函数‘deco’不是装饰器,他只是一个返回装饰器的函数,当你把它放到装饰符号‘@‘后面时,python的语法起了一个美妙的作用,他会先调用这个函数,然后用返回值值作为装饰器。
大概就是这样啦,语法大师也许会描述的更详细更专业。源码狂人还可能深入到python的C代码里寻找成因。不过,作为一个有理智的好青年我们就点到为止吧。
下次,我们再来掰扯一下,装饰器都可以变出哪些戏法吧。洗洗睡了。