不少文章讲装饰器,一上来就是嵌套函数的实现方式,容易让人产生误解,以为装饰器必须那样实现,其实不然。
相对来说,装饰器属于 Python 较难理解的一部分了,想要完全理解装饰器,需要有一定的 Python 基础,像闭包等概念。
本文希望通过由简到繁的例子,帮助你消化 Python 的装饰器。
消化本文需要了解的概念:一等对象、闭包、工厂模式、callable
先附上装饰器的官方描述
A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().
def wrapper(func):
return func
wrapper 就是一个装饰器,没有比这更简单的了。现在使用 wrapper
@wrapper
def target():
print('running target()')
这样,就用 wrapper 装饰了 target。
当然,这个 wrapper 什么也没做,让人感受不到它的存在。但我们可以在 return 之前做些事
def wrapper(func):
print('do something before returning')
return func
这样,没有修改 target,就给它添加了新的功能。
接下来,我们来理解,为什么说装饰器只是一颗语法糖。
首先,我们假设没有这颗糖,该怎么实现:在不修改 target 的前提下,给它添加新的功能
def wrapper(func):
print('do something before returning')
return func
target = wrapper(target)
应该像上面那样,此时,再调用 target(),已经给它添加了新的功能,但调用方式没变。
现在,有了装饰器这颗语法糖,可以像下面这样写,这样就简洁了
@wrapper
def target():
print('running target()')
至此,你应该清楚了装饰器的原理。但上述装饰器的用处有限,只能在被装饰的函数之前做些事,如果我想在被装饰的函数之后也做些事,该怎么实现呢?
此时,上面的一层函数已经力不从心,需要用两层函数,如下:
def wrapper(func):
def inner(*args, **kwargs):
print('do something before running')
r = func(*args, **kwargs)
print('do something after running')
return r
return inner
前面说过,@wrapper 相当于 target = wrapper(target)
这里,target 作为参数传给 wrapper,wrapper 返回一个内嵌函数。
此时,target 已被替换成 inner,只是函数名还是 target,但调用 target 实际上已经变成调用 inner。
这样,利用闭包,就可以在被装饰的函数前后,做一些事情了。
这里,特别强调了装饰器是无参数的,而不是指装饰器函数
前面说过,装饰器函数只能有一个参数,像这里,wrapper 函数有一个参数 func
def wrapper(func):
def inner(*args, **kwargs):
print('do something before running')
r = func(*args, **kwargs)
print('do something after running')
return r
return inner
@wrapper
def target():
print('running target()')
target()
对于有参数的装饰器,上面的两层函数也力不从心了,需要用三层函数。这种属于工厂模式,定义一个返回装饰器的函数,如下:
def factory(yes=False):
def wrapper(func):
def inner(*args, **kwargs):
if yes:
print('do something')
print('do something before running')
r = func(*args, **kwargs)
print('do something after running')
return r
return inner
return wrapper
@factory(True)
def target():
print('running target()')
target()
@factory(True) 相当于 target = factory(True)(target)
factory 是一个工厂函数,用来接收参数。它不是装饰器,而是返回一个装饰器。
wrapper 装饰器本身其实没法接收参数,但是通过闭包,wrapper 可以访问 factory 的参数,就好像装饰器有参数一样。
注意:当 factory 使用默认参数的时候,需要使用 @factory(),而不是 @factory。因为 factory 不是装饰器,它只是一个函数,它的返回值才是装饰器。
def wrapper(func):
'''wrapper doc'''
def inner(*args, **kwargs):
'''inner doc'''
print('do something before running')
r = func(*args, **kwargs)
print('do something after running')
return r
return inner
@wrapper
def target():
'''target doc'''
print('running target()')
print(target.__name__)
print(target.__doc__)
程序输出
inner
inner doc
@wrapper 相当于 target = wrapper(target)
target 已被替换成 inner,属性 __name__ 和 __doc__ 也被替换了
functools.wraps 可以避免替换
import functools
def wrapper(func):
'''wrapper doc'''
@functools.wraps(func)
def inner(*args, **kwargs):
'''inner doc'''
print('do something before running')
r = func(*args, **kwargs)
print('do something after running')
return r
return inner
@wrapper
def target():
'''target doc'''
print('running target()')
print(target.__name__)
print(target.__doc__)
程序输出
target
target doc
def d1(f):
print('d1')
return f
def d2(f):
print('d2')
return f
@d1
@d2
def f():
print('f')
f()
程序输出
d2
d1
f
相当于 f = d1(d2(f)),从近到远地装饰函数
class wrapper:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('do something before running')
r = self.func(*args, **kwargs)
print('do something after running')
return r
@wrapper
def target():
print('running target()')
target()
相当于 target = wrapper(target)
wrapper(target) 创建一个实例对象,由于 wrapper 是个 callable 类,所以这个实例对象是可调用的
target 被替换为这个实例对象,此后调用 target 实际上是调用 __call__
class wrapper:
def __init__(self, yes=False):
self.yes = yes
def __call__(self, func):
def inner(*args, **kwargs):
if self.yes:
print('do something')
print('do something before running')
r = func(*args, **kwargs)
print('do something after running')
return r
return inner
@wrapper(yes=True)
def target():
print('running target()')
target()
相当于 target = wrapper(yes=True)(target)
wrapper(yes=True) 创建一个实例对象,然后调用实例对象,返回了内部函数 inner
target 被替换为 inner,此后调用 target 实际上是调用 inner
如前所述,装饰器可以是一个函数,也可以是一个类。同样的,装饰器可以装饰一个函数,也可以装饰一个类。装饰器函数和装饰器类有一个共同点,就是 callable