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))
代码解读:
log_with_format(log_format)
:这是最外层函数,它接收日志格式log_format
作为参数。decorator(func)
:这是装饰器函数,它接收被装饰的函数func
作为参数。wrapper(*args, **kwargs)
:这是wrapper函数,它负责执行被装饰的函数,并在执行前后添加日志功能。@functools.wraps(func)
: 这个装饰器保留了原函数的元信息,比如__name__
和__doc__
,这样在使用装饰器后,不会影响函数的正常使用和调试。- 使用时,先调用
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__
方法来实现装饰逻辑。
怎么实现呢?
- 定义一个类,在
__init__
方法中接收被装饰的函数。 - 重写
__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秒后可以再次调用
代码解读:
RateLimiter(max_calls, period)
:类的构造函数,接收最大调用次数和时间窗口作为参数,并初始化内部状态。__call__(self, func)
:这个方法让RateLimiter
类的实例可以像函数一样被调用。它接收被装饰的函数func
作为参数,并返回一个wrapper
函数。wrapper(*args, **kwargs)
:这个函数负责执行被装饰的函数,并在执行前后添加频率限制的逻辑。- 使用时,先创建一个
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())
代码解读:
retry(max_attempts, delay)
:这是装饰器工厂函数,它接收最大重试次数和重试间隔时间作为参数,并返回一个装饰器。decorator(func)
:这是装饰器函数,它接收被装饰的函数func
作为参数。wrapper(*args, **kwargs)
:这个函数负责执行被装饰的函数,并在执行过程中添加重试逻辑。- 使用时,先调用
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())
代码解读:
@authenticate
装饰器先执行,进行身份验证。@log_execution
装饰器后执行,记录函数的执行时间。- 装饰器的执行顺序是从下往上,就像穿衣服一样,先穿里面的,再穿外面的。
总结:
组合装饰器可以让你将多个功能组合在一起,使得代码更简洁、更易于维护。 记住装饰器的执行顺序是从下往上。
最佳实践与注意事项
- 使用
functools.wraps
: 务必使用functools.wraps
装饰器来保留原函数的元信息,这对于调试和代码可读性非常重要。 - 避免过度使用: 装饰器虽然强大,但过度使用会使代码难以理解和维护。 只在必要时使用装饰器。
- 注意执行顺序: 当组合多个装饰器时,要注意它们的执行顺序,这会影响最终的结果。
- 异常处理: 在装饰器中妥善处理异常,避免影响被装饰函数的正常执行。
- 理解闭包: 装饰器的核心是闭包,理解闭包的概念对于理解装饰器的工作原理至关重要。
总结
今天我们学习了Python装饰器的进阶用法,包括参数化装饰器、类装饰器和装饰器工厂。
- 参数化装饰器 让你能够定制装饰器的行为。
- 类装饰器 让你能够将装饰器的状态和行为封装在一个类中。
- 装饰器工厂 让你能够批量生产装饰器。
希望今天的讲座能帮助大家更好地理解和使用Python装饰器。 记住,练习是掌握任何技能的关键。 多写代码,多思考,你就能成为装饰器大师!
感谢大家的参与!