Decorator
有种设计模式叫修饰器模式, 它可以在不修改目标函数代码的前提下, 在目标函数执行前后增加一些额外功能, 例如函数计时.
模块decorator_demo.py
:
import time
def time_it(fn):
print ('time_it is executed')
def new_fn(*args):
start = time.time()
result = fn(*args)
end = time.time()
duration = end - start
print('%s seconds are consumed in executing function:%s%r'\
%(duration, fn.__name__, args))
return result
return new_fn
@time_it
def acc1(start, end):
s = 0
for i in xrange(start, end):
s += i
return s
def acc2(start, end):
s = 0
for i in xrange(start, end):
s += i
return s
print acc1
print acc2
if __name__ == '__main__':
acc1(10, 1000000)
可以看出, 修饰器是一个函数, 它需要返回一个新的function(示例中的new_fn
). 函数通常在被修饰函数(示例中的acc1
)执行前后进行一些额外的操作, 例如计时. 这个新的函数一般不会修改被修饰函数的返回结果.
执行脚本:
python decorator_demo.py
可以看到函数执行时间(在最后一行):
time_it is executed
<function new_fn at 0x7fde83bfd9b0>
<function acc2 at 0x7fde83bfda28>
0.0198199748993 seconds are consumed in executing function:acc1(10, 1000000)
执行时机
在模块加载时就会执行修饰器函数, 生成一个个被修饰的新的函数. 这两点可简单验证.
执行:
python
>import decorator_demo
或
python -c 'import decorator_demo'
可以看到输出:
time_it is executed
<function new_fn at 0x7f5dbb17a9b0>
<function acc2 at 0x7f5dbb17aa28>
第一行的输出可以证明修饰器在import
模块时执行.
第二和第三行的输出可以看出被修饰后的函数已经不是原来的函数了.实际上, acc1=time_it(acc1)
. 这样带来的坏处时屏蔽了原函数的元信息, 例如__name__, __doc__
. 如果要保留原函数的信息, 可使用标准库中的修饰器functools.wrap
:
def time_it(fn):
print ('time_it is executed')
@functools.wraps(fn) #########################
def new_fn(*args, **kws):
start = time.time()
result = fn(*args, **kws)
end = time.time()
duration = end - start
print('%s seconds are consumed in executing function:%s%r'\
%(duration, fn.__name__, args))
return result
return new_fn
再执行:
python -c 'import decorator_demo'
输出为:
time_it is executed
<function acc1 at 0x7ff9e3f07b90>
<function acc2 at 0x7ff9e3f07c08>
可以看出, 被修饰的acc1
像acc2
一样保留了它原来的名字.
带参数的修饰器
不说别的, 上面代码中的functools.wraps
就是一个带参数的修饰器.
下面, 我们也来实现一个带参数修饰器: 修改time_it
, 通过传入参数keep_meta
来选择是否保留原函数的元信息.
import time
import functools
def time_it(keep_meta = False):
def real_dec(fn):
if keep_meta:
@functools.wraps(fn)
def new_fn(*args):
start = time.time()
result = fn(*args)
end = time.time()
duration = end - start
print('%s seconds are consumed in executing function:%s%r'\
%(duration, fn.__name__, args))
return result
else:
def new_fn(*args):
start = time.time()
result = fn(*args)
end = time.time()
duration = end - start
print('%s seconds are consumed in executing function:%s%r'\
%(duration, fn.__name__, args))
return result
return new_fn
return real_dec
@time_it(keep_meta = False)
def acc1(start, end):
s = 0
for i in xrange(start, end):
s += i
return s
@time_it(keep_meta = True)
def acc2(start, end):
s = 0
for i in xrange(start, end):
s += i
return s
print acc1
print acc2
if __name__ == '__main__':
acc1(10, 1000000)```
其实就是在外面又套了一层函数. 然后在修饰函数时, 一定要传入参数:
@time_it(keep_meta = False)
def target_fn
其效果相当于:
target_fn = time_it(keep_meta = False)(target_fn)
执行代码得到输出:
<function new_fn at 0x7fe37cc63c08>
<function acc2 at 0x7fe37cc63cf8>
0.0195889472961 seconds are consumed in executing function:acc1(10, 1000000)
可以看到, 因为keep_meta
参数的原因, acc2
的元信息被保留而acc1
没有.