函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为, 是一项强大的功能。本文记录相关内容。
@decorate
def target():
print('running target()')
--------------------------------------------
def target():
print('running target()')
target = decorate(target)
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
-->
running register(<function f1 at 0x0000021C4D7C68B0>)
running register(<function f2 at 0x0000021C4D7C6820>)
running main()
registry -> [<function f1 at 0x0000021C4D7C68B0>, <function f2 at 0x0000021C4D7C6820>]
running f1()
running f2()
running f3()
running register
是在 main
函数运行前执行的。函数装饰器在导入模块时立即执行,而被装饰的 函数只在明确调用时运行。这突出了 Python 程序员所说的导入时和运行时之间的区别。def f1(a):
print(a)
print(b)
f1(3)
b = 9
-->
Error: name 'b' is not defined
调用前未定义的全局变量会被认为
未定义
def f1(a):
print(a)
print(b)
b = 9
f1(3)
-->
3
9
调用前定义过的全局变量可以正常获取
def f2(a):
print(a)
print(b)
b = 6
b = 9
f2(3)
-->
Error: local variable 'b' referenced before assignment
函数
f2
比f1
多了一行对b
变量的赋值语句,使用相同的调用方式却会报错。 因为:Python 编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了。
def f3(a):
global b
print(a)
print(b)
b = 6
b = 9
f3(3)
print(b)
-->
3
9
6
程序可以正常运行,内部变量为全局变量,并在函数内成功修改全局变量绑定关系
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
avg = make_averager()
print(avg(5))
print(avg(7))
print(avg(9))
-->
5.0
6.0
7.0
avg
使用 make_averager
的返回值做了平均值记录的工作,但是 make_averager
的生命周期应该早就结束了才对make_averager
函数每次计算均值时都要重新计算序列中所有元素的和,效率不高,直接保存总和和元素个数的策略在算法复杂度上更优,参考以下示例:def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
avg = make_averager()
avg(3)
avg(5)
avg(7)
-->
local variable 'count' referenced before assignment
+=
操作暗含了赋值操作,因此 python 会将 count
和 total
认为是局部变量,因此不会按照我们设计函数的思路运行nonlocal
声明。它的作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。如果为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
avg = make_averager()
print(avg(3))
print(avg(5))
print(avg(7))
-->
3.0
4.0
5.0
d1
和 d2
两个装饰器按顺序应用到 f
函数上,作用相当于 f = d1(d2(f))
。
@d1
@d2
def f():
print('f')
def f():
print('f')
f = d1(d2(f))
解析源码中的装饰器时,Python 把被装饰的函数作为第一个参数传给装饰器函数。那怎么让装饰器接受其他参数呢?
创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
print('running main()')
print('registry ->', registry)
f1()
registry = set()
def register(active=True):
def decorate(func):
print('running register(active=%s)->decorate(%s)' % (active, func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
@register(active=False)
def f1():
print('running f1()')
@register()
def f2():
print('running f2()')
def f3():
print('running f3()')
print(register)
-->
running register(active=False)->decorate(<function f1 at 0x000001FB582C3940>)
running register(active=True)->decorate(<function f2 at 0x000001FB582C3AF0>)
<function register at 0x000001FB582C3A60>
参数为 True 的被注册,False 的没有
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT):
def decorate(func):
def clocked(*_args):
t0 = time.time()
_result = func(*_args)
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
print(fmt.format(**locals()))
return _result
return clocked
return decorate
if __name__ == '__main__':
@clock()
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
clock
装饰器中加入了 DEFAULT_FMT
参数