查看: 200|回复: 0

Python异常捕获裸except等10个坑点与最佳实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
异常处理的核心原则可以压成两句话:只捕获你能处理的异常,让不能处理的继续向上传播;错误尽早抛出,捕获尽量延迟。原文总结了七篇异常处理内容后,给出10个高频坑点和检查清单。下面不按原文逐条复述,而是按“错误现象—原因—修正代码—适用场景”重组,代码示例保留原文逻辑并做必要精简。

一、10个常见坑点与修正

1. 裸except什么都吞。裸except会连KeyboardInterrupt和SystemExit一起捕获,用户按Ctrl+C都可能关不掉程序。至少捕获Exception并记录日志:
  1. try:
  2.     do_something()
  3. except Exception as e:
  4.     logger.error(f'操作失败: {e}')
复制代码
这里的关键不是“多写一个Exception”,而是明确捕获边界,避免把退出信号当成业务异常。

2. 捕获了但不处理。except里只写pass会让调用者完全不知道失败。捕获后至少记录日志、使用默认值、重试或重新抛出:
  1. try:
  2.     result = risky_operation()
  3. except ValueError as e:
  4.     logger.warning(f'操作失败,使用默认值: {e}')
  5.     result = default_value
复制代码

3. 捕获范围太宽。一个except Exception包住取用户、建订单、支付三段逻辑,出错后无法判断是用户不存在、库存不足还是支付失败。应按异常类型分别处理:
  1. try:
  2.     user = get_user(user_id)
  3.     order = create_order(user, items)
  4.     payment = process_payment(order)
  5. except UserNotFoundError:
  6.     print('用户不存在')
  7. except InsufficientStockError:
  8.     print('库存不足')
  9. except PaymentFailedError as e:
  10.     print(f'支付失败: {e}')
复制代码
精确捕获的前提是底层能抛出有区分度的异常类型。

4. 在finally中使用return。finally里的return会吞掉try中正在传播的异常:
  1. def bad():
  2.     try:
  3.         return 1 / 0
  4.     finally:
  5.         return '看起来正常'
  6. def good():
  7.     try:
  8.         return 1 / 0
  9.     finally:
  10.         cleanup()
复制代码
finally只做清理,不改变控制流。bad()会返回“看起来正常”,ZeroDivisionError不再向外传播,这是排查时很容易误判的行为。

5. 异常处理中的异常。except块里如果再次写错,原始异常会丢失。例如处理代码引用未定义变量会抛NameError。修正思路是让except块保持简洁,复杂处理放到函数里,并保护处理函数:
  1. try:
  2.     result = 1 / 0
  3. except ZeroDivisionError as e:
  4.     try:
  5.         handle_division_error(e)
  6.     except Exception as handle_error:
  7.         print(f'错误处理也失败了: {handle_error}')
复制代码
必要时用traceback记录原始异常,避免“处理失败”覆盖“最初失败”。

6. 循环中的异常处理位置不对。把所有循环包在一个try里,遇到第一个非法值就会终止后续处理:
  1. data = ['1', '2', 'abc', '3', 'xyz', '4']
  2. numbers = []
  3. for item in data:
  4.     try:
  5.         numbers.append(int(item))
  6.     except ValueError:
  7.         print(f'跳过无效数据: {item}')
复制代码
这样numbers得到[1, 2, 3, 4]。如果业务要求“遇到错误立即停止”,才应把try放到循环外,二者是语义差异。

7. 忘记重新抛出。验证错误可以转成返回结果,但数据库错误如果本层无法恢复,不应吞掉:
  1. def process_request(request):
  2.     try:
  3.         validate(request)
  4.         result = execute(request)
  5.         return result
  6.     except ValidationError as e:
  7.         return {'error': str(e)}
复制代码
数据库错误不在这里捕获,交给上层事务管理处理,避免把可恢复与不可恢复异常混在一起。

8. 忽略异常链。异常转换时只抛新异常,会丢失原始连接错误。使用from保留__cause__:
  1. def business_api():
  2.     try:
  3.         raw_query('SELECT ...')
  4.     except ConnectionError as e:
  5.         raise BusinessError(f'查询失败: {e}') from e
复制代码
这样BusinessError.__cause__指向原始ConnectionError,日志和调试能看到完整因果链。

9. 用异常做流程控制。把KeyError当if-else用既慢也不清晰。dict自带get:
  1. def get_value(data, key):
  2.     return data.get(key)
复制代码
异常应服务于意外情况,而不是替代正常分支。

10. 忘记清理资源。open后如果read抛异常,close不会执行。使用with或finally:
  1. def process_file(path):
  2.     with open(path) as f:
  3.         return f.read()
复制代码
如果受限于旧代码不能用with,就在finally中关闭,并判断文件对象是否已创建。

二、最佳实践
原文给出的黄金法则可以落到七个动作:早抛出,在入口处验证;晚捕获,在真正能处理异常的业务逻辑层或API入口捕获;精确捕获,只为能恢复的异常类型写except;不吞异常,捕获后记录日志、回退、重试或重新抛出;资源释放用with或finally;异常消息要包含可操作信息,例如raise ValueError('年龄必须>0'),而不是“无效输入”;用自定义异常表达领域概念,例如InsufficientBalanceError(current=100, required=500),比通用ValueError更有区分度。

入口验证可以这样写:
  1. def validate_input(data):
  2.     if not data:
  3.         raise ValueError('数据不能为空')
  4.     if 'name' not in data:
  5.         raise ValueError('缺少name字段')
复制代码

异常也要文档化。比如转账函数应在docstring中列出可能抛出的ValueError、InsufficientBalanceError、AccountFrozenError,让调用方知道要处理什么。

三、终极检查清单
所有try都有对应的except或finally;except捕获的是能处理的异常类型;没有被吞掉的异常,每个except都做了记录、回退、重试或重新抛出;资源释放用了with或finally;异常消息清晰有用;异常层次结构合理,项目有自定义异常;关键异常记录到日志。

Python异常处理的哲学是EAFP:先做,再处理意外。但EAFP不等于滥用异常。异常处理是区分“能用的代码”和“健壮的代码”的关键:好的异常处理让程序面对意外时优雅降级,而不是崩溃,也不能把Ctrl+C、原始异常链和资源泄漏一起吞掉。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-19 14:12 , Processed in 0.023516 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部