装饰器

绝大多数装饰器都是基于函数和 闭包 实现的,但这并非制造装饰器的唯一方式.

Python 对某个对象是否能通过装饰器(@decorator)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象

1
2
3
def foo(): pass
type(foo) # function
callable(foo) # True

只要自定义类的 __call__ 魔法方法即可让任意类变成可被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from dataclasses import dataclass

@dataclass()
class X:
a:int
b:int
range:int

def __call__(self): # 定义类的 `__call__` 方法
print('__call__ with ({}, {})'.format(self.a, self.b))

def __del__(self):
del self.a
del self.b
del self.range

x = X(1,2,3)
x() # 像函数一样调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import time
import random
import functools

def timer(wrapped):
"""装饰器:记录并打印函数耗时"""

@functools.wraps(wrapped) # 可以使函数保持原有签名
def decorated(*args, **kwargs):
st = time.time()
ret = wrapped(*args, **kwargs)
print('execution take: {} seconds'.format(time.time() - st))
return ret
return decorated

def counter(func):
"""装饰器:记录并打印调用次数"""
count = 0
@functools.wraps(func)
def decorated(*args, **kwargs):
# 次数累加
nonlocal count
count += 1
print(f"Count: {count}")
return func(*args, **kwargs)
return decorated

@counter
@timer
def random_sleep():
'德玛西亚'
time.sleep(random.random())


random_sleep()
random_sleep()
print(random_sleep.__name__)
print(random_sleep.__doc__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


def provide_number(min_num, max_num):
"""装饰器:随机生成一个在 [min_num, max_num] 范围的整数,追加为函数的第一个位置参数
"""

def wrapper(func):
def decorated(*args, **kwargs):
num = random.randint(min_num, max_num)
# 将 num 作为第一个参数追加后调用函数
return func(num, *args, **kwargs)

return decorated

return wrapper


@provide_number(1, 100)
def print_random_number(num):
print(num)

wrapt 模块是一个专门帮助编写装饰器的工具库。可以非常方便的改造 provide_number 装饰器,完美解决“嵌套层级深”和“无法通用”两个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import wrapt
import random


def provide_number(min_num, max_num):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
# 参数含义:
#
# - wrapped:被装饰的函数或类方法
# - instance:
# - 如果被装饰者为普通类方法,该值为类实例
# - 如果被装饰者为 classmethod 类方法,该值为类
# - 如果被装饰者为类/函数/静态方法,该值为 None
#
# - args:调用时的位置参数(注意没有 * 符号)
# - kwargs:调用时的关键字参数(注意没有 ** 符号)
#
num = random.randint(min_num, max_num)
# 无需关注 wrapped 是类方法或普通函数,直接在头部追加参数
args = (num,) + args
return wrapped(*args, **kwargs)

return wrapper

@provide_number(1,100)
def number(num):
print(num)

number()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import time
import functools


class DelayFunc:
def __init__(self, duration, func):
self.duration = duration
self.func = func

def __call__(self, *args, **kwargs):
print(f'Wait for {self.duration} seconds...')
time.sleep(self.duration)
return self.func(*args, **kwargs)

def eager_call(self, *args, **kwargs):
print('Call without delay')
return self.func(*args, **kwargs)


def delay(duration):
"""装饰器:推迟某个函数的执行。同时提供 .eager_call 方法立即执行
"""
# 此处为了避免定义额外函数,直接使用 functools.partial 帮助构造
# DelayFunc 实例
return functools.partial(DelayFunc, duration)

@delay(duration=2)
def add(a, b):
return a + b


# 这次调用将会延迟 2 秒
print(add(1, 2))
# 这次调用将会立即执行
print(add.eager_call(1, 2)) # 还是进入了类,duration=2, 但调用类里面的eager_call()方法直接返回函数
1
2
3
4
5
6
def add(a, b):
return a + b

import functools
add_3 = functools.partial(add, 3)
print(add_3(1))