Python 装饰器入门
概要
要理解装饰器,首先要理解函数在 Python 是一等公民对象(first class objects),可以作为参数传递给另一个函数,也可以作为结果值返回。
而 Python 装饰器正是利用这一特性,其实现方式分为两类:函数装饰器和类装饰器
函数装饰器
函数装饰器是使用函数实现的装饰器,一个简单的例子如下:
>>> def deco(func): # 接受一个函数作为参数
... def wrapper(*args,**kwargs):
... print('Before calling the func')
... func()
... print('After calling the func')
... return wrapper # 返回一个函数作为结果值
...
>>> @deco
... def hello():
... print('Hello, World')
...
>>> hello()
Before calling the func
Hello, World
After calling the func
上述装饰器语法等价于:
>>> hello = coro(func)
>>> hello()
Before calling the func
Hello, World
After calling the func
事实上,我们用这个方法也是可以实现同样的目的,不过装饰器语法更清晰明了。
一个函数允许被多个函数装饰,如下面:
@decorator1
@decorator2
def foo:
pass
其等价于:
foo = decorator1(decorator2(foo))
装饰器执行顺序是从下往上,这样的定义类似于数学上,(g o f)(x) 等价于 g(f(x))。
类装饰器
类装饰器即使用类来实现装饰器,一个简单的例子如下:
>>> class deco(object):
... def __init__(self, func):
... self.func = func
... def __call__(self, *args):
... print('Called {func} with args: {args}'.\
... format(func=self.func.__name__,args=args))
... return self.func(*args)
...
>>> @deco
... def add(x,y):
... return x+y
...
>>> add(1,2)
Called add with args: (1, 2)
3
类装饰器其他用法类似于函数装饰器。
函数装饰器的缺陷
函数装饰器实际上会对被装饰的函数进行替换,见下面:
>>> def deco(func):
... def wrapper(*args,**kwargs):
... """The doc string for wrapper"""
... func(*args,**kwargs)
... return wrapper
...
>>> @deco
... def foo():
... """The doc string for foo"""
... pass
...
>>> foo.__code__
<code object wrapper at 0x7f6555ebf930, ...>
>>> foo.__name__
'wrapper'
>>> foo.__doc__
'The doc string for wrapper'
>>> import inspect
>>> inspect.signature(foo)
<Signature (*args, **kwargs)>
可以看到,经过 deco 装饰的 foo 函数实际上变成了 wrapper 函数。解决这个问题的一个办法是使用 functools.wraps,该函数可以使得被装饰的函数名字,docstring,参数列表保持不变。wraps 也是个装饰器,用法如下:
>>> from functools import wrapper
>>> def deco(func):
... @wrapper(func)
... def wrapper(*args,**kwargs):
... """The doc string for wrapper"""
... func(*args,**kwargs)
... return wrapper
...
>>> @deco
... def foo():
... """The doc string for foo"""
... pass
...
>>> foo.__name__
'foo'
>>> foo.__doc__
'The doc string for foo'
>>> import inspect
>>> inspect.signature(foo)
<Signature ()>
>>> foo.__code__
<code object wrapper at 0x7fdc904d8540, ...>
可以看到,wraps 恢复了 foo 的名字,docstring 和参数列表,但是 foo 仍是 code object wrapper。
wraps 签名如下:
functools.wraps(wrapped[, assigned][, updated])
wraps 只是对 update_wrapper() 做了简单封装,真正其作用的是update_wrapper。事实上,wraps 函数等价于 partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
update_wrapper 签名如下:
functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
wrapper 是装饰器返回的函数,wrapped 是被装饰的函数,assigned 和 updated 都是 tuple。assigned 指明 wrapper 函数中哪些属性需要被 wrapped 函数中的属性覆盖;updated 指明 wrapper 函数中哪些属性需要被 wrapped 函数中的属性更新。assigned 属性由常量 WRAPPER_ASSIGNMENTS 决定,默认值为 __name__, __module__,和 __doc__。updated 属性由常量 WRAPPER_UPDATES 决定,默认值为 __dict__。
参考: PEP 318 – Decorators for Functions and Methods PEP 3129 – Class Decorators What does functools.wraps do?

浙公网安备 33010602011771号