Tenacity重试模块代码问题排查及reraise参数用法咨询
Tenacity重试代码问题排查与正确用法指南
一、reraise参数的核心作用
reraise决定了重试耗尽后抛出的异常类型:
reraise=True:直接抛出原始业务异常(比如请求超时、连接失败),不会被Tenacity包装。此时你可以直接捕获原始异常,RetryError分支不会触发。reraise=False(默认值):Tenacity会把原始异常包装成RetryError抛出。你需要捕获RetryError,并通过retry_error.last_attempt.exception()获取原始异常,同时可以从RetryError中拿到重试次数等上下文信息。
二、你的代码中的关键问题
retry_if_exception_type()参数缺失
这个函数需要明确传入要重试的异常类型(支持元组),空调用等于未指定有效重试触发条件,会导致重试逻辑不符合预期。异常捕获顺序错误
requests.RequestException是所有requests异常的父类,你把它放在最前面的except分支,后面的ConnectTimeoutError(urllib3原生异常)根本不会被触发——因为requests会把urllib3的异常包装成自身的ConnectTimeout(属于RequestException子类),父类分支会先拦截所有requests异常。after钩子逻辑错误after钩子会在每次函数调用后触发(包括成功和失败),你当前的日志固定打印“failed, will try once more”,但如果最后一次调用成功,或者已经是最后一次失败,这个日志就不符合实际情况。未捕获正确的异常类型
你直接捕获urllib3.exceptions.ConnectTimeoutError,但requests实际抛出的是requests.exceptions.ConnectTimeout(已包装后的异常),所以这个分支永远不会执行。
三、修正后的代码
import requests from tenacity import retry, stop_after_attempt, wait_fixed, RetryError, retry_if_exception_type, after, before from requests.exceptions import RequestException, ConnectTimeout def log_attempt_number(retry_state): # 仅当失败且还有重试机会时打印重试提示 if retry_state.outcome.failed() and not retry_state.stop: print(f"Attempt {retry_state.attempt_number} failed, retrying...") # 最后一次失败时打印最终提示 elif retry_state.outcome.failed() and retry_state.stop: print(f"Attempt {retry_state.attempt_number} failed, no retries left.") def log_before(retry_state): print(f"Starting attempt {retry_state.attempt_number}") # 明确指定重试的异常类型,避免无差别重试 @retry( reraise=True, retry=retry_if_exception_type((RequestException,)), stop=stop_after_attempt(3), wait=wait_fixed(1), # 缩短等待时间便于测试 before=log_before, after=log_attempt_number ) def make_request(data): response = requests.get('http://localhost:8888/', timeout=0.1) response.raise_for_status() return response def fun(): try: response = make_request({'key': 'value'}) print(f'SUCCESS: {response}') except ConnectTimeout as e: # 先捕获具体子类异常,处理特定场景 print(f'CONNECT TIMEOUT: {e}') except RequestException as e: # 再捕获父类异常,处理其他requests错误 print(f'REQUEST FAILED: {e}') except RetryError as e: # 仅当reraise=False时会触发此分支 print(f'ALL RETRIES DEPLETED: {e.last_attempt.exception()}') print("[REACHED HERE]") print(make_request.retry.statistics) fun()
四、Tenacity使用关键注意事项
- 精准指定重试异常:始终通过
retry_if_exception_type()传入明确的异常类型,避免无差别重试所有异常(比如代码错误类异常不该重试)。 - 异常捕获顺序:先捕获具体的子类异常,再捕获父类异常,否则子类异常会被父类分支拦截。
- 钩子函数状态判断:使用
retry_state.outcome判断函数调用是否成功,retry_state.stop判断是否还有重试机会,避免日志逻辑错误。 - 根据场景选择
reraise:如果只需要处理业务异常,用reraise=True;如果需要获取重试上下文(比如重试次数),用reraise=False并处理RetryError。
内容的提问来源于stack exchange,提问作者coder_boy
相关产品推荐
相关产品推荐

