Python 中的 assert 语句:基本语法与 AssertionError

调试代码这件事,我干了十几年。说实话,最让我头疼的不是复杂的逻辑错误,而是那些「明明感觉没问题,一跑就崩」的情况。后来我养成了一个习惯——在关键位置插上 assert 语句。这玩意儿看着简单,用好了能省下大把抓头发的时间。

今天咱们就聊聊 Python 里的 assert。别小看它,很多老手都用不对。

assert 的基本语法

先看最基础的样子:

assert 条件表达式

如果条件为 True,啥事没有,程序继续往下走。
如果条件为 False,立刻抛出 AssertionError,程序停住。

举个例子:

def divide(a, b):
    assert b != 0
    return a / b

print(divide(10, 2))  # 输出 5.0
print(divide(10, 0))  # 抛出 AssertionError

运行到第二行 divide(10, 0) 时,程序直接炸了。嗯,这就是 assert 的脾气——它不跟你商量。

带错误信息的 assert

光抛个异常还不够友好。你想想看,线上日志里只看到一行 AssertionError,你根本不知道是哪个条件没满足。所以我一般会加一段描述信息:

assert 条件表达式, "错误描述信息"

比如:

def withdraw(balance, amount):
    assert amount > 0, "取款金额必须大于0"
    assert amount <= balance, f"余额不足,当前余额: {balance}"
    return balance - amount

这样当断言失败时,你能立刻看到具体原因。我在项目中遇到过好几次,就是因为没加描述信息,排查问题花了整整一下午。从那以后,我所有的 assert 都带上了描述。

AssertionError 的本质

说白了,AssertionError 就是 Python 内置的一个异常类。它继承自 Exception。你可以像捕获其他异常一样捕获它:

try:
    assert 1 == 2, "1不等于2"
except AssertionError as e:
    print(f"断言失败: {e}")

输出:

断言失败: 1不等于2

但我要提醒你——不要用 try-except 来捕获 AssertionError。为什么?因为 assert 的本意是「这里不应该出现这种情况」。如果你捕获了它,等于在说「出错了也没关系」,那还不如直接用 if 判断。

⚠️ 注意: 不要用 try-except 包裹 assert。assert 是用来发现 bug 的,不是用来处理预期错误的。

assert 的开关控制

这一点很多人不知道——assert 是可以全局关闭的。

当你用 -O 或 -OO 参数运行 Python 时,所有 assert 语句都会被忽略。比如:

# 正常运行
python my_script.py

# 关闭 assert
python -O my_script.py

为什么会这样?因为 Python 把 assert 当成调试工具。生产环境里,为了性能,你可以关掉它。但这也意味着——不要把 assert 用在必须执行的逻辑上。

我曾经见过一个同事,在支付接口里用 assert 检查金额:

assert amount > 0, "金额非法"

结果上线时加了 -O 参数,这个检查直接失效了。嗯,后果你可以想象。所以记住:

核心原则: assert 只用于调试和测试,永远不要用它来做正式的逻辑校验。

什么时候该用 assert?

我个人的经验是,以下场景特别适合:

  • 函数前置条件检查:比如参数不能为 None、列表不能为空
  • 函数后置条件检查:比如返回值必须在某个范围内
  • 不可能发生的情况:比如 switch-case 的 default 分支
  • 单元测试中的断言:虽然 unittest 有自己的 assert 方法,但原生 assert 也够用

举个例子:

def process_user_data(data):
    assert data is not None, "数据不能为空"
    assert "name" in data, "缺少 name 字段"
    
    result = do_something(data)
    
    assert result["status"] in (0, 1), f"未知状态: {result['status']}"
    return result

你看,我在函数开头和结尾都加了 assert。开头保证输入没问题,结尾保证输出符合预期。这叫「契约式编程」,说白了就是让代码自己给自己做检查。

避坑指南

我曾经踩过一个坑,分享给你:

不要用 assert 检查用户输入。 用户输入是不可信的,而且你不能因为用户输错了就抛 AssertionError。正确的做法是用 if 判断,然后返回友好的错误信息。

还有一点:assert 后面的表达式不要有副作用。比如:

# 错误示范
assert pop_item() is not None  # pop_item 会修改列表

# 正确做法
item = pop_item()
assert item is not None

因为 assert 可能被关闭,如果表达式有副作用,关闭后行为就不一致了。

💡 小技巧: 在开发阶段,我习惯在关键函数里密集使用 assert。等代码稳定了,再考虑是否保留。这比写一堆 if-raise 要快得多。

知识体系总览

下面这张图,帮你把本章的核心内容串起来:

Python assert 语句知识体系 assert 语句 基本语法 assert 条件 assert 条件, "描述" AssertionError 继承自 Exception 可被 try-except 捕获 但不建议捕获 开关控制 python -O 关闭 assert 生产环境可关闭 不要用于逻辑校验 使用场景 前置条件检查 后置条件检查 不可能发生的情况 单元测试 避坑指南 不要检查用户输入 表达式不要有副作用 不要用 try 捕获 不要用于正式校验 最佳实践 开发阶段密集使用 带描述信息 契约式编程 稳定后评估保留

这张图把 assert 的五个核心维度串在了一起。你写代码时,可以对照着看看自己用对了没有。

总结一下

assert 是个好东西,但用不好就是坑。记住三条:

  1. 只用于调试和测试,别用在正式逻辑里
  2. 带上描述信息,方便排查问题
  3. 表达式不要有副作用,否则关了 assert 行为就变了

嗯,今天就聊到这儿。assert 看着简单,但背后这些门道,够你琢磨一阵子了。

可可精品资料库