少劳多得
Decorator 与 Python 之前引入的元编程抽象有着某些共同之处:即使没有这些技术,您也一样可以实现它们所提供的功能。正如 Michele Simionato 和我在 可爱的 Python 专栏的早期文章 中指出的那样,即使在 Python 1.5 中,也可以实现 Python 类的创建,而不需要使用 “元类” 挂钩。
Decorator 根本上的平庸与之非常类似。Decorator 所实现的功能就是修改紧接 Decorator 之后定义的函数和方法。这总是可能的,但这种功能主要是由 Python 2.2 中引入的 classmethod() 和 staticmethod() 内置函数驱动的。在旧式风格中,您可以调用 classmethod(),如下所示:
清单 1. 典型的 “旧式” classmethod
1
2
3
4
|
class C:
def foo( cls , y):
print "classmethod" , cls , y
foo = classmethod (foo)
|
虽然 classmethod() 是内置函数,但并无独特之处;您也可以使用自己的方法转换函数。例如:
清单 2. 典型的 “旧式” 方法的转换
1
2
3
4
5
6
7
8
9
|
def enhanced(meth):
def new( self , y):
print "I am enhanced"
return meth( self , y)
return new
class C:
def bar( self , x):
print "some method says:" , x
bar = enhanced(bar)
|
decorator 所做的一切就是使您避免重复使用方法名,并且将 decorator 放在方法定义中第一处提及其名称的地方。例如:
清单 3. 典型的 “旧式” classmethod
1
2
3
4
5
6
7
|
class C:
@classmethod
def foo( cls , y):
print "classmethod" , cls , y
@enhanced
def bar( self , x):
print "some method says:" , x
|
decorator 也可以用于正则函数,采用的是与类中的方法相同的方式。令人惊奇的是,这一切是如此简单(严格来说,甚至有些不必要),只需要对语法进行简单修改,所有东西就可以工作得更好,并且使得程序的论证更加轻松。通过在方法定义的函数之前列出多个 decorator,即可将 decorator 链接在一起;良好的判断可以有助于防止将过多 decorator 链接在一起,不过有时候将几个 decorator 链接在一起是有意义的:
清单 4. 链接 decorator
1
2
3
4
5
6
7
|
@synchronized
@logging
def myfunc(arg1, arg2, ...):
# ...do something
# decorators are equivalent to ending with:
# myfunc = synchronized(logging(myfunc))
# Nested in that declaration order
|
Decorator 只是一个语法糖,如果您过于急切,那么它就会使您搬起石头砸了自己的脚。decorator 其实就是一个至少具有一个参数的函数 —— 程序员要负责确保 decorator 的返回内容仍然是一个有意义的函数或方法,并且实现了原函数为使连接有用而做的事情。例如,下面就是 decorator 两个不正确的用法:
清单 5. 没有返回函数的错误 decorator
1
2
3
4
5
6
7
8
9
10
11
12
|
>>> def spamdef(fn):
... print "spam, spam, spam"
...
>>> @spamdef
... def useful(a, b):
... print a * * 2 + b * * 2
...
spam, spam, spam
>>> useful( 3 , 4 )
Traceback (most recent call last):
File "<stdin>" , line 1 , in ?
TypeError: 'NoneType' object is not callable
|
decorator 可能会返回一个函数,但这个函数与未修饰的函数之间不存在有意义的关联:
清单 6. 忽略传入函数的 decorator
1
2
3
4
5
6
7
8
9
10
11
|
>>> def spamrun(fn):
... def sayspam( * args):
... print "spam, spam, spam"
... return sayspam
...
>>> @spamrun
... def useful(a, b):
... print a * * 2 + b * * 2
...
>>> useful( 3 , 4 )
spam, spam, spam
|
最后,一个表现更良好的 decorator 可以在某些方面增强或修改未修饰函数的操作:
清单 7. 修改未修饰函数行为的 decorator
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> def addspam(fn):
... def new( * args):
... print "spam, spam, spam"
... return fn( * args)
... return new
...
>>> @addspam
... def useful(a, b):
... print a * * 2 + b * * 2
...
>>> useful( 3 , 4 )
spam, spam, spam
25
|
您可能会质疑,useful() 到底有多么有用?addspam() 真的是那样出色的增强 吗?但这种机制至少符合您通常能在有用的 decorator 中看到的那种模式。
高级抽象简介
根据我的经验,元类应用最多的场合就是在类实例化之后对类中的方法进行修改。decorator 目前并不允许您修改类实例化本身,但是它们可以修改依附于类的方法。这并不能让您在实例化过程中动态添加或删除方法或类属性,但是它让这些方法可以在运行时根据环境的条件来变更其行为。现在从技术上来说,decorator 是在运行 class 语句时应用的,对于*类来说,它更接近于 “编译时” 而非 “运行时”。但是安排 decorator 的运行时决策与创建类工厂一样简单。例如:
清单 8. 健壮但却深度嵌套的 decorator
1
2
3
4
5
6
7
|
def arg_sayer(what):
def what_sayer(meth):
def new( self , * args, * * kws):
print what
return meth( self , * args, * * kws)
return new
return what_sayer
|