===================== decorator in python ===================== -------------------- -------------------- 背景 ==== 从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有它特殊的用途, ``classmethod`` 和 ``staticmethod`` 就是这个类型。 特征保留型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 # 这个实现可以工作在任意特征的函数上,但不幸的是,这个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) # ``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 # 这是它的测试 >>> @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有很多用途 [#]_ .. [#] 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: =========== .. [#] Python PEP 0318: http://www.python.org/dev/peps/pep-0318/ .. [#] 本文decorator module引用自: http://www.python.org/pypi/decorator/1.1 .. [#] http://www.phyast.pitt.edu/~micheles/python/documentation.html .. [#] http://www.phyast.pitt.edu/~micheles/python/decorator.zip .. [#] http://wiki.python.org/moin/PythonDecoratorLibrary 声明接口 -------- .. _WhatsinPython24: http://www.python.org/doc/2.4.3/whatsnew/whatsnew24.html .. _DecoratorFromPython24: http://www.python.org/doc/2.4.3/whatsnew/node6.html .. _PEP318提案: http://www.python.org/dev/peps/pep-0318/ .. |decorator in python| image:: rst.png