在Python中实现Decorator模式
2009 4 12 12:08 PM 3539次查看
一般而言,扩展只需要继承即可。但因为继承是静态的,无法动态扩展,于是会有很大的局限性。
考虑一下Java的java.io.Reader类,它有很多子类,它们之间的组合如果都用继承来实现的话,我想会是超枯燥的。
所以当我们需要一个缓冲的文件读取器,我们不会去创建一个FileBufferedReader对象,而是像下面这样创建:
Reader reader = new BufferedReader(new FileReader(filename))
这样的好处就是,我们可以随意地动态组合这些功能,而不是为每种组合都创建一个类。在静态语言里,要实现Decorator模式是很麻烦的,因为你可能需要定义3个类。
不过在Python这种动态语言里,你完全不需要定义类,因为函数本身就是可以扩展的。
来看下面这个例子:
#-*- coding: gbk -*-
def whatIsLiving():
print '对啊,怎样才算活着呢?'
whatIsLiving()
print
def eat(fun):
def living():
fun()
print '活着就是吃嘛。'
return living
whatIsLiving = eat(whatIsLiving)
whatIsLiving()
print
def sleep(fun):
def living():
fun()
print '活着还包括睡嘛。'
return living
whatIsLiving = sleep(whatIsLiving)
whatIsLiving()
输出结果:对啊,怎样才算活着呢?这里我定义了eat和sleep这2个装饰函数,它们接收一个函数对象,然后返回一个包装过的living函数。
对啊,怎样才算活着呢?
活着就是吃嘛。
对啊,怎样才算活着呢?
活着就是吃嘛。
活着还包括睡嘛。
于是whatIsLiving函数就从最初的只输出一句,变成了输出3句了。
不过Python的神奇并不仅此而已,因为这是JavaScript也能轻易做到的。Python 2.4中新增了一个叫decorator的语法,只要将@函数名放在需要装饰的函数上面,就能非常简单地装饰它了。
也就是下面这种情况:
def A(fun): ...
def f(): ...
f = A(f)
# 可以简化成下面的方式:
def A(fun): ...
@A
def f(): ...
并且它还支持嵌套。于是我将上面的例子改写成新语法:
#-*- coding: gbk -*-
def eat(fun):
def living():
fun()
print '活着就是吃嘛。'
return living
def sleep(fun):
def living():
fun()
print '活着还包括睡嘛。'
return living
@sleep
@eat
def whatIsLiving():
print '对啊,怎样才算活着呢?'
whatIsLiving()
结果:对啊,怎样才算活着呢?此外,decorator还有第2种语法:
活着就是吃嘛。
活着还包括睡嘛。
@A(args)
def f (): ...
# 其实就是
a = A(args)
def f (): ...
f = a(f)
也就是说,装饰器A本身还可以接受参数args,然后生成一个函数对象a,这个函数对象再接收我们传入的函数对象f,并生成最终所要的函数对象。当然,没人强制你非得封装原来的函数对象,你可以把它改得面目全非:
#-*- coding: gbk -*-
def foo(fun):
return len
@foo
def bar(arg):
return list(arg)
print bar('abcd')
print bar is len
由于foo完全没有使用它的参数fun,所以实际上bar就变成了len函数,结果如下:4再举几个日常中的应用,可能最常见的就是staticmethod和classmethod了:
True
# 老语法
class C:
def f(args): ...
f = staticmethod(f)
# 新语法:
class C:
@staticmethod
def f(args): ...
虽然看上去只是比原来漂亮了些,但却让不支持static关键字的Python可以用类似C++的语法来定义静态方法了。而且更重要的是,老语法必须先写完方法的实现,而你可能会漏写将它设为静态方法的那条语句;但现在你是在定义它之前就写了,相当于一个声明,自然不会漏掉了。
还有一处用过GAE的可能会注意到。当我们执行一些函数时,可能需要用户已经登录。但如果每个函数都去判断的话,会是很麻烦的事,于是我们可以写一个Login Decorator来修饰这些函数。
幸运的是,GAE中已有一个google.appengine.ext.webapp.util.login_required函数了,所以我们只需要写上@login_required修饰即可。
顺便给出其实现,注意版权归Google所有,文档注释中也给出了用法:
def login_required(handler_method):
"""A decorator to require that a user be logged in to access a handler.
To use it, decorate your get() method like this:
@login_required
def get(self):
user = users.get_current_user(self)
self.response.out.write('Hello, ' + user.nickname())
We will redirect to a login page if the user is not logged in. We always
redirect to the request URI, and Google Accounts only redirects back as a GET
request, so this should not be used for POSTs.
"""
def check_login(self, *args):
if self.request.method != 'GET':
raise webapp.Error('The check_login decorator can only be used for GET '
'requests')
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
else:
handler_method(self, *args)
return check_login
你也可以照这里写一个简化的函数。至于更多的介绍,你不妨看看这篇可爱的 Python: Decorator 简化元编程。
我从里面摘抄一段很优雅的代码:
def elementwise(fn):
def newfn(arg):
if hasattr(arg,'__getitem__'):
return type(arg)(map(fn, arg))
else:
return fn(arg)
return newfn
@elementwise
def square(obj):
return obj ** 2
print square(5)
print square(range(10))
结果:25可以看到,square函数本来只能返回对象的平方,而list对象是没有定义乘方操作的,于是会失败。
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
可是elementwise将其进行了包装,当发现对象有__getitem__属性时,便知道它是一个序列,于是用map对整个序列进行处理。
最后,你可以看看decorator这个模块,也是非常实用的。
向下滚动可载入更多评论,或者点这里禁止自动加载。