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?

posted @ 2017-07-26 14:57  天涯海角路  阅读(115)  评论(0)    收藏  举报