
本文详解如何在 python 装饰器内部获取并判断被装饰函数的返回值(如 http 状态码),从而实现条件化重试逻辑,避免盲目重试,并给出可直接运行的完整示例与关键注意事项。
本文详解如何在 python 装饰器内部获取并判断被装饰函数的返回值(如 http 状态码),从而实现条件化重试逻辑,避免盲目重试,并给出可直接运行的完整示例与关键注意事项。
在 Python 中,装饰器本质上是对函数调用过程的封装,而非对函数定义的静态修饰。这意味着:装饰器中的逻辑(如重试控制)必须在实际执行被装饰函数后,才能访问其返回值——你无法在函数执行前“预知”返回值,也不能将返回值作为装饰器参数传入(因为此时函数尚未运行)。因此,正确做法是在装饰器内部调用 func(*args, **kwargs),捕获其返回值,再根据该值决定是否重试。
以下是一个符合需求的、生产就绪的重试装饰器实现:
import requests
import time
from functools import wraps
def retry_on_5xx(retries=3, delay=3):
"""
仅在响应状态码为 5xx 时重试;4xx(如 404)等客户端错误不重试。
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(retries + 1): # +1 表示首次尝试也计入循环
try:
result = func(*args, **kwargs) # ✅ 关键:执行函数并获取返回值
if isinstance(result, requests.Response):
status = result.status_code
elif isinstance(result, int): # 直接返回 status_code 的场景
status = result
else:
raise ValueError(f"Unsupported return type: {type(result)}")
# ✅ 核心逻辑:仅当 status 是 5xx 时才重试(且未达最大重试次数)
if 500 <= status < 600 and attempt < retries:
print(f"Status {status} (5xx), retrying in {delay}s... (attempt {attempt + 1}/{retries})")
time.sleep(delay)
continue # 进入下一次循环重试
return result # 成功或非 5xx 错误,直接返回
except requests.exceptions.RequestException as e:
print(f"Request failed with exception: {e}")
if attempt < retries:
time.sleep(delay)
continue
raise # 最后一次失败,抛出异常
return result # 理论上不会执行到此处,但为健壮性保留
return wrapper
return decorator
# 使用示例:返回 Response 对象(推荐方式)
@retry_on_5xx(retries=2, delay=1)
def get_with_response():
return requests.get("https://httpstat.us/503", timeout=5) # 模拟 503 Service Unavailable
# 或返回 status_code(兼容旧逻辑)
@retry_on_5xx(retries=2, delay=1)
def get_status_code():
resp = requests.get("https://httpstat.us/500", timeout=5)
return resp.status_code
def main():
print("=== Testing 5xx retry ===")
resp = get_with_response()
print(f"Final status: {resp.status_code}")
print("\n=== Testing 4xx (no retry) ===")
# 注意:需替换为真实返回 4xx 的 URL,例如 https://httpstat.us/404
try:
# 此处仅作示意;实际应确保 URL 可访问且返回 4xx
resp4 = requests.get("https://httpbin.org/status/404", timeout=5)
print(f"4xx response handled without retry: {resp4.status_code}")
except Exception as e:
print(f"4xx test skipped due to network: {e}")
if __name__ == "__main__":
main()⚠️ 关键注意事项:
-
不要混淆装饰器参数与函数返回值:
@retry_on_5xx(retries=3)中的retries是装饰器配置,而status_code是被装饰函数运行后产生的结果,二者生命周期完全不同; - 避免全局/环境变量传递状态:这会破坏函数纯度、引发并发问题,且违背装饰器设计原则;
-
优先返回
Response对象:比仅返回status_code更灵活(可后续读取json()、text等); -
显式处理异常与非 5xx 错误:如
requests.exceptions.Timeout或404,应立即终止重试,防止无意义轮询; -
使用
@wraps(func):保留原函数的__name__、__doc__等元信息,利于调试和文档生成。
通过将返回值检查逻辑置于装饰器内部的主循环中,你既能精准控制重试边界,又能保持代码清晰、可测试、符合 Python 惯例。

















