欢迎来到 Python 学习计划的第 50 天!🎉
昨天我们学习了 装饰器的原理与编写,掌握了如何增强函数和类的功能。今天,我们将在此基础上更进一步,学习 带参数的装饰器(Decorator with Arguments)。
在实际开发中,我们经常需要配置装饰器的行为(如重试次数、缓存时间、权限级别)。掌握带参数的装饰器,将让你的代码更加灵活和强大!
昨天的基础装饰器只能“开/关”功能,无法控制细节。
@retry # ❌ 重试几次?间隔多久?无法配置def fetch_data():pass
通过参数,我们可以定制装饰器的行为。
@retry(times=3, delay=1) # ✅ 重试 3 次,每次间隔 1 秒def fetch_data():pass
带参数的装饰器本质上是一个 装饰器工厂(Decorator Factory)。它返回一个真正的装饰器,再由这个装饰器去装饰函数。
带参数的装饰器需要 三层函数嵌套,这是初学者最容易混淆的地方。
外层函数 (接收装饰器参数)↓ 返回中层函数 (接收被装饰的函数)↓ 返回内层函数 (接收被装饰函数的参数)↓ 执行原函数
import functoolsdef decorator_with_args(arg1, arg2):# 第一层:接收装饰器参数def real_decorator(func):# 第二层:接收被装饰的函数@functools.wraps(func)def wrapper(*args, **kwargs):# 第三层:接收函数调用参数# 使用 arg1, arg2 控制逻辑print(f"装饰器参数:{arg1}, {arg2}")return func(*args, **kwargs)return wrapperreturn real_decorator@decorator_with_args("A", "B")def my_func():pass
💡 记忆口诀:“外参、中函、内调”。
让函数执行指定次数。
import functoolsdef repeat(times):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):for _ in range(times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(times=3)def say_hello():print("Hello")say_hello()# 输出:# Hello# Hello# Hello
网络请求失败时自动重试。
import timeimport functoolsdef retry(max_attempts=3, delay=1):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(max_attempts):try:return func(*args, **kwargs)except Exception as e:if i == max_attempts - 1:raise eprint(f"失败,{delay}秒后重试... ({i+1}/{max_attempts})")time.sleep(delay)return wrapperreturn decorator@retry(max_attempts=3, delay=1)def connect_db():import randomif random.random() < 0.7:raise ConnectionError("连接失败")return "连接成功"# 运行try:print(connect_db())except Exception as e:print(f"最终失败:{e}")
结合 类属性 vs 实例属性的区别 中的属性知识,我们可以控制缓存的大小或过期时间。
import functoolsdef cache_with_size(max_size=128):def decorator(func):cache = {}@functools.wraps(func)def wrapper(*args):if args in cache:return cache[args]if len(cache) >= max_size:cache.clear() # 简单策略:满了就清空result = func(*args)cache[args] = resultreturn resultreturn wrapperreturn decorator@cache_with_size(max_size=2)def calculate(x):print(f"计算 {x}")return x * xprint(calculate(1)) # 计算 1print(calculate(2)) # 计算 2print(calculate(1)) # 命中缓存print(calculate(3)) # 计算 3 (可能清空缓存)
结合 实例方法与 self 的本质和 第 44 天 __call__ 的知识,类也可以实现带参数的装饰器。
__init__:接收装饰器参数。__call__:接收被装饰的函数,并返回 wrapper。import functoolsclass Retry:def __init__(self, max_attempts=3):self.max_attempts = max_attemptsdef __call__(self, func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(self.max_attempts):try:return func(*args, **kwargs)except Exception as e:if i == self.max_attempts - 1:raiseprint(f"重试 {i+1}/{self.max_attempts}")return wrapper@Retry(max_attempts=3)def unstable_func():import randomif random.random() < 0.5:raise Exception("失败")return "成功"💡 对比:
__init__ 和 __call__,状态管理更方便(适合复杂逻辑)。特性 | 不带参数装饰器 | 带参数装饰器 |
|---|---|---|
语法 |
|
|
嵌套层数 | 两层(装饰器 -> wrapper) | 三层(工厂 -> 装饰器 -> wrapper) |
调用过程 |
|
|
灵活性 | 低(逻辑固定) | 高(可配置) |
复杂度 | 简单 | 较复杂,易出错 |
有时我们希望装饰器既可以 @decorator 也可以 @decorator(arg=1)。这需要判断第一个参数是否是函数。
def flexible_decorator(func=None, *, prefix="Log"):def real_decorator(f):@functools.wraps(f)def wrapper(*args, **kwargs):print(f"{prefix}: Calling {f.__name__}")return f(*args, **kwargs)return wrapperif func is not None:return real_decorator(func)return real_decorator@flexible_decoratordef func1(): pass@flexible_decorator(prefix="Debug")def func2(): pass
外层函数必须返回中层装饰器函数。
# ❌ 错误def wrong_decorator(times):def wrapper(*args): # 直接返回了 wrapper,缺少中间层passreturn wrapper# ✅ 正确def right_decorator(times):def decorator(func): # 中间层def wrapper(*args):passreturn wrapperreturn decorator
functools.wraps 位置@functools.wraps 应该放在 最内层 wrapper 上,修饰的是原函数。
def my_decorator(arg):def real_decorator(func):@functools.wraps(func) # ✅ 在这里def wrapper(*args, **kwargs):passreturn wrapperreturn real_decorator
确保内层函数能访问外层参数(闭包特性)。
def decorator(prefix):def real_decorator(func):def wrapper(*args):print(prefix) # ✅ 可以访问外层的 prefixreturn func(*args)return wrapperreturn real_decorator
创建一个 @timeout(seconds) 装饰器,如果函数执行时间超过指定秒数,抛出 TimeoutError。
import timeimport functoolsdef timeout(seconds):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()if end - start > seconds:raise TimeoutError(f"函数执行超时 {seconds} 秒")return resultreturn wrapperreturn decorator@timeout(seconds=1)def slow_task():time.sleep(1.5)try:slow_task()except TimeoutError as e:print(e) # 函数执行超时 1 秒
创建一个 @require_level(min_level) 装饰器,只有当用户的 level 大于等于指定值时才能执行函数。
import functoolsdef require_level(min_level):def decorator(func):@functools.wraps(func)def wrapper(user, *args, **kwargs):if user.get("level", 0) < min_level:return f"权限不足:需要等级 {min_level}"return func(user, *args, **kwargs)return wrapperreturn decorator@require_level(min_level=5)def admin_panel(user):return "欢迎进入管理面板"user = {"name": "Bob", "level": 3}print(admin_panel(user)) # 权限不足:需要等级 5
知识点 | 说明 |
|---|---|
本质 | 装饰器工厂,返回真正的装饰器 |
结构 | 三层嵌套(外参、中函、内调) |
类装饰器 |
|
| 始终放在最内层 wrapper 上 |
应用场景 | 重试、超时、权限、可配置缓存 |
注意事项 | 不要忘记返回中间层函数 |

恭喜你!完成了 第 43-50 天 的高级 OOP 特性学习。你已经掌握了:
__str__, __call__, 运算符重载)@property)@staticmethod, @classmethod)