业务背景
编写爬虫、连接远程数据库等操作常常因为网络问题失败,需要我们重新尝试。为了避免对整个程序的影响,我们可以使用装饰器让特定模块再出错时自动重新运行。
代码
import functools
def retry_on_error(max_attempts=2):
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"函数 {func.__name__} 运行出错:{e}. 正在尝试第 {attempts} 次重新运行...")
else:
raise RuntimeError(f"函数 {func.__name__} 已达到最大尝试次数 {max_attempts},仍无法成功运行")
return wrapper
return decorator
# 使用装饰器来修饰函数
@retry_on_error(max_attempts=3)
def potentially_unstable_function():
import random
if random.random() < 0.5:
raise ValueError("随机错误")
else:
print("函数成功运行")
# 调用函数
potentially_unstable_function()
其中functools.wraps
是 Python 标准库中 functools
模块提供的一个装饰器,用于将一个函数的元信息(比如函数名、文档字符串、参数列表等)复制给另一个函数。
在编写装饰器时,通常会嵌套多层函数,例如在上面的示例中,retry_on_error
装饰器返回了一个内部的 decorator
函数,而 decorator
函数返回了一个 wrapper
函数。这样多层嵌套的装饰器会使得被修饰的函数的元信息(比如函数名、文档字符串等)被覆盖掉,这可能导致调试和使用时的困惑。
为了解决这个问题,我们可以在装饰器内部使用 functools.wraps
装饰器来复制被修饰函数的元信息。这样可以确保被修饰函数的元信息不会丢失,使得调试和使用更加方便。
举个例子,我们可以这样使用 functools.wraps
:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("调用被装饰的函数前")
result = func(*args, **kwargs)
print("调用被装饰的函数后")
return result
return wrapper
@my_decorator
def example_function():
"""这是一个示例函数"""
print("示例函数被调用")
print(example_function.__name__) # 输出:example_function
print(example_function.__doc__) # 输出:"这是一个示例函数"
简单来说就是, functools.wraps可以让你在重复嵌套核心函数时不被装饰器覆盖掉。
参考文献
关于装饰器,详细可见Python 装饰器 | 菜鸟教程
关于functools.wraps
,详细可见Python装饰器functools.wraps(func)详解-CSDN博客