前言

decorator是python中十分常用的一种东西,网上也已经有很多文章介绍这东西的用法,但是没有看到一个比较全面的文章。这里尝试对看到的各种资料进行总结,便于温故知新。

decorator for functions and methods

PEP 318提出了decorator语法,

@dec2
@dec1
def func(arg1, arg2, ...):
    pass

等价于,

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

可以看到decorator可以认为是函数调用的语法糖,decorator的语法简化了这种链式函数调用。

decorator for class

PEP 3129里面提出了用在类上的decorator,两者的语法基本一样,

@foo
@bar
class A:
  pass

等价于,

class A:
  pass
A = foo(bar(A))

PEP 3129里面也提及了在class已经有metaclass这个利器的情况下引入class decorator的争议。在class上使用decorator与使用metaclass的不同之处在于,metaclass会存在于类的继承体系中,而decorator不会。像上面说的那样,decorator只是函数调用的语法糖。

在实际开发中,还没有遇到需要实现类上的decorator的场景。

decorator定义

定义成函数的decorator

看到的那些文章基本都从最简单的例子开始说明,这里反过来介绍,先给比较完备的decorator例子,再给出最糙的版本。

实际应用中decorator需要满足的需求有,

  • 支持参数设置
def decorator(to_upper):
  def _decorator(func):
      def _func(*args, **kwargs):
        res = func(*args, **kwargs)
        if to_upper:
            res = res.upper()
        return res
      return _func
  return _decorator

@decorator(to_upper=True)
def foobar(msg):
  return msg

print foobar("hello, world")  # HELLO, WORLD

foobar = decorator(to_upper=True)(foobar)

print foobar("hello, world")  # HELLO, WORLD

上面给出了一个带参数的decorator的样例以及等价的函数调用,

foobar = decorator(to_upper=True)(foobar)

如果不需要在decroator里面定义参数会有什么不一样呢?保持上面的形式不变,

def decorator():
  def _decorator(func):
      def _func(*args, **kwargs):
        return func(*args, **kwargs)
      return _func
  return _decorator

@decorator()
def foobar(msg):
  return msg

如果按照最初的形式定义,在不需要参数的情况下还需要写成@decorator(),不加()的话就相当于,

decorator(foobar)

而decorator的函数定义中是没有参数的。所以在不需要decorator参数的情况下,decorator的定义可以减少一层,

def decorator(func):
    def _func(*args, **kwargs):
      return func(*args, **kwargs)
    return _func

@decorator
def foobar():
    pass

这几个代码片段中可以证实decorator只是函数包装的简化版,为了支持在decorator中定义参数,就需要多定义一层函数调用。

  • 保留原函数信息

从上面的代码可以看到,经过decorator的作用,实际上生成的是一个新函数,

def decorator(func):
  def _func(*args, **kwargs):
      return func(*args, **kwargs)
  return _func

@decorator
def foobar(msg):
  return msg

print foobar.__name__  # _func

如果在日志中输出这个信息我们就无法获得原始的函数名称等相关信息,非常不方便,不过标准库的functools提供了一个wraps函数可以帮助解决合格问题,

def decorator(func):
  @wraps(func)
  def _func(*args, **kwargs):
      return func(*args, **kwargs)
  return _func

@decorator
def foobar(msg):
  return msg

print foobar.__name__  # foobar

wraps可以处理__name__,__doc__,__module__,但是无法获取函数的默认参数信息。如果需要进一步获取这部分内容,就需要获取原始函数来进行处理,

# 获取原始函数
original = foobar.__closure__[0].cell_contents
print original.__defaults__

当有多个decorator进行作用的时候,上面的方法也要一层层去获取。

定义成类的decorator

python的function也是对象,在定义类的时候如果定义了__call__,那么也可以像函数一样进行调用,所以decorator也可以定义成类形式。

class Decorator(object):
  def __init__(self, func):
      self.func = func
  def __call__(self, *args, **kwargs):
      return self.func(*args, **kwargs)

@Decorator
def foobar(msg):
  return msg

等价于

foobar = Decorator(foobar)

在定义成类的时候如何保留原函数的信息呢,这里依然可以用functools.wraps进行处理,稍微修改一下,

class Decorator(object):
  def __init__(self, func):
      wraps(func)(self)
      self.func = func
  def __call__(self, *args, **kwargs):
      return self.func(*args, **kwargs)

改进decorator的定义

Painless Decorators里介绍了一种更便利的decorator定义方法,

@decorator
def some_decorator(call):
    # ... do something before
    result = call()
    # ... do something after
    return result

这个库可以从funcy进行下载。这个写法的好处就是通过函数定义decorator的时候更加直观。不过将decorator定义成类的话或多或少也解决了这个问题。

decorator用法

decorator的常见用处就是缓存处理、日志记录等,可以用decorator进行类似AOP编程。在这些用处之外,自己觉得还可以采用类似java annotation的用法,仅仅用decorator来标记特殊的方法或函数,并不修改函数实际的执行逻辑。

def label(func):
  @wraps(func)
  def _label(*args, **kwargs):
      return func(*args, **kwargs)
  return _label

class Foobar(object):
  def __init__(self):
      self.name = 'foobar'
  @label
  def label_name(self):
      return self.name

将decorator简化成annotation,用以标记数据,在另外的地方读取类上含有此标记信息的方法,再进行处理。

总结

Python Cookbook里是将decorator内容放到metaprogramming这个章节,decorator的实质是动态修改了作用的函数或类。对于一些需要统一处理的流程性代码,使用decorator会比较方便。

decorator使用上非常像java annotation,但是其表达能力比annotation要强。java annoation只能用于标记数据,如何处理还需要另外的类实现。不过个人并不觉得decorator的设计更优,至少不认同这篇Python修饰器的函数式编程里对比两者的观点。弄清楚decorator的各种定义比弄明白annotation麻烦多了,因为annotation是一个单一的概念,而decorator则是多种内容的混合。更偏向哪个,就看个人的经验了。

参考