Python -O 会移除 assert:本地能拦住的非法输入,为什么换个启动选项就通过了
把 assert quantity > 0 放在入口,看起来可以拒绝负数;但用 python -O 启动时,这项断言会被移除,原本非法的值可能继续进入后面的计算。需要始终成立的外部输入检查,应该显式判断并抛出约定的异常,不能依赖启动模式。
下面只启动两个短命的本地 Python 子进程,执行固定字符串源码,对照普通模式与 -O。没有 shell 命令拼接、联网、业务写入或真实订单。需要可启动自身解释器的 Python 3.8 及以上环境;保存为 demo.py,运行 python demo.py。
AI模型生成概念图:可移除检查门与保留在路径上的检查门形成对照;比喻调试断言和必要校验,不是安全系统实测图。
import subprocess
import sys
sample = """def by_assert(quantity):
assert quantity > 0, "quantity must be positive"
return quantity * 2
def by_check(quantity):
if type(quantity) is not int or quantity <= 0:
raise ValueError("quantity must be a positive integer")
return quantity * 2
print("debug:", __debug__)
for label, function in [("assert", by_assert), ("check", by_check)]:
try:
value = function(-2)
except (AssertionError, ValueError) as error:
print(label + ":", type(error).__name__)
else:
print(label + ": accepted", value)
print("valid:", by_check(3))
"""
expected = {
"normal": [
"debug: True",
"assert: AssertionError",
"check: ValueError",
"valid: 6",
],
"optimized": [
"debug: False",
"assert: accepted -4",
"check: ValueError",
"valid: 6",
],
}
for label, options in [("normal", []), ("optimized", ["-O"])]:
result = subprocess.run(
[sys.executable, "-I", *options, "-c", sample],
capture_output=True,
text=True,
timeout=5,
)
if result.returncode != 0 or result.stderr:
raise RuntimeError(label + " subprocess failed: " + result.stderr)
if result.stdout.splitlines() != expected[label]:
raise RuntimeError(label + " output differed from the expected result")
print("[" + label + "]")
print(result.stdout, end="")同一份源码,启动方式改变了检查
普通模式输出 debug: True,assert 分支得到 AssertionError,显式检查分支得到 ValueError。优化模式则输出 debug: False;by_assert 接受负二并返回负四,by_check 仍然拒绝。两次合法输入都得到 valid: 6,说明显式检查没有把正常路径一并堵住。
这不是断言表达式偶尔算错,而是在优化模式下根本没有生成相应检查代码。代码使用 -I 隔离常见环境变量和用户路径影响,让两组主要差异来自明确列出的 -O 选项;输出对应本地固定样本,没有推断生产服务如何启动。
必要的校验要放在普通控制流里
by_check 使用 if 与 raise ValueError,因而不会随着 assert 一起消失。它还明确要求正整数,排除零、负数和布尔值。真正应用可按接口约定选择异常类型与允许范围,但不能仅为了演示方便就把类型或单位约束遗漏在别处。
已有程序迁移时,优先查找承担输入、权限、金额范围或文件条件判断的 assert,确认哪些检查即使关闭调试也必须执行。将这些检查改成显式分支后,同时测试合法与非法输入,不要仅观察异常类型变了就认为所有路径都已覆盖。
断言仍然适合表达内部不变量
assert 可以用于开发和测试中检查程序员预期不会被破坏的内部状态。关键在于:移除它以后,程序必要的行为和外部输入边界不能因此改变。不要把必须发生的赋值、写入或函数调用放进断言表达式,否则优化时副作用也会一并消失。
本教程许多固定示例会使用断言核对预期输出,运行前提是普通模式。此处的外层比较故意使用显式 if 与 RuntimeError,便于检查两种启动方式的结果,不因外层被误加 -O 而悄悄省略验证。这并不是禁止在测试里使用 assert。
验证部署时看实际进程参数
如果同一脚本在开发和运行环境表现不同,应核对真实解释器路径、启动参数和部署配置是否启用优化,再对最小输入重现。-O 的作用不止让某条代码跑快一点,它会改变断言与 __debug__ 相关行为,因此不能把它当作没有语义影响的通用加速开关。
本例只比较语言检查行为,不测量性能,也不宣称两次子进程耗时能代表业务收益。将可靠性问题解决之后,再用代表性工作负载评估性能;需要始终保留的校验不应因为追求某项计时数字而被关闭。
资料核对日期:2026年10月2日。本文固定输入示例在本地 Python 3.12.14 实际执行,退出码为0,预期结果检查通过;这是语言行为演示,不是生产压测。


