Python装饰器(Decorator)简介

时间:2022-02-07 05:53:32

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代码里寻找成因。不过,作为一个有理智的好青年我们就点到为止吧。

下次,我们再来掰扯一下,装饰器都可以变出哪些戏法吧。洗洗睡了。