Python 装饰器进阶:参数化、类装饰器与装饰器工厂

好的,各位观众老爷们,欢迎来到“Python 装饰器进阶:参数化、类装饰器与装饰器工厂”特别节目!今天咱们要一起解锁装饰器的高级玩法,让你的代码瞬间逼格满满,成为同事眼中的大神!

第一幕:参数化装饰器,让装饰器更灵活!

话说,装饰器这玩意儿,用起来是方便,但有时候我们想让它更个性化一点,比如根据不同的情况执行不同的操作。这时候,参数化装饰器就派上用场了。

啥是参数化装饰器?

简单来说,就是在装饰器外面再套一层函数,这层函数负责接收参数,然后返回一个真正的装饰器。

代码示例:带参数的日志记录装饰器

假设我们想写一个日志记录装饰器,可以指定日志级别(比如debug, info, warning)。

import functools
import logging

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def log_level(level):
    """
    参数化装饰器,用于指定日志级别
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            logging.log(level, f"Calling function: {func.__name__}")
            result = func(*args, **kwargs)
            logging.log(level, f"Function {func.__name__} returned: {result}")
            return result
        return wrapper
    return decorator

@log_level(logging.DEBUG) # 使用DEBUG级别
def add(x, y):
    """
    一个简单的加法函数
    """
    return x + y

@log_level(logging.WARNING) # 使用WARNING级别
def divide(x, y):
    """
    一个简单的除法函数
    """
    if y == 0:
        return "除数不能为0"
    return x / y

# 测试
print(add(5, 3))
print(divide(10, 2))
print(divide(10, 0))

代码解读:

  1. log_level(level): 这就是我们的参数化装饰器,它接收一个 level 参数,表示日志级别。
  2. decorator(func): log_level 函数返回一个真正的装饰器 decorator,它接收一个函数 func 作为参数。
  3. *`wrapper(args, kwargs)`: decorator 函数返回一个包装器 wrapper,它负责在函数执行前后记录日志。
  4. @log_level(logging.DEBUG): 使用 @log_level(logging.DEBUG) 相当于 add = log_level(logging.DEBUG)(add)。 先调用 log_level(logging.DEBUG) 拿到装饰器,然后再用这个装饰器去装饰 add 函数。

原理剖析:

参数化装饰器其实就是一个函数嵌套。外层函数接收参数,内层函数才是真正的装饰器。 这种方式赋予了装饰器极大的灵活性,我们可以根据不同的参数来定制装饰器的行为。

第二幕:类装饰器,让装饰器更有状态!

除了函数,类也能当装饰器用!类装饰器最大的优势是可以保存状态,这在某些场景下非常有用。

啥是类装饰器?

简单来说,就是把一个类当成装饰器来使用。 实现方式是让类实现 __call__ 方法,这样类的实例就可以像函数一样被调用。

代码示例:统计函数调用次数的类装饰器

import functools

class CallCounter:
    """
    类装饰器,用于统计函数调用次数
    """
    def __init__(self, func):
        self.func = func
        self.count = 0
        functools.wraps(func)(self) # 保留原函数的信息

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Function {self.func.__name__} called {self.count} times")
        return self.func(*args, **kwargs)

@CallCounter
def multiply(x, y):
    """
    一个简单的乘法函数
    """
    return x * y

# 测试
print(multiply(2, 3))
print(multiply(4, 5))
print(multiply(6, 7))

代码解读:

  1. CallCounter(func): 类的构造函数接收一个函数 func 作为参数,并初始化计数器 count 为 0。
  2. *`call(self, args, kwargs)`: __call__ 方法让类的实例可以像函数一样被调用。 每次调用时,计数器 count 加 1,并打印调用次数。
  3. @CallCounter: 使用 @CallCounter 相当于 multiply = CallCounter(multiply)。 创建 CallCounter 的实例,并将 multiply 函数作为参数传递给构造函数。

原理剖析:

当使用 @CallCounter 装饰 multiply 函数时,实际上创建了一个 CallCounter 类的实例,并将 multiply 函数作为参数传递给该实例。 当调用 multiply(2, 3) 时,实际上是调用了 CallCounter 实例的 __call__ 方法,从而实现了对函数调用的拦截和统计。

类装饰器的优势:

  • 状态保持: 类可以保存状态,比如计数器、缓存等。
  • 代码组织: 可以将装饰器的逻辑封装在类中,使代码更清晰。

带参数的类装饰器

import functools

class CallCounterWithLimit:
    """
    带参数的类装饰器,用于统计函数调用次数,并限制最大调用次数
    """
    def __init__(self, max_calls):
        self.max_calls = max_calls

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if wrapper.count < self.max_calls:
                wrapper.count += 1
                print(f"Function {func.__name__} called {wrapper.count} times (limit: {self.max_calls})")
                return func(*args, **kwargs)
            else:
                print(f"Function {func.__name__} call limit reached ({self.max_calls})")
                return None  # 或者抛出异常
        wrapper.count = 0  # 初始化调用次数
        return wrapper

@CallCounterWithLimit(max_calls=3)
def greet(name):
    """
    一个简单的问候函数
    """
    return f"Hello, {name}!"

# 测试
print(greet("Alice"))
print(greet("Bob"))
print(greet("Charlie"))
print(greet("David")) # 超过限制
print(greet("Eve"))   # 超过限制

代码解读:

  1. CallCounterWithLimit(max_calls): 构造函数接收 max_calls 参数,用于限制最大调用次数。
  2. __call__(self, func): __call__ 方法接收被装饰的函数 func,并返回一个 wrapper 函数。
  3. wrapper.count = 0: 注意这里, 我们在wrapper函数上定义了一个count属性,用来记录被装饰函数的调用次数。

第三幕:装饰器工厂,批量生产装饰器!

有时候,我们需要创建多个相似的装饰器,这时候就可以使用装饰器工厂来简化代码。

啥是装饰器工厂?

装饰器工厂就是一个返回装饰器的函数。 它接收一些配置参数,然后根据这些参数生成不同的装饰器。

代码示例:创建重试装饰器的工厂

import time
import functools

def retry(max_attempts, delay=1):
    """
    装饰器工厂,用于创建重试装饰器
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    print(f"Attempt {attempts} failed: {e}")
                    time.sleep(delay)
            print(f"Function {func.__name__} failed after {max_attempts} attempts")
            return None  # 或者抛出异常
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unreliable_function():
    """
    一个可能失败的函数
    """
    import random
    if random.random() < 0.5:
        raise Exception("Function failed!")
    return "Function succeeded!"

