decorator in python

<crquan@gmail.com>

背景

从2.2开始,就有了staticmethod和classmethod,但直到2.4以前,还必须这样定义:

def foo(cls, arg):
    perform method operation
    ...
    this method definition is
    very
    very long.
foo = classmethod(foo)

decorator

@classmethod
def foo(cls, arg):
    perform method operation
    ...
    this method definition is
    very
    very long.

用途示例之一: 注册退出函数

def onexit(f):
    import atexit
    atexit.register(f)
    return f

@onexit
def func():
    ...

用途示例之二: 为函数增加属性

def attrs(**kwds):
    def decorate(f):
        for k in kwds:
            setattr(f, k, kwds[k])
        return f
    return decorate

@attrs(versionadded="2.2",
       author="Guido van Rossum")
def mymethod(f):
    ...

用途示例之三: 声明参数类型

def accepts(*types):
    def check_accepts(f):
        assert len(types) == f.func_code.co_argcount
        def new_f(*args, **kwds):
            for (a, t) in zip(args, types):
                assert isinstance(a, t), \
                       "arg %r does not match %s" % (a,t)
            return f(*args, **kwds)
        new_f.func_name = f.func_name
        return new_f
    return check_accepts

def returns(rtype):
    def check_returns(f):
        def new_f(*args, **kwds):
            result = f(*args, **kwds)
            assert isinstance(result, rtype), \
                   "return value %r does not match %s" % (result,rtype)
            return result
        new_f.func_name = f.func_name
        return new_f
    return check_returns

@accepts(int, (int,float))
@returns((int,float))
def func(arg1, arg2):
    return arg1 * arg2

the decorator module

写新的decorator的难题:
要写出新的decorator函数必须要写嵌套函数
decorator.py是一个python module,
致力于在python应用decorator更简单

解决方法

解决方法是提供一个通用的方法来构造decorator,以隐藏签名保持所要求的复杂性。 decorator模块就提供了这样一个类,名为 decorator 。 小写名称的意义是一般像函数一样使用它,而不是把它当作类。

定义

技术上讲,任何能以一个参数调用的python对象都可以作为decorator。 但是要做到有用的decorator往往是很大的, 为方便叙述将所有decorator分为两类:

  • signature-preserving decorators 即可调用对象以一个函数作为输入并返回相同特征的函数
  • signature-changing decorators 即改变了输入函数的牲,返回值的牲与输入不一致,或者根本不是一个可调用的对象

改变特征的decorator有它特殊的用途, classmethodstaticmethod 就是这个类型。

特征保留型decorator是可叠回的。相反classmethod或staticmethod是显然不可叠加的。

然而从头写一个特征保留型并不是那样显然的,特别是想为任意特征的函数写合适的decorator时。

陈述问题

假设你想跟踪一个函数,这个decorator的很典型的用途:

#<_main.py>

def decorator_trace(f):
    def newf(*args, **kw):
        print "calling %s with args %s, %s" % (f.__name__, args, kw)
        return f(*args, **kw)
    newf.__name__ = f.__name__
    newf.__dict__ = f.__dict__
    newf.__doc__ = f.__doc__
    newf.__module__ = f.__module__
    return newf

#</_main.py>

这个实现可以工作在任意特征的函数上,但不幸的是,这个decorator并不是特征保留的,

考虑这个实例:

>>> @decorator_trace
... def f1(x):
...     pass

这里原始函数只能接受一个参数x但decorator返回的函数能接受任意多个参数:

>>> from inspect import getargspec
>>> print getargspec(f1)
([], 'args', 'kw', None)

探测工具报告说该函数可以接受 *args**kw ,但当你以多于一个参数调用时就会出错:

>>> f1(0, 1)
Traceback (most recent call last):
   ...
TypeError: f1() takes exactly 1 argument (2 given)

decorator 工厂的另一个重要的优点在于你可以使用不必使用嵌套函数就能解决问题。 如下面的定义 decorator_trace

>>> from decorator import decorator

首先定义一个helper function,使用的签名是 (f, *args, **kw)

#<_main.py>

def trace(f, *args, **kw):
    print "calling %s with args %s, %s" % (f.func_name, args, kw)
    return f(*args, **kw)

#</_main.py>

decorator 有能力将它转化为签名保持型的decorator

>>> @decorator(trace)
... def f1(x):
...     pass

调用一下来检查其工作正确:

>>> f1(0)
calling f1 with args (0,), {}

再检查签名:

>>> print getargspec(f1)
(['x'], None, None, None)

它也同样能工作在任意签名的函数上:

>>> @decorator(trace)
... def f(x, y=1, z=2, *args, **kw):
...     pass
>>> f(0, 3)
calling f with args (0, 3, 2), {}
>>> print getargspec(f)
(['x', 'y', 'z'], 'args', 'kw', (1, 2))

即使函数包含了外来签名也照样能工作:

>>> @decorator(trace)
... def exotic_signature((x, y)=(1,2)): return x+y
>>> print getargspec(exotic_signature)
([['x', 'y']], None, None, ((1, 2),))
>>> exotic_signature()
calling exotic_signature with args ((1, 2),), {}
3

decorator 也是一个decorator

它也是一个会签名改变型的decorator

示例: memoize

这是一个实现了记忆特征的decorator,也就是它将结果缓存在了一个内部dict中,当下次以同样的参数调用时它直接返回而不用计算。

#<_main.py>

from decorator import decorator

def getattr_(obj, name, default_thunk):
    "Similar to .setdefault in dictionaries."
    try:
        return getattr(obj, name)
    except AttributeError:
        default = default_thunk()
        setattr(obj, name, default)
        return default

@decorator
def memoize(func, *args):
    dic = getattr_(func, "memoize_dic", dict)
    # memoize_dic is created at the first call
    if args in dic:
        return dic[args]
    else:
        result = func(*args)
        dic[args] = result
        return result

#</_main.py>

这是它的测试

>>> @memoize
... def heavy_computation():
...     time.sleep(2)
...     return "done"
>>> print heavy_computation() # the first time it will take 2 seconds
done
>>> print heavy_computation() # the second time it will be instantaneous
done

locked

在多线程的程序中,decorator有很多用途 [1]

[1]In Python 2.5, 最好的使用锁定语义是使用with语句 the with statement: http://docs.python.org/dev/lib/with-locks.html

delayed and threaded

def delayed(nsec):
    def call(proc, *args, **kw):
        thread = threading.Timer(nsec, proc, args, kw)
        thread.start()
        return thread
    return decorator(call)

测试

>>> @delayed(2)
... def start_browser():
...     "code to open an external browser window here"
>>> #start_browser() # will open the browser in 2 seconds
>>> #server.serve_forever() # enter the server mainloop

threaded = delayed(0) # no-delay decorator

>>> @threaded
... def writedata(data):
...     write(data)

blocking

资源阻塞

redirecting_stdout

重定向输出

References:

[2]Python PEP 0318: http://www.python.org/dev/peps/pep-0318/
[3]本文decorator module引用自: http://www.python.org/pypi/decorator/1.1
[4]http://www.phyast.pitt.edu/~micheles/python/documentation.html
[5]http://www.phyast.pitt.edu/~micheles/python/decorator.zip
[6]http://wiki.python.org/moin/PythonDecoratorLibrary