装饰器中异常需显式转发:wrapper内须用无参raise保留原始traceback;异步场景需在await同级try块中处理;包装异常用raise...from保持因果链。

装饰器里抛出的异常默认不会被外层 try 捕获
直接在 @decorator 包裹的函数内部 raise 异常,调用方能正常捕获;但若异常发生在 wrapper 函数本身(比如参数校验失败、日志写入出错、缓存连接超时),默认会被吞掉或变成 UnboundLocalError/TypeError 等伪装异常——因为没显式 re-raise,Python 会按常规作用域规则终止 wrapper 执行,不向下传递。
必须在 wrapper 中用 try/except + raise 显式转发异常
核心动作就两步:捕获、原样抛出。不能只 print 或 logging.error 就完事,否则调用链断裂。
-
raise单独写(无参数)可保留原始异常类型、消息和 traceback - 不要用
raise e,它会重置 traceback,丢失原始位置信息 - 如果要在转发前加日志,确保
logging.exception()在raise前调用
def log_on_failure(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
logging.exception("Failed in %s", func.__name__)
raise # ← 关键:不带参数,复用原 traceback
return wrapper需要修改异常内容时,用 raise ... from 保持因果链
有些场景得把底层异常包装成业务异常(比如把 requests.ConnectionError 转成 ServiceUnavailableError),这时不能丢掉根因。
- 用
raise NewException(...) from e显式声明因果关系 - 这样
__cause__和 traceback 都保留,调试时能顺藤摸瓜 - 避免
raise NewException(str(e))这种信息蒸馏,会丢失类型和上下文
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
except requests.RequestException as e:
raise ServiceUnavailableError(f"API unreachable: {url}") from easync 装饰器要区分 await 和普通异常处理
异步装饰器的 wrapper 是 async def,里面 await func(...) 抛出的异常仍需显式 raise,但注意:不能在 await 外层套同步的 try/except 后直接 raise —— 那样会抛出 RuntimeError: await wasn't used with future。
立即学习“Python免费学习笔记(深入)”;
- 异常捕获必须在
await同一层级的try块内 - 所有
await调用都要包在自己的try/except里,不能靠外层“兜底” - 异步上下文管理器(如
async with)里的异常也要单独处理
async def retry_on_timeout(func):
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs) # ← await 必须在这里
except asyncio.TimeoutError:
logging.warning("Timeout, retrying...")
return await func(*args, **kwargs)
except Exception:
raise # ← 正确:在 await 同级 raise
return wrapper装饰器异常转发最易忽略的点是:你以为异常来自被装饰函数,其实它卡在 wrapper 的某个中间步骤(比如序列化参数时 json.dumps(non_serializable_obj) 报错),而你只在业务逻辑里写了 try,根本没覆盖到那行。


