# 测试
print(unreliable_function())

代码解读:

  1. retry(max_attempts, delay=1): 这就是我们的装饰器工厂,它接收 max_attempts(最大重试次数)和 delay(重试间隔)两个参数。
  2. decorator(func): retry 函数返回一个真正的装饰器 decorator,它接收一个函数 func 作为参数。
  3. *`wrapper(args, kwargs)`: decorator 函数返回一个包装器 wrapper,它负责在函数执行失败时进行重试。
  4. @retry(max_attempts=3, delay=0.5): 使用 @retry(max_attempts=3, delay=0.5) 相当于 unreliable_function = retry(max_attempts=3, delay=0.5)(unreliable_function)

原理剖析:

装饰器工厂的本质还是函数嵌套。 外层函数接收配置参数,内层函数才是真正的装饰器。 装饰器工厂可以根据不同的配置参数生成不同的装饰器,从而避免了重复编写相似的装饰器代码。

总结:

我们今天学习了装饰器的高级用法,包括:

  • 参数化装饰器: 让装饰器可以接收参数,更加灵活。
  • 类装饰器: 让装饰器可以保存状态,适用于某些特定场景。
  • 装饰器工厂: 批量生产装饰器,简化代码。

希望今天的课程能帮助你更深入地理解装饰器,并在实际开发中灵活运用。 记住,编程的乐趣在于不断学习和探索! 感谢大家的观看,我们下期再见!

表格总结:

特性 参数化装饰器 类装饰器 装饰器工厂
核心概念 装饰器外层嵌套函数,用于接收参数 使用类实现装饰器,通过__call__方法实现调用 返回装饰器的函数,用于批量生成装饰器
优势 灵活,可根据参数定制装饰器行为 可保存状态,代码组织清晰 简化代码,避免重复编写相似装饰器
实现方式 多层函数嵌套 实现__call__方法的类 返回装饰器的函数
适用场景 需要根据不同情况执行不同操作的装饰器 需要保存状态的装饰器,如计数器、缓存等 需要创建多个相似装饰器的场景
示例 日志级别可配置的日志记录装饰器 统计函数调用次数的装饰器 创建重试装饰器的工厂

补充说明:

  • functools.wraps: 这个装饰器很重要! 它可以保留被装饰函数的元信息,比如函数名、文档字符串等。 强烈建议在编写装饰器时使用它,以保证代码的可读性和可维护性。
  • 异常处理: 在编写装饰器时,要考虑异常处理。 尽量不要让装饰器影响被装饰函数的正常执行。
  • 性能: 装饰器会增加函数调用的开销。 在性能敏感的场景下,要谨慎使用装饰器。

希望这些补充说明能让你对装饰器有更全面的了解! 记住,实践是检验真理的唯一标准! 多写代码,多思考,你也能成为装饰器大师!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注