Python重试工具案例如何封装请求重试

wen python案例 30

本文目录导读:

Python重试工具案例如何封装请求重试

  1. 基础重试装饰器
  2. 使用tenacity库(推荐)
  3. 完整的重试工具类
  4. 高级重试 - 带回调函数
  5. 异步重试实现
  6. 使用建议

我来介绍几种Python请求重试的封装方法,从简单到复杂:

基础重试装饰器

import time
import functools
from typing import Callable, Any, Type
def retry(
    max_retries: int = 3,
    delay: float = 1.0,
    exceptions: tuple = (Exception,),
    backoff: float = 2.0
) -> Callable:
    """
    重试装饰器
    Args:
        max_retries: 最大重试次数
        delay: 初始延迟时间(秒)
        exceptions: 需要重试的异常类型
        backoff: 延迟倍增因子
    """
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            last_exception = None
            current_delay = delay
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt < max_retries - 1:
                        print(f"重试第 {attempt + 1} 次,延迟 {current_delay}s")
                        time.sleep(current_delay)
                        current_delay *= backoff
            raise last_exception
        return wrapper
    return decorator
# 使用示例
@retry(max_retries=3, delay=1.0, exceptions=(ConnectionError, TimeoutError))
def fetch_data(url: str) -> dict:
    import requests
    response = requests.get(url, timeout=5)
    response.raise_for_status()
    return response.json()

使用tenacity库(推荐)

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import requests
# 配置重试策略
retry_config = {
    "stop": stop_after_attempt(3),  # 最多重试3次
    "wait": wait_exponential(multiplier=1, min=1, max=10),  # 指数退避
    "retry": retry_if_exception_type((ConnectionError, TimeoutError, requests.RequestException))
}
@retry(**retry_config)
def api_request(url: str, method: str = 'GET', **kwargs) -> requests.Response:
    """带重试的API请求"""
    response = requests.request(method, url, **kwargs)
    response.raise_for_status()
    return response
# 使用示例
try:
    result = api_request('https://api.example.com/data', timeout=5)
    print("请求成功:", result.json())
except Exception as e:
    print("所有重试都失败:", e)

完整的重试工具类

import time
import logging
from typing import Optional, Callable
from dataclasses import dataclass
from functools import wraps
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class RetryConfig:
    """重试配置"""
    max_retries: int = 3
    initial_delay: float = 1.0
    max_delay: float = 60.0
    backoff_factor: float = 2.0
    jitter: bool = True  # 添加随机抖动避免惊群效应
class RetryHandler:
    """重试处理器"""
    def __init__(self, config: Optional[RetryConfig] = None):
        self.config = config or RetryConfig()
    def execute(self, func: Callable, *args, **kwargs):
        """执行带重试的函数"""
        last_exception = None
        current_delay = self.config.initial_delay
        for attempt in range(self.config.max_retries):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                last_exception = e
                if attempt < self.config.max_retries - 1:
                    # 计算延迟时间
                    delay = min(current_delay, self.config.max_delay)
                    # 添加随机抖动(±50%)
                    if self.config.jitter:
                        import random
                        delay = delay * (0.5 + random.random())
                    logger.warning(
                        f"请求失败 (尝试 {attempt + 1}/{self.config.max_retries}): {e}"
                        f"\n将在 {delay:.2f} 秒后重试..."
                    )
                    time.sleep(delay)
                    current_delay *= self.config.backoff_factor
        # 所有重试都失败
        logger.error(f"所有重试都失败,共尝试 {self.config.max_retries} 次")
        raise last_exception
# 使用示例
retry_handler = RetryHandler(RetryConfig(max_retries=5))
# 方式1:直接使用
try:
    result = retry_handler.execute(requests.get, 'https://api.example.com', timeout=5)
    print("成功获取数据")
except Exception as e:
    print(f"最终失败: {e}")
# 方式2:作为装饰器
def with_retry(handler: Optional[RetryHandler] = None):
    """重试装饰器工厂"""
    handler = handler or RetryHandler()
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return handler.execute(func, *args, **kwargs)
        return wrapper
    return decorator
@with_retry(RetryHandler(RetryConfig(max_retries=3)))
def fetch_user_data(user_id: int) -> dict:
    """获取用户数据"""
    response = requests.get(f'https://api.example.com/users/{user_id}')
    response.raise_for_status()
    return response.json()

高级重试 - 带回调函数

from typing import Callable, Optional, List
from dataclasses import dataclass, field
@dataclass
class RetryCallback:
    """重试回调"""
    on_retry: Optional[Callable] = None  # 每次重试前回调
    on_success: Optional[Callable] = None  # 成功回调
    on_failure: Optional[Callable] = None  # 最终失败回调
    on_attempt: Optional[Callable] = None  # 每次尝试后回调
