在Python中实现Decorator模式

标签:Python

Decorator模式一般被翻译成装饰模式,它主要是用于扩展已有的功能。
一般而言,扩展只需要继承即可。但因为继承是静态的,无法动态扩展,于是会有很大的局限性。

考虑一下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
True
再举几个日常中的应用,可能最常见的就是staticmethod和classmethod了:
# 老语法
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
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
可以看到,square函数本来只能返回对象的平方,而list对象是没有定义乘方操作的,于是会失败。
可是elementwise将其进行了包装,当发现对象有__getitem__属性时,便知道它是一个序列,于是用map对整个序列进行处理。

最后,你可以看看decorator这个模块,也是非常实用的。

2条评论 你不来一发么↓ 顺序排列 倒序排列

    向下滚动可载入更多评论,或者点这里禁止自动加载

    想说点什么呢?