曾灵敏 — APRIL 27, 2015html
你们都知道装饰器是一个很著名的设计模式,常常被用于AOP(面向切面编程)的场景,较为经典的有插入日志,性能测试,事务处理,Web权限校验
, Cache
等。python
Python语言自己提供了装饰器语法(@),典型的装饰器实现以下:git
@function_wrapper def function(): pass
@其实是python2.4才提出的语法糖,针对python2.4之前的版本有另外一种等价的实现:github
def function(): pass function = function_wrapper(function)
函数包装器 - 经典实现编程
def function_wrapper(wrapped): def _wrapper(*args, **kwargs): return wrapped(*args, **kwargs) return _wrapper @function_wrapper def function(): pass
类包装器 - 易于理解设计模式
class function_wrapper(object): def __init__(self, wrapped): self.wrapped = wrapped def __call__(self, *args, **kwargs): return self.wrapped(*args, **kwargs) @function_wrapper def function(): pass
当咱们谈到一个函数时,一般但愿这个函数的属性像其文档上描述的那样,是被明肯定义的,例如__name__
和__doc__
。app
针对某个函数应用装饰器时,这个函数的属性就会发生变化,但这并非咱们所指望的。函数
def function_wrapper(wrapped): def _wrapper(*args, **kwargs): return wrapped(*args, **kwargs) return _wrapper @function_wrapper def function(): pass >>> print(function.__name__) _wrapper
python标准库提供了functools.wraps()
,来解决这个问题。性能
import functools def function_wrapper(wrapped): @functools.wraps(wrapped) def _wrapper(*args, **kwargs): return wrapped(*args, **kwargs) return _wrapper @function_wrapper def function(): pass >>> print(function.__name__) function
然而,当咱们想要获取被包装函数的参数(argument
)或源代码(source code
)时,一样不能获得咱们想要的结果。测试
import inspect def function_wrapper(wrapped): ... @function_wrapper def function(arg1, arg2): pass >>> print(inspect.getargspec(function)) ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None) >>> print(inspect.getsource(function)) @functools.wraps(wrapped) def _wrapper(*args, **kwargs): return wrapped(*args, **kwargs)
当包装器(@function_wrapper
)被应用于@classmethod
时,将会抛出以下异常:
class Class(object): @function_wrapper @classmethod def cmethod(cls): pass Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in Class File "<stdin>", line 2, in wrapper File ".../functools.py", line 33, in update_wrapper setattr(wrapper, attr, getattr(wrapped, attr)) AttributeError: 'classmethod' object has no attribute '__module__'
由于@classmethod
在实现时,缺乏functools.update_wrapper
须要的某些属性。这是functools.update_wrapper
在python2中的bug,3.2版本已被修复,参考http://bugs.python.org/issue3445。
然而,在python3下执行,另外一个问题出现了:
class Class(object): @function_wrapper @classmethod def cmethod(cls): pass >>> Class.cmethod() Traceback (most recent call last): File "classmethod.py", line 15, in <module> Class.cmethod() File "classmethod.py", line 6, in _wrapper return wrapped(*args, **kwargs) TypeError: 'classmethod' object is not callable
这是由于包装器认定被包装的函数(@classmethod
)是能够直接被调用的,但事实并不必定是这样的。被包装的函数实际上多是描述符(descriptor
),意味着为了使其可调用,该函数(描述符)必须被正确地绑定到某个实例上。关于描述符的定义,能够参考https://docs.python.org/2/howto/descriptor.html。
尽管你们实现装饰器所用的方法一般都很简单,但这并不意味着它们必定是正确的而且始终能正常工做。
如同上面咱们所看到的,functools.wraps()
能够帮咱们解决__name__
和__doc__
的问题,但对于获取函数的参数(argument)或源代码( source code
)则一筹莫展。
以上问题,wrapt均可以帮忙解决,详细用法可参考其官方文档:http://wrapt.readthedocs.org