Python的Decorator 杂谈

 

Decorator 这个特性,顾名思义,这是一个内置的装饰器模式的实现,利用这个特征在Python里实现AOP易如反掌,这里我分3个部分来说明Decorator 是什么,怎么写,怎么用

首先我们要知道Decorator 是什么。

Decorator r是装饰器模式的实现,那么 简单的来说就是用一个新的对象来替换掉原有的对象,新的对象包含原有的对象,我们可以像调用原有对象一样的来调用新对象,且新对象的创建我们可以控制,所以可以在调用原有对象的时候实现控制。或者将原有对象处理后原样返回。

我们来举一个例子

假设有一个函数

 

1 def foo():pass

 

 

这个方法什么都不做。现在我们定义一个装饰器来扩展这个函数 

 

1 def before(func):
2     def wapper(*args):
3          print "print before invoke foo"
4          return func(*args)
5     return wapper

 

 

这个装饰器方法通过参数func得到被装饰的对象foo 函数(注意:在Python中一切都是对象,包括类和函数),然后定义了一个新的函数来代替原有的函数,并且返回新函数,由于闭包的特性,在定义的新函数内部能够访问到参数func,所以我们在新函数内部,执行被装饰的函数,并且将被装饰的函数的返回值作为新函数的返回值。

 

1 @before
2 def foo():pass

 

 

如上面这般就能用before装饰器来装饰foo函数了。我们执行foo(),就会显示 print before invoke foo。

这是最简单的情况。 下面我们来看装饰函数的Decorator ,装饰类的Decorator ,带参数的Decorator ,用类的方式来写Decorator 

在上面的例子我们知道了装饰器的基本形式,被装饰的对象通过第一层函数的参数传入,那么如果装饰的是一个函数,那么第一个参数就是函数对象,如果是类那么就是类的对象。

函数的装饰我们看过了,下面来看一个装饰类的。通常我们调用类的形式都是实例化这个类,所以我们装饰的也是实例化的过程。

 

代码
1 def single(cls):
2     ins={}                         #存储所有的类的唯一实例
3     def getins(*args):             #用于替代类的装饰方法,返回相当于是类的工厂函数。
4         if cls not in ins:         #如果类的实例不在缓存中
5             ins[cls]=cls(*args)    #实例化对象,加入缓存
6         return ins[cls]            #返回缓存中的唯一实例
7     return getins                  #返回工厂函数

 

 

这个例子我们可以看出如果用一个函数来代替类 ,那么结果就是我们可以扩展一个类的构造过程。当然在这里我们不知能够扩展实例化 的过程,扩展类对象本身也是没有问题的。


如果我们想在装饰一个对象的时候加上参数怎么办呢?那么就需要用另外一种写法了。我们来看看:

 

1 def author(name):
2     def add(cls):
3         setattr(cls,"__author__",name)
4         return cls
5     return add

 

 

这个装饰器为一个类对象加上 __author__的值

如果是想要给类的实例加上 __author__ 的值就要像下面这么写

 

1 def author(name):
2     def add(cls):
3         def wapper(*args):
4             ins=cls(*args)
5             setattr(ins,"__author__",name)
6             return ins
7         return wapper
8     return add

 

 

为了给类的实例对象加入值我们就不能返回类对象本身而是返回实例化类的方法

我们可以看到上面的装饰器都是一个个的函数,一般来说我们利用高阶函数和闭包的特性来延迟具体操作的实现,不过闭包也有自身的局限性,所以我们还可以用Python的类形的特征来用类来实现装饰器。

我们将装饰器可以看作是一次或者是两次函数调用,如果不包含参数就是调用一次,如果是包含参数就是两次,一次是传递参数,一次是传递要装饰的对象。而Python的类实例化其实和函数调用的形式差不多,所以我们可以用类的构造器来实现第一次调用,而Python的类特殊函数中有一个__call__函数可以使类实例变成可以和函数一样的调用,这样又可以实现第二次调用,我们把不带参数的和带参数的分别用类来实现一次:

不带参数:

 

1 class before:
2     def __init__(self,func):
3         def wapper(*args):
4             print "before invoke"
5             return func(*args)
6         return wapper

 

 


带参数

 

代码
1 class author:
2     def __init__(self,name):
3         self._name=name
4     def __call__(self,cls):
5         def wapper(*args):
6             ins=cls(*args)
7             setattr(ins,"__author__",self._name)
8             return ins
9         return wapper

 

 


刚才我们说到了装饰器其实是系统自动发起的两次函数调用,但是系统在什么时候调用的呢?是在你import这个模块的时候。我们可以用下面的例子来实验

 

代码
 1 class author:
 2     def __init__(self,name):
 3         print "first invoke in __init__"
 4         self._name=name
 5     def __call__(self,cls):
 6         print "second invoke in __call__"
 7         def wapper(*args):
 8             ins=cls(*args)
 9             setattr(ins,"__author__",self._name)
10             return ins
11         return wapper
12 
13 @author
14 class A:pass

 

 

将上面的代码保存到一个py文件中,然后新建另外一个py文件,import 这个模块后不做任何操作,然后就能看到

first invoke in __init__

second invoke in __call__

这两行

利用这个特征我们可以很轻松实现很多特殊的效果,比如生成Django的Url映射。

然后我们还要注意Decorator 的这个特性可能引发的问题,因为其实很多时候我们是返回了新的对象替换了原有对象,而且是在import后,这样子会造成很多功能,比如pydoc等在使用了装饰器后就不好用了,因为得到的文档其实是新对象的,所以你在用PyDoc自动生成文档的时候就得不到原有对象的__doc__信息,所以在实际的使用场景我们还有在返回前将__doc__,__name__等替换过来。比如

 

代码
1 class before:
2     def __init__(self,func):
3         def wapper(*args):
4             print "before invoke"
5             return func(*args)
6         wapper.__name__=func.__name__
7         wapper.__doc__=func.__doc__
8         return wapper

 

 


相信通过本文你应该清楚Decorator 的基本特征了,剩下的就是大家一起来发掘更多的用法了

 

 

posted on 2010-09-04 16:37  亚历山大同志  阅读(6459)  评论(2编辑  收藏  举报

导航