Python zip 输入长度不一致时静默丢掉尾部数据
zip 默认在最短输入耗尽时停止。Python 3.10 起可用 strict=True 检出长度差,但检查随消费发生,已处理的前缀和输入迭代器副作用不会自动回退。
两列数据本应逐项配对,zip() 却少处理了末尾几条,也没有抛出异常:
names = ["alpha", "beta", "gamma"]
scores = [10, 20]
print(list(zip(names, scores)))
# 预期 [('alpha', 10), ('beta', 20)]
这是 zip() 的默认结束规则:任意一个输入耗尽,配对就停止。如果业务约定两个输入必须等长,默认行为会把输入缺失隐藏成一次正常完成。这里处理的是数量不一致;即使长度相等,记录顺序不对应的问题仍需另外核对。
Python 3.10 起可以在配对时要求等长
对于必须一一对应的输入,把 strict=True 加到实际消费的 zip() 调用中:
names = ["alpha", "beta", "gamma"]
scores = [10, 20]
pairs = zip(names, scores, strict=True)
print(list(pairs))
# 预期 ValueError: zip() argument 2 is shorter than argument 1
strict 参数从 Python 3.10 加入。它接受一般可迭代对象,包括没有 len() 的生成器;不要求先把每个输入全部转换成列表。旧版 Python 不支持这个参数,不能只复制调用而忽略运行环境。
这个参数适合“缺任何一项都算数据错误”的约定。如果业务本来就只需要公共前缀,默认截断可以是有意选择;如果要保留较长输入的剩余项并填充缺失位置,则是另一种数据规则,不能把补齐后的结果当成已经证明等长。
创建 zip 对象时还没有检查完输入
zip() 是惰性迭代器。上例中,赋值给 pairs 的那一行不会立刻发现长度差;直到消费到一方耗尽的位置,严格模式才有机会抛错。
这也意味着,下面的代码不能验证两份输入是否等长:
pairs = zip([1, 2, 3], [10, 20], strict=True)
first = next(pairs)
print(first)
# 预期 (1, 10)
只读第一项、提前 break,或让下游只取有限前缀,都可能没有触及差异。验收时应让需要验证的有限输入完整消费,并分别覆盖第一份较短、第二份较短、两份等长和空输入。
不要为了验证无限流而强行完整消费。严格模式的报错取决于输入耗尽;持续产生数据的流没有一个可以提前保证到达的结束点。此时应按业务定义有限批次,再检查每批的配对约定。
严格模式不会撤销之前处理过的配对
例如在 for left, right in zip(..., strict=True) 的循环体中写数据库,前两对可能已经写入,第三轮才发现数量不同。最终抛出 ValueError 不代表前面没有执行。
对于规模可控的有限输入,可以先完成配对检查,再进入业务处理阶段:
def checked_pairs(left, right):
return list(zip(left, right, strict=True))
pairs = checked_pairs(["alpha", "beta"], [10, 20])
for name, score in pairs:
print(name, score)
这里把业务循环放在列表构造成功之后。若长度不等,函数无法返回配对列表,后面的循环也不会开始。但它会占用保存所有配对的内存,不适合直接照搬到很大的数据集。
输入迭代器自身也可能读取文件、推进游标或执行其他操作。列表构造失败不会撤销这些操作,检测长度差时还可能已经从某个输入取出了尚未产出为一对的元素。不要在异常后继续复用同一批一次性迭代器,假定它们还停在原位置;重试应从可确认的位置重新取得输入。
需要边读边处理且要求整批一致时,应让业务自己的事务或暂存机制承担回退职责。strict=True 只报告配对长度错误,不提供数据库事务、消息撤回或文件写入回滚。
旧版代码要明确支持哪类输入
若暂时仍使用 Python 3.9 或更早版本,并且接口只接收稳定的列表等有长度的序列,可在配对前比较长度:
def pair_lists(left, right):
if len(left) != len(right):
raise ValueError("input lengths differ")
return list(zip(left, right))
这段代码的前提是两份序列在检查与消费之间不会被修改。它不是生成器的通用替代方案:对普通生成器调用 len() 会失败,先转换列表又会消费输入并增加内存开销。若接口必须支持一次性迭代器,优先在兼容性允许时升级到支持严格模式的 Python,或保留已有经过边界测试的等长迭代工具。
修复后除核对异常外,还要核对成功路径的配对数量和内容。保留原代码及输入批次标识;若此前默认截断已经漏处理记录,增加严格参数不会自动补齐历史结果。重跑前核对已完成的记录,避免前缀再次被处理。回退代码若移除严格检查,也应保留其他长度校验,不能把没有异常重新当成输入完整的证据。


