You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tenacity重试模块代码问题排查及reraise参数用法咨询

Tenacity重试代码问题排查与正确用法指南

一、reraise参数的核心作用

reraise决定了重试耗尽后抛出的异常类型:

  • reraise=True:直接抛出原始业务异常(比如请求超时、连接失败),不会被Tenacity包装。此时你可以直接捕获原始异常,RetryError分支不会触发。
  • reraise=False(默认值):Tenacity会把原始异常包装成RetryError抛出。你需要捕获RetryError,并通过retry_error.last_attempt.exception()获取原始异常,同时可以从RetryError中拿到重试次数等上下文信息。

二、你的代码中的关键问题

  1. retry_if_exception_type()参数缺失
    这个函数需要明确传入要重试的异常类型(支持元组),空调用等于未指定有效重试触发条件,会导致重试逻辑不符合预期。

  2. 异常捕获顺序错误
    requests.RequestException是所有requests异常的父类,你把它放在最前面的except分支,后面的ConnectTimeoutError(urllib3原生异常)根本不会被触发——因为requests会把urllib3的异常包装成自身的ConnectTimeout(属于RequestException子类),父类分支会先拦截所有requests异常。

  3. after钩子逻辑错误
    after钩子会在每次函数调用后触发(包括成功和失败),你当前的日志固定打印“failed, will try once more”,但如果最后一次调用成功,或者已经是最后一次失败,这个日志就不符合实际情况。

  4. 未捕获正确的异常类型
    你直接捕获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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 08:03:26