编写自定义装饰器
使用包装函数和 functools.wraps 创建装饰器。
编写自定义装饰器 是 CoddyKit 上的免费 Python Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。
简介
装饰器是用于包装其他函数的函数,可以在不修改原函数的情况下,在其执行前后添加行为。
什么是装饰器
函数上方的 @decorator 是一种语法糖,等价于:func = decorator(func)。装饰器接收函数并返回一个包装器。
def shout(func):
def wrapper(*a, **kw):
result = func(*a, **kw)
return str(result).upper()
return wrapper
@shout
def greet(name): return f'hello {name}'
print(greet('Alice'))包装器模式
包装函数与原函数具有相同的签名。它会在内部调用原函数,并在调用前后添加行为。
def timer(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f'{func.__name__} took {time.time()-start:.4f}s')
return result
return wrapper
@timer
def slow(): import time; time.sleep(0.01)
slow()functools.wraps
在包装函数上使用 @wraps(func) 可以保留原函数的 __name__、__doc__ 和 __annotations__。
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*a, **kw):
print(f'calling {func.__name__}')
return func(*a, **kw)
return wrapper
@log
def add(a, b): return a + b
print(add.__name__)
add(1, 2)带参数的装饰器
带参数的装饰器需要多一层包装:使用一个接收参数并返回装饰器的工厂函数。
def repeat(n):
def decorator(func):
from functools import wraps
@wraps(func)
def wrapper(*a, **kw):
for _ in range(n): func(*a, **kw)
return wrapper
return decorator
@repeat(3)
def hi(): print('hi')
hi()叠加装饰器
多个装饰器会从下到上叠加:@d2 @d1 表示 func = d2(d1(func))。最外层的装饰器(顶部装饰器)最后运行。
def bold(f):
from functools import wraps
@wraps(f)
def w(*a,**k): return '<b>' + f(*a,**k) + '</b>'
return w
def italic(f):
from functools import wraps
@wraps(f)
def w(*a,**k): return '<i>' + f(*a,**k) + '</i>'
return w
@bold
@italic
def text(): return 'hello'
print(text())基于类的装饰器
实现了 __call__ 的类可以作为装饰器使用。它可以在多次调用之间保存状态。
class Counter:
def __init__(self, func): self.func,self.count=func,0
def __call__(self, *a, **kw):
self.count+=1
return self.func(*a, **kw)
@Counter
def greet(name): return f'hi {name}'
greet('Alice'); greet('Bob')
print(greet.count)记忆化装饰器
缓存之前的结果,避免重复计算:if args in cache: return cache[args]。functools.lru_cache 内置实现了这一功能。
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2: return n
return fib(n-1) + fib(n-2)
print(fib(30))授权装饰器
装饰器常用于访问控制:运行函数前检查用户是否已经登录。
def requires_auth(func):
from functools import wraps
@wraps(func)
def wrapper(user, *a, **kw):
if not user.get('authenticated'):
raise PermissionError('Not authenticated')
return func(user, *a, **kw)
return wrapper
@requires_auth
def dashboard(user): return 'Welcome!'
try: dashboard({'authenticated': False})
except PermissionError as e: print(e)重试装饰器
包装一个函数,使其在失败时重试:for _ in range(retries): try: return func() except Exception: pass
import random
from functools import wraps
def retry(times=3):
def dec(func):
@wraps(func)
def wrapper(*a, **kw):
for i in range(times):
try: return func(*a, **kw)
except Exception as e:
if i==times-1: raise
return wrapper
return dec
@retry(3)
def flaky():
if random.random()<0.7: raise ValueError
return 'ok'
try: print(flaky())
except ValueError: print('failed')装饰器最佳实践
始终使用 @functools.wraps。让每个装饰器只负责一个目的。记录它添加的功能。将装饰器与被装饰的函数分开测试。
from functools import wraps
def validate_positive(func):
@wraps(func)
def wrapper(x, *a, **kw):
if x <= 0: raise ValueError(f'{x} must be positive')
return func(x, *a, **kw)
return wrapper
@validate_positive
def sqrt(x): return x**0.5
print(sqrt(9))快速检查
将 @functools.wraps(func) 应用于包装器时,它会做什么?
回顾
装饰器:func = decorator(func)。使用 wrapper(*args, **kwargs)。@wraps 会保留元数据。参数化装饰器会增加一层包装。使用多个 @decorators 叠加装饰器。
继续学习
进步非常出色!请继续学习,掌握下一个概念。
常见问题解答
「编写自定义装饰器」课时是免费的吗?
是的 — 「编写自定义装饰器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。
「编写自定义装饰器」这节课中我会学到什么?
使用包装函数和 functools.wraps 创建装饰器。 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Python Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「编写自定义装饰器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Python Academy 课中编写并运行代码吗?
能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。