绝大多数情况下函数内部不该盲目用 try/except 捕获所有异常,应只捕获能处理的具体异常类型并明确异常契约;需区分严格模式与安全模式,边界层可用装饰器统一处理,finally 中异常会覆盖主流程异常,应优先使用上下文管理器。

函数内部该不该用 try/except 捕获所有异常
绝大多数情况下,不该。盲目在函数开头套一层 try/except Exception: 会掩盖真实错误位置,让调用方无法区分是参数错误、IO失败还是逻辑 bug。
真正需要捕获的,是函数明确能处理且有替代行为的异常,比如:requests.get() 失败时返回默认值,或 json.loads() 解析失败时跳过该条数据。
- 只捕获具体异常类型(如
ValueError、FileNotFoundError),避免捕获BaseException或空except: - 捕获后若无法恢复逻辑,应重新抛出(
raise)或转为更上层能理解的异常(raise MyCustomError(...) from e) - 日志记录要包含原始异常链(用
logger.exception()而非logger.error(str(e)))
如何设计带异常语义的函数签名
函数名和参数本身就应该暗示它的异常边界。比如 parse_int(s) 隐含可能失败,而 safe_parse_int(s, default=0) 则承诺不抛异常。
两种常见模式要主动选择,而不是让调用方猜:
立即学习“Python免费学习笔记(深入)”;
- 「严格模式」:函数只做一件事,失败即抛异常(如
open(path, 'r')抛FileNotFoundError) - 「安全模式」:函数内置 fallback 行为,返回
None、默认值或Result类型(需显式导入或定义) - 避免混合:不要让一个函数有时返回值、有时抛异常,除非异常是极少数意外(如内存耗尽)
装饰器统一处理异常是否靠谱
对 Web 视图函数或 CLI 入口这类「边界层」,用装饰器集中处理异常是合理且推荐的;但对普通工具函数,加装饰器反而增加调用栈深度、隐藏控制流、难以调试。
典型误用:给每个 calculate_* 函数都套 @handle_api_errors,结果网络错误跑到数学计算里被静默吞掉。
- 装饰器适合处理与函数业务无关的横切关注点:认证失败、限流拒绝、序列化错误
- 装饰器内不要做业务恢复逻辑(如“重试三次”),那属于函数职责
- 如果必须用,确保它保留原始 traceback(用
raise e.with_traceback(...)或raise原地重抛)
finally 中的异常会覆盖主流程异常吗
会,而且非常容易踩坑。如果 try 块已抛出异常 A,而 finally 块中又抛出异常 B,那么 A 会被丢弃,外部只能看到 B。
这在资源清理场景特别危险——比如文件未关闭成功,却把原始的 KeyError 掩盖了。
- 避免在
finally中执行可能抛异常的操作;改用try/except包裹清理逻辑 - 使用上下文管理器(
with)代替手写finally,__exit__方法天然支持异常抑制与传播控制 - 若必须手动清理,优先用
contextlib.closing()或封装成__enter__/__exit__类
:raises ValueError: 这类标注。










