文章目录
- 1. 装饰器在导入的时候就会执行
- 2. functools.wraps 装饰器,保持 被装饰的函数的 `__name__` 的值不变
- 3. functools.lru_cache 实现备忘录
- 4. functools.singledispatch 处理多个不同的输入类型
- 5. 堆叠装饰器
- 6. 参数化装饰器
learn from 《流畅的python》
代码语言:javascript复制def deco(func):
def inner():
print("running inner()")
return inner # deco 返回 inner 函数对象
@deco
def target():
print('running target()')
target() # running inner() 调用被装饰的 target,运行的是 inner
print(target) # <function deco.<locals>.inner at 0x000001B66CBBD3A0>
# target 是 inner 的引用
1. 装饰器在导入的时候就会执行
代码语言:javascript复制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 0x000002770981D550>) # 装饰器导入时立即运行
# running register(<function f2 at 0x000002770981D430>) # 装饰器导入时立即运行
# running main()
# registry -> [<function f1 at 0x000002770981D550>, <function f2 at 0x000002770981D430>]
# running f1()
# running f2()
# running f3()
# 可以看见 f1(), f2() 还没有运行,因为被装饰了,已经被加到 list 内了
- 简单例子
# 装饰器示例
# clockdeco.py 输出函数运行时间
import time
def clock(func):
def clocked(*args): # 内部函数 clocked
t0 = time.perf_counter()
res = func(*args) # clocked的闭包中包含自由变量 func
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ','.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, res))
# 打印 时间, 函数名, 参数名, 函数结果
return res
return clocked # 返回内部函数,取代被装饰的函数
代码语言:javascript复制import time
from clockdeco import clock
@clock
def testFunc(seconds):
time.sleep(seconds)
@clock
def fact(n):
return 1 if n < 2 else n * fact(n - 1)
testFunc(.123)
fact(6)
输出:
代码语言:javascript复制[0.13175840s] testFunc(0.123) -> None
[0.00000070s] fact(1) -> 1
[0.00002130s] fact(2) -> 2
[0.00004540s] fact(3) -> 6
[0.00005840s] fact(4) -> 24
[0.00007230s] fact(5) -> 120
[0.00009220s] fact(6) -> 720
代码语言:javascript复制装饰器的 典型 行为: 把 被装饰的函数 替换成 新函数,二者接受相同 的参数,而且(通常)返回被装饰的函数 本该返回的值,同时还会做些 额外操作。
print(fact.__name__) # clocked , 上面例子的装饰器有缺点
# 不支持关键参数
# 被装饰函数的 __name__, __doc__ 属性被遮盖
2. functools.wraps 装饰器,保持 被装饰的函数的 __name__
的值不变
- 加入
functools.wraps
装饰器,保持 被装饰的函数的__name__
的值不变
# 装饰器示例
# clockdeco2.py 输出函数运行时间
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs): # 内部函数 clocked
t0 = time.perf_counter()
res = func(*args, **kwargs) # clocked的闭包中包含自由变量 func
elapsed = time.perf_counter() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, res))
# 打印 时间, 函数名, 参数名, 函数结果
return res
return clocked # 返回内部函数,取代被装饰的函数
代码语言:javascript复制from clockdeco2 import clock
@clock
def fact(n):
return 1 if n < 2 else n * fact(n - 1)
fact(6)
print(fact.__name__) # fact
# @functools.wraps 起到了作用, 被装饰的 fact 函数 名没有被遮盖
3. functools.lru_cache 实现备忘录
实现一个斐波那契数计算
代码语言:javascript复制@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) fibonacci(n-1)
print(fibonacci(6))
输出:
代码语言:javascript复制[0.00000030s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00001400s] fibonacci(2) -> 1
[0.00000030s] fibonacci(1) -> 1
[0.00000030s] fibonacci(0) -> 0
[0.00000020s] fibonacci(1) -> 1
[0.00001310s] fibonacci(2) -> 1
[0.00002570s] fibonacci(3) -> 2
[0.00005320s] fibonacci(4) -> 3
[0.00000030s] fibonacci(1) -> 1
[0.00000030s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001260s] fibonacci(2) -> 1
[0.00002550s] fibonacci(3) -> 2
[0.00000020s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001300s] fibonacci(2) -> 1
[0.00000020s] fibonacci(1) -> 1
[0.00000080s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001320s] fibonacci(2) -> 1
[0.00002560s] fibonacci(3) -> 2
[0.00005080s] fibonacci(4) -> 3
[0.00008840s] fibonacci(5) -> 5
[0.00015550s] fibonacci(6) -> 8
8
可以发现有很多重复计算
代码语言:javascript复制@functools.lru_cache() # () 可接收参数
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) fibonacci(n-1)
print(fibonacci(6))
输出:(无重复计算,时间也快了很多)
代码语言:javascript复制[0.00000030s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001600s] fibonacci(2) -> 1
[0.00000060s] fibonacci(3) -> 2
[0.00003130s] fibonacci(4) -> 3
[0.00000050s] fibonacci(5) -> 5
[0.00004570s] fibonacci(6) -> 8
8
functools.lru_cache(maxsize=128, typed=False)
,maxsize
指定存储多少个调用结果,满了后,删除旧的,推荐设置为2的幂typed
设为True
把不同的参数类型分开保存(区分 1, 1.0)lru_cache
使用的是字典存储,key 是 传入的定位参数和关键字参数,所以被装饰函数的所有参数必须是可散列的
4. functools.singledispatch 处理多个不同的输入类型
代码语言:javascript复制import numbers
@functools.singledispatch
def testFunc(val):
return "{}".format(val)
@testFunc.register(str) # 处理字符串输入
def _(val):
return val " add str"
@testFunc.register(numbers.Integral) # 处理int输出
def _(val):
return str(val 100)
@testFunc.register(tuple) # 处理 tuple 输入
def _(val):
return ",".join(testFunc(v) for v in val)
print(testFunc(5))
print(testFunc("hello"))
print(testFunc((5, "hello", (6, 7))))
# 输出
# 105
# hello add str
# 105,hello add str,106,107
5. 堆叠装饰器
代码语言:javascript复制@d1
@d2
def f():
print('f')
等价于
代码语言:javascript复制def f():
print('f')
f = d1(d2(f))
6. 参数化装饰器
- 例子1:接受参数,是否注册函数
print("running start ")
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(registry)
f1()
f2()
f3()
输出:
代码语言:javascript复制running start
running register(active=False)->decorate(<function f1 at 0x0000014464380A60>)
running register(active=True)->decorate(<function f2 at 0x0000014461A6D3A0>)
{<function f2 at 0x0000014461A6D3A0>}
running f1()
running f2()
running f3()
- 例子2,接受字符串格式,打印不同的输出
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())) # **locals() 在 fmt 中引用 clocked 的局部变量
return _result # clocked 会取代被装饰的函数,它应该返回被装饰的函数返回的值
return clocked # decorate 返回 clocked
return decorate # clock 返回 decorate
@clock() # 不传参数,使用默认的
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
# [0.13069201s] snooze(0.123) -> None
# [0.13592529s] snooze(0.123) -> None
# [0.13488460s] snooze(0.123) -> None
@clock('{name}: {elapsed}s')
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
# snooze: 0.134782075881958s
# snooze: 0.1345205307006836s
# snooze: 0.13508963584899902s
@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
# snooze(0.123) dt = 0.134s
# snooze(0.123) dt = 0.135s
# snooze(0.123) dt = 0.135s