函数装饰器用于在源码中「标记」函数,以某种方式增强函数的行为。
闭包除了在装饰器中有用处外,还是回调式异步编程和函数式编程风格的基础。
装饰器只是语法糖。
装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。
装饰器在函数定义之后立即运行
函数装饰器在导入模块时立即执行,被装饰的函数只在明确调用时运行。
registry = []
def register(func):
print(f"注册函数:{func}")
registry.append(func)
return func
@register
def f1():
print("运行f1")
@register
def f2():
print("运行f2")
def f3():
print("运行f3")
if __name__ == '__main__':
print(registry)
f1()
f2()
f3()
print(registry)
"""
注册函数:<function f1 at 0x7f854f30cd40>
注册函数:<function f2 at 0x7f854f312ef0>
[<function f1 at 0x7f854f30cd40>, <function f2 at 0x7f854f312ef0>]
运行f1
运行f2
运行f3
[<function f1 at 0x7f854f30cd40>, <function f2 at 0x7f854f312ef0>]
"""
通过装饰器将内容进行预添加
# 注册装饰器.py
import pytest
all_func = []
def get_func(func):
all_func.append(func.__name__)
return func
@get_func
def test_f1():
print("f1")
@get_func
def test_f3():
print("f3")
@get_func
def test_f2():
print("f2")
if __name__ == '__main__':
print("执行顺序\n" + "\n".join(all_func))
pytest.main([
'-v', '注册装饰器.py'
])
记录pytest的执行顺序
执行顺序
test_f1
test_f3
test_f2
============================= test session starts ==============================
platform darwin -- Python 3.7.6, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /opt/anaconda3/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/Users/zhongxin/PycharmProjects/流畅的Python/第七章:函数装饰器和闭包/.hypothesis/examples')
rootdir: /Users/zhongxin/PycharmProjects/流畅的Python/第七章:函数装饰器和闭包
plugins: hypothesis-5.5.4, arraydiff-0.3, remotedata-0.3.2, openfiles-0.4.0, doctestplus-0.5.0, astropy-header-0.1.2
collecting ... collected 3 items
注册装饰器.py::test_f1 PASSED [ 33%]
注册装饰器.py::test_f3 PASSED [ 66%]
注册装饰器.py::test_f2 PASSED [100%]
============================== 3 passed in 0.04s ===============================
闭包指延伸了作用域的函数,其中包含了「函数定义体中引用」、「不在定义体中定义的非全局变量」
关键是它能访问定义体之外定义的非全局变量
自由变量:未在本地作用域中绑定的变量
闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数的时,虽然作用定义域不可用,但是仍能继续使用那些绑定。

未使用nonlocal
由于数字是不可变类型,所以count = count + 1会隐式创建局部变量count
正确的写法
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
计算运行时间的装饰器
import time
def clock(func):
def clocked(*args):
start = time.perf_counter()
result = func(*args)
end = time.perf_counter()
arg_str = ','.join(repr(arg) for arg in args)
print(f"[{end - start:0.8f} s] {func.__name__}({arg_str})")
return result
return clocked
@clock
def func(a):
return a + 1
if __name__ == '__main__':
func(5) # [0.00000112 s] func(5)
该装饰器的缺点:不支持关键字参数,遮盖了被装饰函数的__name__ 和__doc__属性
使用functools.wraps装饰器可以把相关属性复制过来
import functools
import time
def clock1(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
arg_lst = []
if args:
arg_lst.append(','.join(repr(arg) for arg in args))
if kwargs:
pairs = [f"{k}={v}" for k, v in sorted(kwargs.items())]
arg_lst.append(','.join(pairs))
arg_str = ','.join(arg_lst)
print(f"[{end - start:0.8f} s] {func.__name__}({arg_str})")
return result
return clocked
functools.lru_cache做备忘,可以把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。生成第n个斐波那契数这种慢速递归适合使用。从Web中获取信息的应用中也能发挥巨大作用。
PEP443-Single-dispatch generic functions:https://www.python.org/dev/peps/pep-0443/
functools.singledispatch单分派泛函数。把整体方案拆分成多个模块
被装饰的普通函数变为泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。类似于重载
可以在系统的任何地方和任何模块中注册专门函数。
如果后来在新的模块中定义了新的类型,可以轻松地添加一个新的专门函数来处理那个类型。
还可以为不是自己编写的或者不能修改的类添加自定义函数。
from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print("泛函数:", end=" ")
print(arg)
@fun.register(int)
def _(arg, verbose=False):
if verbose:
print("整型:", end="")
print(arg)
@fun.register(list)
def _(arg, verbose=False):
if verbose:
print("列表:")
for i, elem in enumerate(arg):
print(f'索引{i}:内容{elem}')
def nothing(arg, verbose=False):
print("啥也没有")
fun.register(type(None), nothing)
if __name__ == '__main__':
fun("你好") # 你好
fun("你好呀", True) # 泛函数: 你好呀
fun(1, True) # 整型:1
fun(['a', 'b', 'c'], True)
"""
列表:
索引0:内容a
索引1:内容b
索引2:内容c
"""
fun(None) # 啥也没有
def d1(f):
print('d1')
return f
def d2(f):
print('d2')
return f
@d1
@d2
def f():
print('f')
执行顺序相当于f = d1(d2(f))
参数化装饰器需要使用
()进行调用,非参数化装饰器则不需要()
下面是一个参数化输出格式的计算运行时间装饰器
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 snnoze(seconds):
time.sleep(seconds)
@clock('{name}:{elapsed}s')
def snnoze1(seconds):
time.sleep(seconds)
@clock('{name}({args}) dt={elapsed:0.3f}s')
def snnoze2(seconds):
time.sleep(seconds)
snnoze(0.123) # [0.12421203s] snnoze(0.123) -> None
snnoze1(0.123) # snnoze1:0.125046968460083s
snnoze2(0.123) # snnoze2(0.123) dt=0.127s
https://github.com/micheles/decorator/blob/master/docs/documentation.md
$ pip install decorator
import time
from decorator import decorator
@decorator
def warn_slow(func, style=None, *args, **kw):
t0 = time.time()
result = func(*args, **kw)
dt = time.time() - t0
if not style:
print(f'执行{func.__name__}总耗时:{dt}s')
else:
print(f"参数化执行{func.__name__}总耗时:{dt}s")
return result
@warn_slow
def f1(t):
time.sleep(t)
@warn_slow(style=1)
def f2(t):
time.sleep(t)
if __name__ == '__main__':
f1(0.123)
f2(0.123)