当编译器经过这段代码时,aFunction()被编译然后将结果函数对象传递给myDecorator代码,后者创建一个类函数对象并取代原来的aFunction()。
myDecorator代码长什么样呢?大多数介绍性的例子都将其作为函数给出,但我发现对于decoration机制使用类而非函数来理解decorators会更容易些,而且也更强大。
Decorator所返回的对象唯一的约束是它可以作为函数使用–也就意味着它必须是可调用的。因此,作为decorators使用的任何一个类必须实现__call__。
decorator该做什么呢?它什么都能做,只是你常常期望在某些地方能够使用原来的函数代码。其实没有这个必要:
class myDecorator(object):
def __init__(self, f):
print “inside myDecorator.__init__()”
f() # Prove that function definition has completed
def __call__(self):
print “inside myDecorator.__call__()”
@myDecorator
def aFunction():
print “inside aFunction()”
print “Finished decorating aFunction()”
aFunction()
当运行这段代码时,会看见:
inside myDecorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside myDecorator.__call__()
注意,myDecorator的构造器(constructor)在函数的decoration处执行。由于我们可以在__init__()里面调用f(),它意味着在调用decorator前就完成了f()的创建。另外需注意,decorator构造器接收到decorated的函数对象。你将在构造器中得到函数对象,之后在__call__()方法中进行使用(当使用类时,decoration和调用是两个泾渭分明的阶段,这也是我为何说它更简单、更强大的原因)。
当decorated之后再调用aFunction(),它的行为就完全不一样了:不再用原来的代码而开始调用myDecorator.__call__()方法。原因是decoration过程是将decoration结果取代原先的函数–在我们的例子中,myDecorator对象取代了aFunction。实际上,加入decorators之前,为了达到同样效果你需要编写十分晦涩的代码:
def foo(): pass
foo = staticmethod(foo)
有了‘@’这个decoration操作符的加入,你可以这样做:
@staticmethod
def foo(): pass
这也是有人为何反对decorators的原因。因为‘@’实在有点像一块语法糖(syntax sugar,意指指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜“的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。–译者注):通过另外一个函数传递函数对象,然后将结果赋给原来的函数。
我认为,decorator之所以产生如此大影响,就是因为其带着一点点语法糖的味道改变了人们思考程序设计的方式。事实上,当成一种语言概念对其进行形式化,这种方法使“将代码应用到其他代码之中”(比如宏)的思想出现在主流思想之中。
更多用处
现在,让我们回过头来实现第一个例子。这里我们实现一些更常规的东西,并在decorated函数中实际使用这些代码:
class entryExit(object):
def __init__(self, f):
self.f = f
def __call__(self):
print “Entering”, self.f.__name__
self.f()
print “Exited”, self.f.__name__
@entryExit
def func1():
print “inside func1()”
@entryExit
def func2():
print “inside func2()”
func1()
func2()
输出是:
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
现在可以看见,在整个调用期间decorated函数拥有了“Entering”和“Exited”跟踪语句。
构造器保存着自变量,即函数对象。在调用中,我们使用函数的__name__属性来显示函数名称,然后调用函数本身。
作为Decorators使用函数
对于一个decorator结果,其唯一的约束是必须是可调用的。这样它就可以完全取代decorated函数。在上面的例子中,我用包含一个__call__()方法的类的一个对象替代原有的函数。但函数对象仍是可调用的。因此我们可以使用函数(而非类)重写上一个例子,即:
def entryExit(f):
def new_f():
print “Entering”, f.__name__
f()
print “Exited”, f.__name__
return new_f
@entryExit
def func1():
print “inside func1()”
@entryExit
def func2():
print “inside func2()”
func1()
func2()
print func1.__name__
new_f()在entryExit()里定义,所以在调用entryExit()时它就创建并返回了。注意,new_f()是一个闭包(closure),因为它获得的是f的实际值。
只要定义过new_f(),它就从entryExit()返回,这样decorator机制就可以将结果作为decorated函数进行赋值了。
‘print func1.__name__’一行的输出是new_f,因为new_f函数在decoration期间已取代原函数。如果还有疑问,可以在函数返回前改变decorator函数的名字:
def entryExit(f):
def new_f():
print “Entering”, f.__name__
f()
print “Exited”, f.__name__
new_f.__name__ = f.__name__
return new_f
动态获得有关函数的信息、对函数的修改能力,都是Python的强大之处。
更多例子
既然你有一定基础了,在这里可以看到更多一些decorators的例子。注意使用类作为decorators的例子数量远多于使用函数的例子。
在本文中我有意避开含有自变量的decorated函数,在下一篇文章中我将重点讨论它。