当前位置:首页>python>【一起学 Python】第 50 天:高级 OOP 特性之带参数的装饰器

【一起学 Python】第 50 天:高级 OOP 特性之带参数的装饰器

  • 2026-09-25 17:21:02
【一起学 Python】第 50 天:高级 OOP 特性之带参数的装饰器

欢迎来到 Python 学习计划的第 50 天!🎉

昨天我们学习了 装饰器的原理与编写,掌握了如何增强函数和类的功能。今天,我们将在此基础上更进一步,学习 带参数的装饰器(Decorator with Arguments)。

在实际开发中,我们经常需要配置装饰器的行为(如重试次数、缓存时间、权限级别)。掌握带参数的装饰器,将让你的代码更加灵活和强大!

一、为什么需要带参数的装饰器?

1. 局限性回顾

昨天的基础装饰器只能“开/关”功能,无法控制细节。

@retry  # ❌ 重试几次?间隔多久?无法配置def fetch_data():    pass

2. 带参数的优势

通过参数,我们可以定制装饰器的行为。

@retry(times=3, delay=1)  # ✅ 重试 3 次,每次间隔 1 秒def fetch_data():    pass

3. 本质

带参数的装饰器本质上是一个 装饰器工厂(Decorator Factory)。它返回一个真正的装饰器,再由这个装饰器去装饰函数。

二、核心结构:三层嵌套

带参数的装饰器需要 三层函数嵌套,这是初学者最容易混淆的地方。

1. 结构图解

外层函数 (接收装饰器参数)    ↓ 返回中层函数 (接收被装饰的函数)    ↓ 返回内层函数 (接收被装饰函数的参数)    ↓ 执行原函数

2. 代码模板

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 wrapper    return real_decorator@decorator_with_args("A", "B")def my_func():    pass

💡 记忆口诀:“外参、中函、内调”。

  1. 外层拿装饰器自己的参数。
  2. 中层拿被装饰的函数。
  3. 内层拿函数调用的参数。

三、实战示例

1. 重复执行装饰器

让函数执行指定次数。

import functoolsdef repeat(times):    def decorator(func):        @functools.wraps(func)        def wrapper(*args, **kwargs):            for _ in range(times):                result = func(*args, **kwargs)            return result        return wrapper    return decorator@repeat(times=3)def say_hello():    print("Hello")say_hello()# 输出:# Hello# Hello# Hello

2. 失败重试装饰器(实用)

网络请求失败时自动重试。

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 e                    print(f"失败,{delay}秒后重试... ({i+1}/{max_attempts})")                    time.sleep(delay)        return wrapper    return decorator@retry(max_attempts=3, delay=1)def connect_db():    import random    if random.random() < 0.7:        raise ConnectionError("连接失败")    return "连接成功"# 运行try:    print(connect_db())except Exception as e:    print(f"最终失败:{e}")

3. 带参数的缓存装饰器

结合 类属性 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] = result            return result        return wrapper    return 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__ 的知识,类也可以实现带参数的装饰器。

1. 结构

  • __init__:接收装饰器参数。
  • __call__:接收被装饰的函数,并返回 wrapper。

2. 示例

import functoolsclass Retry:    def __init__(self, max_attempts=3):        self.max_attempts = max_attempts    def __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:                        raise                    print(f"重试 {i+1}/{self.max_attempts}")        return wrapper@Retry(max_attempts=3)def unstable_func():    import random    if random.random() < 0.5:        raise Exception("失败")    return "成功"

💡 对比:

  • 函数式装饰器:三层嵌套,简洁。
  • 类装饰器:利用 __init__ 和 __call__,状态管理更方便(适合复杂逻辑)。

五、带参数 vs 不带参数

特性

不带参数装饰器

带参数装饰器

语法

@decorator

@decorator(arg=1)

嵌套层数

两层(装饰器 -> wrapper)

三层(工厂 -> 装饰器 -> wrapper)

调用过程

decorator(func)

decorator(arg)(func)

灵活性

低(逻辑固定)

高(可配置)

复杂度

简单

较复杂,易出错

特殊情况:可选参数

有时我们希望装饰器既可以 @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 wrapper    if func is not None:        return real_decorator(func)    return real_decorator@flexible_decoratordef func1(): pass@flexible_decorator(prefix="Debug")def func2(): pass

六、常见误区与注意事项

1. 忘记返回装饰器

外层函数必须返回中层装饰器函数。

# ❌ 错误def wrong_decorator(times):    def wrapper(*args):  # 直接返回了 wrapper,缺少中间层        pass    return wrapper# ✅ 正确def right_decorator(times):    def decorator(func):  # 中间层        def wrapper(*args):            pass        return wrapper    return decorator

2. functools.wraps 位置

@functools.wraps 应该放在 最内层 wrapper 上,修饰的是原函数。

def my_decorator(arg):    def real_decorator(func):        @functools.wraps(func)  # ✅ 在这里        def wrapper(*args, **kwargs):            pass        return wrapper    return real_decorator

3. 参数作用域

确保内层函数能访问外层参数(闭包特性)。

def decorator(prefix):    def real_decorator(func):        def wrapper(*args):            print(prefix)  # ✅ 可以访问外层的 prefix            return func(*args)        return wrapper    return real_decorator

七、实战练习

练习 1:实现超时装饰器

创建一个 @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 result        return wrapper    return decorator@timeout(seconds=1)def slow_task():    time.sleep(1.5)try:    slow_task()except TimeoutError as e:    print(e)  # 函数执行超时 1 秒

练习 2:实现权限等级装饰器

创建一个 @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 wrapper    return decorator@require_level(min_level=5)def admin_panel(user):    return "欢迎进入管理面板"user = {"name": "Bob", "level": 3}print(admin_panel(user))  # 权限不足:需要等级 5

八、总结

知识点

说明

本质

装饰器工厂,返回真正的装饰器

结构

三层嵌套(外参、中函、内调)

类装饰器

__init__ 收参数,__call__ 收函数

functools.wraps

始终放在最内层 wrapper 上

应用场景

重试、超时、权限、可配置缓存

注意事项

不要忘记返回中间层函数

🎉 高级 OOP 特性阶段完结!

恭喜你!完成了 第 43-50 天 的高级 OOP 特性学习。你已经掌握了:

  • ✅ 魔法方法(__str__, __call__, 运算符重载)
  • ✅ 属性控制(@property)
  • ✅ 方法类型(@staticmethod, @classmethod)
  • ✅ 装饰器(原理、编写、带参数)

最新文章

随机文章