断言与异常处理:assert 与 try-except 的博弈
说实话,很多刚入行的同学都会问我一个问题:「老师,assert 和 try-except 到底有什么区别?我该用哪个?」
这个问题问得好。我当年刚写 Python 的时候也纠结过。有一次线上服务半夜挂了,查了半天发现是 assert 把生产数据给拦了——嗯,从那以后我才真正搞明白这两个东西的定位。
今天咱们就把这事彻底说清楚。
核心区别:一个用于「不应该发生」,一个用于「可能发生」
先给个最直白的结论:
- assert 是开发阶段的检查工具。它假设「这事不可能发生」。
- try-except 是运行时的容错机制。它承认「这事确实有可能发生」。
你想想看,如果你写了一个函数,要求传入的参数必须是正整数。你觉得调用方一定会传对——那用 assert。但如果你在读取一个用户上传的文件,文件可能不存在、可能格式不对——那就得用 try-except。
一句话总结:
assert 是「防呆」的,try-except 是「防错」的。
assert 的正确打开方式
assert 的语法很简单:
assert 条件, "错误信息"
如果条件为 False,程序会直接抛出 AssertionError 并终止。
我个人习惯在以下场景用 assert:
- 函数前置条件检查:比如参数不能为 None,列表不能为空
- 函数后置条件检查:比如返回值必须在某个范围内
- 单元测试中的断言:这是最标准的用法
- 代码中「不可能到达」的分支:比如 switch-case 的 default 分支
举个例子:
def calculate_discount(price, rate):
assert price > 0, "价格必须为正数"
assert 0 < rate <= 1, "折扣率必须在 0 到 1 之间"
result = price * rate
assert result > 0, "计算结果异常"
return result
我在项目中遇到过一个问题:有个同事在核心业务逻辑里用了 assert 来校验用户输入。结果测试环境一切正常,上了生产就崩了——因为生产环境默认会禁用 assert(python -O 参数)。
警告:
千万不要用 assert 来做数据校验!生产环境可能禁用 assert,你的校验逻辑会直接消失。
try-except 的正确打开方式
try-except 是用来处理「预期内」的异常情况的。说白了,就是你知道这里可能会出问题,而且你知道怎么处理。
我建议在以下场景用 try-except:
- 外部资源访问:文件读写、网络请求、数据库操作
- 用户输入处理:格式转换、类型转换
- 第三方库调用:你不知道它会抛出什么异常
- 业务逻辑中的可恢复错误:比如重试、降级、回滚
看个实际例子:
def load_user_config(user_id):
try:
with open(f"configs/{user_id}.json", "r") as f:
return json.load(f)
except FileNotFoundError:
# 用户没有配置文件,返回默认配置
return get_default_config()
except json.JSONDecodeError:
# 配置文件损坏,记录日志并返回默认配置
logger.error(f"用户 {user_id} 的配置文件损坏")
return get_default_config()
except Exception as e:
# 其他未知错误,记录日志并抛出
logger.critical(f"加载用户配置时发生未知错误: {e}")
raise
注意看,这里我用了三个不同的 except 分支。为什么?因为每个异常的处理方式不一样。
我曾经接手过一个项目,整个代码里只有一个 except Exception,所有错误都吞掉了。结果线上出了 bug,日志里全是「发生错误」,根本不知道哪里出了问题。嗯,那段时间我每天都在骂人。
小技巧:
尽量捕获具体的异常类型。不要用裸的 except: 或者 except Exception。你想想看,如果连键盘中断(KeyboardInterrupt)都被你吞了,用户想停都停不了。
什么时候该用哪个?一张表说清楚
| 场景 | 用 assert | 用 try-except |
|---|---|---|
| 函数参数校验(开发阶段) | ✅ | ❌ |
| 用户输入校验(生产环境) | ❌ | ✅ |
| 单元测试 | ✅ | ❌ |
| 文件读写 | ❌ | ✅ |
| 「不可能发生」的分支 | ✅ | ❌ |
| 网络请求重试 | ❌ | ✅ |
| 调试时检查中间结果 | ✅ | ❌ |
| 业务逻辑中的可恢复错误 | ❌ | ✅ |
避坑指南:我踩过的那些坑
我曾经在一个金融项目里,用 assert 来校验交易金额是否合法。结果上线后,运维同学用 -O 参数启动了服务,所有校验全部失效。那天晚上,我们回滚了三次。
我曾经看到有人这么写代码:
try:
assert condition
except AssertionError:
# 处理错误
pass
这简直是双重错误。首先,assert 不应该被 try 捕获——它本身就是用来让程序崩溃的。其次,你捕获了 AssertionError 却什么都不做,那这个 assert 还有什么意义?
我曾经在一个遗留系统里看到,整个项目只有一个 try-except 块,包住了 main 函数。所有异常都被捕获,然后打印一句「系统错误」就完事了。这种写法,说白了就是掩耳盗铃。
核心逻辑图
下面这张图展示了 assert 和 try-except 在整个软件生命周期中的定位:
最后说两句
其实 assert 和 try-except 没有谁好谁坏,关键是用对地方。我个人的经验是:
- 写代码的时候,先用 assert 把「不可能」的情况卡死
- 上线之前,把 assert 换成 try-except 来处理「可能」的情况
- 如果某个地方既可以用 assert 又可以用 try-except,那大概率是设计有问题
记住一句话:assert 是写给开发者的,try-except 是写给用户的。
可可精品资料库