tenacity是retrying的官方推荐替代库,支持同步/异步、类型提示完善、重试条件可组合,且兼容Python 3.11+;迁移只需将@retry替换为@retry(stop=stop_after_attempt(3))等配置。

Retrying库不维护了,直接换 tenacity
如果你刚 pip install retrying,会发现它在 Python 3.11+ 上无法安装,且自 2019 年起已停止维护。官方推荐替代方案是 tenacity——功能更全、类型提示完善、错误处理更清晰。
常见错误现象:ImportError: No module named 'retrying' 或 TypeError: 'RetryError' object is not subscriptable(老版本与新 asyncio 不兼容)。
- 用
pip install tenacity替代pip install retrying -
tenacity默认支持同步/异步、可组合重试条件(比如“超时 + 网络错误”才重试),而retrying的@retry装饰器对异常类型判断较粗糙 - 迁移成本低:把
@retry换成@retry(stop=stop_after_attempt(3))即可起步
如何让 requests 请求只在特定 HTTP 状态码下重试?
盲目重试 400、401、422 这类客户端错误没意义,反而可能触发风控;真正该重试的是 5xx 或连接中断这类临时性故障。
使用 retry_if_exception + 自定义判断逻辑最稳妥:
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
from tenacity import retry, stop_after_attempt, retry_if_exception import requests <p>def is_server_error_or_connection_error(exc): return isinstance(exc, requests.RequestException) and ( hasattr(exc.response, 'status_code') and 500 <= exc.response.status_code < 600 or 'Connection refused' in str(exc) or 'Timeout' in str(exc) )</p><p>@retry( stop=stop_after_attempt(3), retry=retry_if_exception(is_server_error_or_connection_error) ) def call_api(url): return requests.get(url, timeout=5)
- 别依赖
status_code属性直接访问——exc.response在连接失败时为None,必须先判空或捕获 AttributeError - 建议把超时设短(如 3–5 秒),避免重试拖长整体耗时;
tenacity默认无 jitter,加上wait=wait_exponential(multiplier=1, min=1, max=10)更友好 - 如果用
httpx,同理检查exc.response.status_code或exc.__cause__是否含网络错误
重试时怎么避免敏感参数被重复打印到日志?
默认情况下,tenacity 在重试失败时会把整个函数调用栈和参数原样打到日志里,如果 call_api(url, headers={'Authorization': 'Bearer xxx'}),token 就会明文泄露。
- 用
before=before_log(logger, logging.DEBUG)会记录所有参数;改用before=before_nothing()关闭自动日志,自己控制输出 - 或者重写
before回调,对敏感字段做脱敏处理: def before_log_redacted(retry_state): logger.debug("Calling API (url redacted, auth: *** )")- 更彻底的做法:把 token 放进 session 实例或全局配置,不在函数参数中传递认证信息
异步 API 调用(aiohttp/httpx)怎么加重试?
同步重试装饰器不能直接套在 async def 函数上,否则会报 RuntimeWarning: coroutine 'xxx' was never awaited。
必须用 tenacity 提供的异步支持:
from tenacity import AsyncRetrying, stop_after_attempt, wait_exponential import httpx <p>@AsyncRetrying( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), reraise=True ) async def fetch_data(client: httpx.AsyncClient, url: str): response = await client.get(url) response.raise_for_status() return response.json()
- 注意:必须显式传入
reraise=True,否则异常会被吞掉,函数返回None - 不要在重试装饰器内创建
AsyncClient实例——连接池应复用,否则每轮重试都新建 client,可能触发 “Too many open files” - 若混用 sync/async 场景,别共用同一个重试策略实例;
AsyncRetrying和Retrying是不同类
重试逻辑越靠近真实错误原因越有效,比如区分 DNS 失败、SSL 握手失败、503 Service Unavailable,这些在 tenacity 里都能拆开定制,但多数人只配个次数就上线——结果是重试了,问题还在。

















