Python 装饰器

前言

不少文章讲装饰器,一上来就是嵌套函数的实现方式,容易让人产生误解,以为装饰器必须那样实现,其实不然。
相对来说,装饰器属于 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

  • 装饰器是一个返回另一个函数的函数。它只有一个位置参数,这个参数是一个可调用的对象
  • 装饰器是一颗语法糖
  • 无参数的装饰器用两层嵌套函数
  • 有参数的装饰器用三层嵌套函数
  • 同时使用多个装饰器,从近到远地装饰函数
  • functools.wraps 避免 __name__ 和 __doc__ 被替换
  • 类装饰器可以减少嵌套层数

你可能感兴趣的:(python,python)