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

Python 装饰器进阶:参数化、类装饰器与装饰器工厂 – 讲座模式

大家好,我是今天的主讲人,很高兴能和大家一起探索Python装饰器的更高级用法。相信各位已经对装饰器的基本概念有所了解,知道它们就像魔法盒子,可以给函数或方法“穿衣服”,增强它们的功能,而不需要修改函数本身的源代码。

今天我们要深入研究三个更酷炫的装饰器玩法:参数化装饰器、类装饰器,以及最终的大招——装饰器工厂。 准备好了吗?让我们开始吧!

1. 参数化装饰器:定制你的魔法

想象一下,你有一个装饰器,用于记录函数执行的时间。但是,你希望能够自定义日志的格式,比如是简单的时间戳,还是包含更多信息的详细记录。这就需要我们的第一个主角——参数化装饰器登场了。

啥是参数化装饰器?

简单来说,就是让你的装饰器可以接收参数,从而根据不同的参数,执行不同的装饰逻辑。

怎么实现呢?

实现参数化装饰器,需要多包一层函数。最外层函数接收参数,中间层函数接收被装饰的函数,最内层函数才是真正执行装饰逻辑的地方。

代码示例:

import time
import functools

def log_with_format(log_format):
    """
    一个参数化装饰器,可以自定义日志格式。
    """
    def decorator(func):
        """
        真正的装饰器函数。
        """
        @functools.wraps(func)  # 别忘了保留原函数的元信息!
        def wrapper(*args, **kwargs):
            """
            wrapper函数,负责执行被装饰的函数,并添加日志功能。
            """
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            execution_time = end_time - start_time

            if log_format == "simple":
                print(f"Function {func.__name__} executed in {execution_time:.4f} seconds.")
            elif log_format == "detailed":
                print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Function {func.__name__} called with args: {args}, kwargs: {kwargs}. "
                      f"Execution time: {execution_time:.4f} seconds. Result: {result}")
            else:
                print(f"Unsupported log format: {log_format}")

            return result
        return wrapper
    return decorator

# 使用示例:
@log_with_format("simple")
def my_function(x, y):
    """
    一个简单的函数,用于演示参数化装饰器。
    """
    time.sleep(0.5)  # 模拟函数执行时间
    return x + y

@log_with_format("detailed")
def another_function(a, b, c=10):
    """
    另一个函数,用于演示参数化装饰器。
    """
    time.sleep(0.2)
    return a * b + c

print(my_function(5, 3))
print(another_function(2, 4, c=5))

代码解读:

  1. log_with_format(log_format):这是最外层函数,它接收日志格式 log_format 作为参数。
  2. decorator(func):这是装饰器函数,它接收被装饰的函数 func 作为参数。
  3. wrapper(*args, **kwargs):这是wrapper函数,它负责执行被装饰的函数,并在执行前后添加日志功能。
  4. @functools.wraps(func): 这个装饰器保留了原函数的元信息,比如__name____doc__,这样在使用装饰器后,不会影响函数的正常使用和调试。
  5. 使用时,先调用 log_with_format("simple")log_with_format("detailed"),返回一个装饰器,然后再用这个装饰器去装饰函数。

总结:

参数化装饰器让你的装饰器更灵活,可以根据不同的参数定制不同的行为。记住,关键在于多包一层函数来接收参数。

表格总结参数化装饰器结构:

层级 函数名称 作用 接收参数
1 log_with_format 接收配置参数,返回装饰器函数 log_format (日志格式)
2 decorator 接收被装饰的函数,返回 wrapper 函数 func (被装饰的函数)
3 wrapper 执行被装饰的函数,并添加额外的逻辑(日志) *args, **kwargs (被装饰函数的参数)

2. 类装饰器:让装饰器更像对象

接下来,我们来看看类装饰器。如果说参数化装饰器让装饰器更灵活,那么类装饰器就让装饰器更像一个对象,拥有自己的状态和行为。

啥是类装饰器?

类装饰器就是用一个类来充当装饰器。它通过重写 __init__ 方法来接收被装饰的函数,并通过重写 __call__ 方法来实现装饰逻辑。

