Python中decorator的使用

Python中的decorator用法很有意思,是一个类似decorator模式的典型的AOP特性。

例如我们原来有一个函数是这样的:

def openurl(url):
return urllib2.urlopen(url, timeout = 5).read()


如果我们想在进入这个函数和退出这个函数的时候打印日志,可以这样写:

def openurl(url):
print 'entering openurl'
result = urllib2.urlopen(url, timeout = 5).read()
print 'leaving openurl'
return result

 

当然,如果有很多这样的函数,我们就会比较麻烦,更麻烦的是可能需要有时候打开日志,有时候关闭日志,这时候我们显然需要一个全局变量来控制每个函数里日志的开关。decorator可以帮我们一把,就像这样:

def logfn(fn):
def _(*args, **kw):
print 'entering ' + fn.__name__
v = fn(*args, **kw)
print 'leaving ' + fn.__name__
return v
return _

@logfn
def openurl(url):
return urllib2.urlopen(url, timeout = 5).read()

 

@logfn

def openurl(url):

这种形式表示的是将openurl的调用作为参数传给logfn,由logfn在内部调用(或者不调用)它,同时logfn必须返回一个与openurl返回值兼容的返回值。同时我们又可以利用python丰富的反射特性得到被调用函数的一系列属性,例如它的名字(__name__),这样写日志就方便了。

除了写成函数的形式外,因为python的类也支持__call__,可以将一个类的实例通过()的形式进行函数调用,因此实际上我们也可以写一个类来做decorator,类做decorator的好处是可以传参,在实例初始化的时候获得参数,进行必要的计算与载入必要的资源。如下:

class cacheopen(object):
def __init__(self, path = './naivecache'):
self.path = path
self.repo = dict()

def __call__(self, fn):
def cache_read(*args, **kw):
url = args[0] if len(args) > 0 else kw['url'] if 'url' in kw.keys() else None
if url in self.repo.keys():
return self.repo[url]
value = fn(*args, **kw)
self.repo[url] = value
return value
return cache_read

@cacheopen(path = './dummycache')
def openurl(url):
return urllib2.urlopen(url, timeout = 5).read()

 

cacheopen类是一个实现了__call__的类,因此可以被当做函数调用。它在初始化时需要的参数由decorator的path参数传入。它的主要功能就是将函数的调用结果缓存起来,避免多次重复的网络传输。

不过从这里也看出一个问题,那就是decorator的参数是在写程序的时候就确定了的,不太方便通过运行时用户输入的值进行调整(当然,其实也是可以的,例如将类变量提升为全局变量,可以从全局变量中读取,不过全局变量还是希望尽量避免的)。因此,如果希望在运行的时候能够载入不同的参数,在这里,就是例如不同的程序载入不同的缓存文件,那么使用decorator可能不一定很方便,仍然可以采用传统一点的方式,将一个cache变量传给openurl,让openurl先读取缓存,再urlopen,同时不忘更新缓存即可。

 

 

posted on 2012-01-09 00:17  熊猫凶猛  阅读(411)  评论(0编辑  收藏  举报