把函数当作一等对象来使用,从而编写健壮、可带参数且透明的装饰器。
在 Kodokon 中打开本课在 Python 中,函数就是一个和其他对象没什么两样的对象:你可以把它赋给变量、当作参数传递、把它返回。高阶函数接收或返回一个函数 - sorted(data, key=...) 就是其中之一。再加上闭包 - 内层函数捕获外层函数的变量,并在外层函数返回之后依然让它们存活 - 你就集齐了装饰器的全部要素。
def make_multiplier(factor):
def multiply(value):
return value * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10)) # 20 30装饰器是一个接收函数并返回另一个函数的函数 - 它通常返回一个 wrapper,在调用的前后添加行为。写在 def slow_sum 上方的 @timed 语法,严格来说只是 slow_sum = timed(slow_sum) 的语法糖。wrapper 接收 *args, kwargs,从而对被装饰函数的签名保持中立,并且要返回**被装饰函数的结果 - 忘掉这一步是个经典错误,它会把所有返回值都变成 None。
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
delay = time.perf_counter() - start
print(f"{func.__name__}: {delay:.4f}s")
return result
return wrapper
@timed
def slow_sum(n):
return sum(range(n))
print(slow_sum(1_000_000))要让装饰器带参数(@repeat(times=3)),你还需要再多一层:一个工厂函数,它接收这些参数并返回装饰器,装饰器再返回 wrapper。三层嵌套函数 - 这就是灵活性的代价。如果状态变得复杂,一个带 __call__ 方法的类往往更易读。请把装饰器留给那些横切关注点:日志、缓存、重试、访问控制。一个会改变业务逻辑的装饰器,会把最关键的东西藏起来,让读代码的人看不见。
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello {name}")
greet("Ada")