词频任务可以接收空文本,得到空的结果字典。若把 if not text 当作“未提交文本”,合法的 "" 会被拒绝;若把 0 == False 当作“二者是同一个对象”,又会把两种类型混为一谈。输入边界需要区分三个问题:值是否相等、是否指向同一对象、是否在条件语句里视为真。

本章在 00 的顺序任务和01 的引用图上继续实验,解释器仍是已记录的 CPython 3.12.3。

一个空字符串不是“没有参数”

目前的 submit(store, "word_count", text) 要求 text 是字符串,允许空字符串;输入 None、整数 0 或布尔值 False 均报 TypeError,且不增加任务。与三种拒绝输入相比,空字符串会创建正常的 queued 任务,run 后状态为 succeeded、结果为 {}。

这是业务约定,不是语言替应用自动定义的“空值语义”。在 Python 中,空字符串、数字零、False 和 None 都是假值;标准类型文档的真值测试列出了这类行为。因而 if not text: 无法区分“合法的空文本”和“错误的非字符串”。代码先用 isinstance(text, str) 检查类型,再允许长度为零。未来若 None 要表示“未提供,使用默认文本”,应显式写 if text is None:,然后分别验证省略参数和传入 "";当前版本不提供这个默认值。

query 返回状态快照时也没有写 if task["result"]:。空结果 {} 的真值为假,却是已经成功的合法结果;只有 None 表示此时尚无结果。使用 is not None,才不会把成功的空结果误判为未执行。这条检查在 00 的状态路径里已经成立,02 用空输入把原本不明显的边界暴露出来。

相等不能回答“是不是这份对象”

官方比较表达式文档分别定义了等值比较和身份比较。0 == False 为 True,因为 bool 与整数存在数值关系;zero is False 为 False,二者不是同一个对象。在业务输入校验里,如果拿 text == False 排除布尔值,整数零也会被相等规则卷进来;如果只用 if not text,连空字符串也不能通过。用明确的类型和业务条件才能说明规则。

两段内容相等的字符串可能由不同的求值过程创建:"hello world" 与 " ".join(["hello", "world"])。任务只关心内容,两次提交产生的词频结果相等。实验不把“两个字符串必须 is not”写成业务断言,因为是否复用某个字符串对象不是任务合同。01 的列表实验需要 is 来画共享关系;这里的词频比较需要 == 来核对结果。关于对象身份与可变性的语言边界见数据模型。

本机实验和拒绝路径

从 examples/python-language/ 执行:

1
2
PYTHONPATH=src /usr/bin/python3.12 -W error experiments/02_values.py
PYTHONPATH=src /usr/bin/python3.12 -m unittest discover -s tests -v

第一条命令的退出码为 0,原始 JSON 包含 "empty_state": "succeeded"、"empty_result": {}、"zero_equals_false": true、"zero_is_false": false 和 "task_count": 3。计数 3 来自空文本与两次合法的相等文本提交;中间的 0、False、None 均被拒绝。第二条命令累计运行八条测试,退出码 0;其中新增测试对每个错误类型分别检查不增任务,随后正常提交的 ID 仍为 task-0002。原始命令、输出和断言保存在 examples/python-language/evidence/02/20261007-values/。演示对语法警告使用 -W error,验收运行没有警告。

预测题

把 submit 中的 isinstance(text, str) 换成 if not text: raise ValueError(...),分别传入 ""、0、False、None 时,哪项业务规则会被破坏?

答案:四个值都会走到拒绝分支,但 "" 原本是合法输入;其余三个虽然被拒绝,错误原因也从“不是字符串”被隐藏成“值为假”。这是一道按语言规则推导的改动题,代码库没有应用这个错误补丁;实际正例由本章的空文本测试证明。

变更题

现在把空文本视为非法,但仍允许包含空格的字符串。只改变一条校验时,应检查什么,才能避免 0、False 被转成字符串后误入任务?

答案:先保留类型检查,再单独判断 text == "";验证 " " 可以提交但词频结果仍是 {},同时验证 0 和 False 保持 TypeError,失败不改变已有任务。此规则尚未落盘和运行,属于变更练习的待测设计,不算本章已完成的实验。

下一步需要面对嵌套可变参数:即使外层容器是新建的,内层数据也可能仍为两项任务共享。系列导航只链接已经验收的文章。

参考资料