python 装饰器

函数装饰器用于在源码中标记函数, 并改变或增强函数的行为.
这是一个很强大的功能, 在理解闭包后, 我们可以看一下装饰器了.

首先看一个例子.

1
2
3
4
5
6
7
8
def deco(func):
def inner():
print('running inner')
return inner

@deco
def target():
print('running target')

此时的 target 使用 deco 函数装饰之后相当于执行了 target = deco(target),
而 deco 返回的函数替换了原来的 target.

不过一般使用装饰器时一般是用于增强而不是替换函数.

需要注意的是, 在被装饰的函数定义后装饰器立即执行, 所以运行的时候就已经被处理过了.

接下来我们实现一个计时装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time

def clock(func):
def clocked(*args):
t0 = time.time()
result = func(*args)
cost = time.time() - t0
print('{}({}): time cost {}'.format(func.__name__, ', '.join(repr(arg) for arg in args), cost))
return result
return clocked

@clock
def snooze(second):
time.sleep(second)

运行:

1
2
3
4
>>> snooze(2)
snooze(2): time cost 2.001378297805786
>>> snooze.__name__
'clocked'

可以发现正常运行,
但问题是, 这样被装饰的函数本身的名称等属性也已经发生了变化,
我们可以使用 functools.wraps 来处理这个问题:

1
2
3
4
5
6
import functools

def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
#...

运行:

1
2
3
4
>>> snooze(2)
snooze(2): time cost 2.0035228729248047
>>> snooze.__name__
'snooze'

最后, 如果需要为装饰器本身指定参数, 则需要在其上层再次嵌套并传入参数.

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
import time
import functools

def top(hint=False):
def clock(func):
@functools.wraps(func)
def clocked(*args):
t0 = time.time()
result = func(*args)
cost = time.time() - t0
if hint:
print('[hint:]')
print('{}({}): time cost {}'.format(func.__name__, ', '.join(repr(arg) for arg in args), cost))
# 返回运行结果
return result
# 返回更改后的函数
return clocked
# 返回装饰器
return clock

@top()
def snooze1(second):
print('snooze1 {} second(s)'.format(second))
time.sleep(second)

@top(hint=True)
def snooze2(second):
print('snooze {} second(s)'.format(second))
time.sleep(second)

此处为方便理解才如此命名函数, 实际使用时一般会将 top, clock, clocked 分别命名为 clock, decorate, clocked,
三处返回应加深理解.

运行结果:

1
2
3
4
5
6
7
>>> snooze1(2)
snooze1 2 second(s)
snooze1(2): time cost 2.0000576972961426
>>> snooze2(1)
snooze 1 second(s)
[hint:]
snooze2(1): time cost 1.00199294090271

ref: fluent python chepter 7