怎么实现呢?

  1. 定义一个类,在 __init__ 方法中接收被装饰的函数。
  2. 重写 __call__ 方法,在其中实现装饰逻辑,并调用被装饰的函数。

代码示例:

import time
import functools

class RateLimiter:
    """
    一个类装饰器,用于限制函数的调用频率。
    """
    def __init__(self, max_calls, period):
        """
        初始化 RateLimiter 对象。

        Args:
            max_calls: 在 period 时间内允许的最大调用次数。
            period: 时间窗口,单位为秒。
        """
        self.max_calls = max_calls
        self.period = period
        self.calls = []  # 记录每次调用的时间

    def __call__(self, func):
        """
        实现装饰逻辑。
        """
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            """
            wrapper 函数,负责执行被装饰的函数,并添加频率限制。
            """
            now = time.time()
            # 移除超出时间窗口的调用记录
            self.calls = [call_time for call_time in self.calls if call_time > now - self.period]

            if len(self.calls) >= self.max_calls:
                print(f"Function {func.__name__} is rate limited. Please try again later.")
                return None  # 可以返回 None 或抛出异常

            self.calls.append(now)
            return func(*args, **kwargs)
        return wrapper

# 使用示例:
@RateLimiter(max_calls=2, period=5)  # 5秒内最多调用2次
def api_call():
    """
    模拟一个需要限制调用频率的 API 调用。
    """
    print("Making API call...")
    time.sleep(1)
    return "API response"

print(api_call())
print(api_call())
print(api_call())  # 会被限流
time.sleep(6)
print(api_call())  # 5秒后可以再次调用

代码解读:

  1. RateLimiter(max_calls, period):类的构造函数,接收最大调用次数和时间窗口作为参数,并初始化内部状态。
  2. __call__(self, func):这个方法让 RateLimiter 类的实例可以像函数一样被调用。它接收被装饰的函数 func 作为参数,并返回一个 wrapper 函数。
  3. wrapper(*args, **kwargs):这个函数负责执行被装饰的函数,并在执行前后添加频率限制的逻辑。
  4. 使用时,先创建一个 RateLimiter 类的实例,然后用这个实例去装饰函数。

总结:

类装饰器允许你将装饰器的状态和行为封装在一个类中,使得代码更易于组织和维护。 __call__ 方法是关键,它让你的类实例可以像函数一样被调用。

表格总结类装饰器结构:

方法名称 作用 接收参数
__init__ 初始化类实例,接收装饰器的配置参数 max_calls, period (频率限制参数)
__call__ 使类实例可以像函数一样被调用,实现装饰逻辑 func (被装饰的函数)
wrapper 执行被装饰的函数,并添加额外的逻辑(频率限制) *args, **kwargs (被装饰函数的参数)

3. 装饰器工厂:批量生产魔法盒子

最后,我们来到装饰器工厂。 参数化装饰器和类装饰器已经足够强大了,但如果你需要创建多个相似的装饰器,只是参数不同,难道要写很多重复的代码吗? 这时候,装饰器工厂就派上用场了。

啥是装饰器工厂?

装饰器工厂就是一个函数,它接收一些参数,然后返回一个装饰器。 简单来说,它是一个“生产装饰器的工厂”。

怎么实现呢?

其实,我们之前写的参数化装饰器就是一个装饰器工厂!只不过我们现在把它更一般化,让它可以生产更复杂的装饰器。

代码示例:

import time
import functools

def retry(max_attempts, delay=1):
    """
    一个装饰器工厂,用于创建重试装饰器。

    Args:
        max_attempts: 最大重试次数。
        delay: 重试间隔时间,单位为秒。
    """
    def decorator(func):
        """
        真正的装饰器函数。
        """
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            """
            wrapper 函数,负责执行被装饰的函数,并添加重试逻辑。
            """
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    print(f"Attempt {attempts} failed. Retrying in {delay} seconds...")
                    time.sleep(delay)
            print(f"Function {func.__name__} failed after {max_attempts} attempts.")
            return None  # 可以返回 None 或抛出异常
        return wrapper
    return decorator

