简介

Python 中的 装饰器(decorator)是一种在代码运行期间动态增加功能的方式。下面看一个实际例子,配合讲解理解一下装饰器的运行机制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 装饰器
def A(func):
    def C():
        return func()
    return C

# 方式 1. 直接使用装饰器
def B():
    pass
B = A(B)

# 方式 2. 利用 Python 的 @ 语法糖使用装饰器
@A
def B():
    pass

先看上面的方式 1。假设我们要使用装饰器函数 A 来装饰函数 B,那么函数 A 会接受函数 B 作为输入,然后返回一个新的函数 C,函数 C 其实是对函数 B 的一个封装,并且在函数 B 的执行前后增加了一些功能,最后我们用返回的函数 C 来直接替代函数 B。

而方式 2 其实与方式 1 等价,只不过是利用了 Python 自带的 @ 语法糖来实现。

常用示例

装饰不带参数的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def wrapper(func):
    def wrapped():
        print('<--------')
        result = func()
        print('-------->')
        return result
    return wrapped

@wrapper
def hello():
    print('Hello.')

hello()

Output:

1
2
3
<--------
Hello.
-------->

装饰带参数的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def wrapper(func):
    def wrapped(*args, **kwargs):
        print('<--------')
        result = func(*args, **kwargs)
        print('-------->')
        return result
    return wrapped

@wrapper
def hello(name):
    print(f'Hello {name}.')

hello('Tony')

Output:

1
2
3
<--------
Hello Tony.
-------->

带参数的装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def wrapper_factory(level):
    def wrapper(func):
        def wrapped():
            print(f'<--------level={level}')
            result = func()
            print('-------->')
            return result
        return wrapped
    return wrapper

@wrapper_factory(6)
def hello():
    print('Hello.')

hello()

Output:

1
2
3
<--------level=6
Hello.
-------->

高级技巧

functools.wraps

使用装饰器极大地复用了代码会失去原函数的元信息,包括 docstring__name__参数列表 等。此时可以使用 Python 内置的 functools.wraps 来解决,具体就是为装饰器函数的内部函数加上一个装饰器 wraps,示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def wrapper(func):
    @wraps
    def wrapped():
        print('<--------')
        result = func()
        print('-------->')
        return result
    return wrapped

@wrapper
def hello():
    print('Hello.')

hello()

嵌套装饰器

一个函数还可以同时使用多个装饰器,如下

1
2
3
4
5
@a
@b
@c
def hello():
    print('Hello.')

其实相当于 a(b(c(hello())))。此时的执行顺序为

  • 装饰器的初始化(即 wrapper() 之外的部分)顺序:cba
  • 装饰器的执行(即 wrapper() 以内的部分)顺序:abc

「初始化」其实类似「用多层礼物纸包装礼物」,初始化一层礼物纸包上去(c),再初始化一层礼物纸再包上去(b),以此类推。执行则相当于「拆开多层的礼物纸」,从外到内依次拆开(abc)。

类装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@Counter
def foo():
    print('i am foo')
# 相当于 Counter()(foo()),该 Counter 复用

for i in range(10):
    foo()

print(foo.count)  # 10

装饰类方法的装饰器

1
2
3
4
5
6
7
8
9
def host_operation(name):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            self.logger.info(f'<{name}> start')
            results = func(self, *args, **kwargs)
            self.logger.info(f'<{name}> done')
            return results
        return wrapper
    return decorator

参考