Python -O 会移除 assert:本地能拦住的非法输入,为什么换个启动选项就通过了

前天 3阅读

把 assert quantity > 0 放在入口,看起来可以拒绝负数;但用 python -O 启动时,这项断言会被移除,原本非法的值可能继续进入后面的计算。需要始终成立的外部输入检查,应该显式判断并抛出约定的异常,不能依赖启动模式。

下面只启动两个短命的本地 Python 子进程,执行固定字符串源码,对照普通模式与 -O。没有 shell 命令拼接、联网、业务写入或真实订单。需要可启动自身解释器的 Python 3.8 及以上环境;保存为 demo.py,运行 python demo.py。

Python -O 会移除 assert:本地能拦住的非法输入,为什么换个启动选项就通过了

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,预期结果检查通过;这是语言行为演示,不是生产压测。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。