class AdvancedRetryHandler:
    """高级重试处理器"""
    def __init__(
        self,
        config: RetryConfig,
        callbacks: Optional[RetryCallback] = None,
        should_retry: Optional[Callable[[Exception, int], bool]] = None
    ):
        self.config = config
        self.callbacks = callbacks or RetryCallback()
        self.should_retry = should_retry or (lambda e, attempt: True)
    def execute(self, func: Callable, *args, **kwargs):
        """执行带重试的函数"""
        last_exception = None
        current_delay = self.config.initial_delay
        for attempt in range(self.config.max_retries):
            try:
                result = func(*args, **kwargs)
                # 成功回调
                if self.callbacks.on_success:
                    self.callbacks.on_success(result)
                # 尝试后回调
                if self.callbacks.on_attempt:
                    self.callbacks.on_attempt(attempt, True, result)
                return result
            except Exception as e:
                last_exception = e
                # 判断是否需要重试
                if not self.should_retry(e, attempt):
                    raise
                if attempt < self.config.max_retries - 1:
                    delay = min(current_delay, self.config.max_delay)
                    # 重试回调
                    if self.callbacks.on_retry:
                        self.callbacks.on_retry(e, attempt, delay)
                    # 尝试后回调
                    if self.callbacks.on_attempt:
                        self.callbacks.on_attempt(attempt, False, e)
                    time.sleep(delay)
                    current_delay *= self.config.backoff_factor
        # 最终失败回调
        if self.callbacks.on_failure:
            self.callbacks.on_failure(last_exception, self.config.max_retries)
        raise last_exception
# 使用示例
class APIClient:
    """API客户端"""
    def __init__(self):
        # 配置重试
        config = RetryConfig(
            max_retries=3,
            initial_delay=1.0,
            backoff_factor=2.0,
            jitter=True
        )
        # 配置回调
        callbacks = RetryCallback(
            on_retry=lambda e, attempt, delay: logger.info(
                f"正在重试 (第{attempt+1}次): {e}, 延迟{delay:.2f}s"
            ),
            on_success=lambda result: logger.info("请求成功"),
            on_failure=lambda e, retries: logger.error(
                f"请求失败,已重试{retries}次: {e}"
            ),
            on_attempt=lambda attempt, success, data: logger.debug(
                f"尝试第{attempt+1}次: {'成功' if success else '失败'}"
            )
        )
        # 判断是否需要重试
        should_retry = lambda e, attempt: isinstance(e, (ConnectionError, TimeoutError))
        self.retry_handler = AdvancedRetryHandler(config, callbacks, should_retry)
    def get_data(self, url: str) -> dict:
        """获取数据"""
        def _request():
            response = requests.get(url, timeout=5)
            response.raise_for_status()
            return response.json()
        return self.retry_handler.execute(_request)
# 使用
client = APIClient()
try:
    data = client.get_data('https://api.example.com/data')
    print("数据获取成功:", data)
except Exception as e:
    print("最终失败:", e)

异步重试实现

import asyncio
from typing import Callable, Any
import aiohttp
import logging
logger = logging.getLogger(__name__)
class AsyncRetryHandler:
    """异步重试处理器"""
    def __init__(self, max_retries: int = 3, delay: float = 1.0):
        self.max_retries = max_retries
        self.delay = delay
    async def execute(self, func: Callable, *args, **kwargs) -> Any:
        """异步执行带重试的函数"""
        for attempt in range(self.max_retries):
            try:
                return await func(*args, **kwargs)
            except Exception as e:
                if attempt < self.max_retries - 1:
                    wait_time = self.delay * (2 ** attempt)  # 指数退避
                    logger.warning(
                        f"异步请求失败 (尝试 {attempt + 1}/{self.max_retries}): {e}"
                        f"\n将在 {wait_time:.2f} 秒后重试..."
                    )
                    await asyncio.sleep(wait_time)
                else:
                    logger.error(f"异步请求最终失败: {e}")
                    raise
# 使用示例
async def fetch_async_data(url: str) -> dict:
    """异步获取数据"""
    async with aiohttp.ClientSession() as session:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as response:
            response.raise_for_status()
            return await response.json()
async def main():
    retry_handler = AsyncRetryHandler(max_retries=3, delay=1.0)
    try:
        data = await retry_handler.execute(fetch_async_data, 'https://api.example.com')
        print("异步获取成功:", data)
    except Exception as e:
        print("异步获取失败:", e)
# 运行异步代码
if __name__ == "__main__":
    asyncio.run(main())

使用建议

  1. 简单场景:使用装饰器方式(第一种)
  2. 推荐使用:tenacity库(功能最全面)
  3. 复杂场景:自定义重试处理器
  4. 需要回调:使用高级重试处理器
  5. 异步场景:使用异步重试处理器

这些封装方式都考虑了:

  • 指数退避策略
  • 异常类型过滤
  • 日志记录
  • 灵活配置

选择哪种方式取决于你的具体需求场景。

抱歉,评论功能暂时关闭!