# 使用示例:
@retry(max_attempts=3, delay=2)  # 最大重试 3 次,每次间隔 2 秒
def flaky_function():
    """
    一个不稳定的函数,有时会抛出异常。
    """
    import random
    if random.random() < 0.5:
        raise ValueError("Something went wrong!")
    else:
        print("Function executed successfully!")
        return "Success!"

print(flaky_function())

代码解读:

  1. retry(max_attempts, delay):这是装饰器工厂函数,它接收最大重试次数和重试间隔时间作为参数,并返回一个装饰器。
  2. decorator(func):这是装饰器函数,它接收被装饰的函数 func 作为参数。
  3. wrapper(*args, **kwargs):这个函数负责执行被装饰的函数,并在执行过程中添加重试逻辑。
  4. 使用时,先调用 retry(max_attempts=3, delay=2),返回一个装饰器,然后再用这个装饰器去装饰函数。

总结:

装饰器工厂让你能够批量生产具有相似功能的装饰器,避免代码重复。 它本质上就是一个返回装饰器的函数。

表格总结装饰器工厂结构:

层级 函数名称 作用 接收参数
1 retry 接收配置参数,返回装饰器函数 max_attempts, delay (重试参数)
2 decorator 接收被装饰的函数,返回 wrapper 函数 func (被装饰的函数)
3 wrapper 执行被装饰的函数,并添加额外的逻辑(重试) *args, **kwargs (被装饰函数的参数)

进阶应用:组合装饰器

装饰器不仅可以单独使用,还可以组合使用,就像给函数穿多层“衣服”一样。

代码示例:

import time
import functools

def log_execution(func):
    """
    一个简单的装饰器,用于记录函数的执行时间。
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """
        wrapper 函数,负责执行被装饰的函数,并添加日志功能。
        """
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Function {func.__name__} executed in {execution_time:.4f} seconds.")
        return result
    return wrapper

def authenticate(func):
    """
    一个简单的装饰器,用于模拟身份验证。
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """
        wrapper 函数,负责执行被装饰的函数,并添加身份验证逻辑。
        """
        # 模拟身份验证
        is_authenticated = True  # 假设用户已通过身份验证
        if is_authenticated:
            print("User is authenticated.")
            return func(*args, **kwargs)
        else:
            print("Authentication failed.")
            return None  # 可以返回 None 或抛出异常
    return wrapper

@authenticate
@log_execution
def sensitive_operation():
    """
    一个需要身份验证和记录执行时间的操作。
    """
    print("Performing sensitive operation...")
    time.sleep(0.3)
    return "Operation completed successfully."

print(sensitive_operation())

代码解读:

  1. @authenticate 装饰器先执行,进行身份验证。
  2. @log_execution 装饰器后执行,记录函数的执行时间。
  3. 装饰器的执行顺序是从下往上,就像穿衣服一样,先穿里面的,再穿外面的。

总结:

组合装饰器可以让你将多个功能组合在一起,使得代码更简洁、更易于维护。 记住装饰器的执行顺序是从下往上。

最佳实践与注意事项

  • 使用 functools.wraps 务必使用 functools.wraps 装饰器来保留原函数的元信息,这对于调试和代码可读性非常重要。
  • 避免过度使用: 装饰器虽然强大,但过度使用会使代码难以理解和维护。 只在必要时使用装饰器。
  • 注意执行顺序: 当组合多个装饰器时,要注意它们的执行顺序,这会影响最终的结果。
  • 异常处理: 在装饰器中妥善处理异常,避免影响被装饰函数的正常执行。
  • 理解闭包: 装饰器的核心是闭包,理解闭包的概念对于理解装饰器的工作原理至关重要。

总结

今天我们学习了Python装饰器的进阶用法,包括参数化装饰器、类装饰器和装饰器工厂。

  • 参数化装饰器 让你能够定制装饰器的行为。
  • 类装饰器 让你能够将装饰器的状态和行为封装在一个类中。
  • 装饰器工厂 让你能够批量生产装饰器。

希望今天的讲座能帮助大家更好地理解和使用Python装饰器。 记住,练习是掌握任何技能的关键。 多写代码,多思考,你就能成为装饰器大师!

感谢大家的参与!

发表回复

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