Python zip_longest 的补位值:读到 None,怎样分清原值为空还是这一格根本不存在
比较两列记录时,希望较长一侧的尾部也出现在报告里,于是把zip换成zip_longest。结果里出现None,看起来像“没有数据”,但原列表本来就允许None。默认补位把原值为空与该位置不存在表示成同一个对象,报告很容易把两种情况混在一起。
本例在Linux、CPython 3.12.14实跑,只操作自建短列表。保存为demo.py,运行python3 demo.py。左列有三项,右列有两项,第二项都是真实None;程序先观察默认补位,再换成唯一哨兵并生成差异清单,最后覆盖空输入。
AI模型生成概念插图:实际存在的空心标记与条纹缺位标记使用不同形状;不是表格界面或真实数据。
完整程序与本地结果
完整可运行程序
from itertools import zip_longest
left = ["A", None, "C"]
right = ["A", None]
ambiguous = list(zip_longest(left, right))
assert ambiguous == [("A", "A"), (None, None), ("C", None)]
print("default fill:", ambiguous)
missing = object()
pairs = list(zip_longest(left, right, fillvalue=missing))
assert pairs[1][1] is None
assert pairs[2][1] is missing
def describe(value):
return "missing" if value is missing else repr(value)
for index, (a, b) in enumerate(pairs, start=1):
print(f"row {index}: left={describe(a)}, right={describe(b)}")
changes = []
for index, (a, b) in enumerate(pairs, start=1):
if a is missing:
changes.append((index, "right-only"))
elif b is missing:
changes.append((index, "left-only"))
elif a != b:
changes.append((index, "different"))
assert changes == [(3, "left-only")]
print("differences:", changes)
assert list(zip_longest([], [], fillvalue=missing)) == []
one_side = list(zip_longest([], [None], fillvalue=missing))
assert one_side[0][0] is missing and one_side[0][1] is None
print("empty-side checks: passed")本次实际输出(以下为结果,不是程序)
default fill: [('A', 'A'), (None, None), ('C', None)]
row 1: left='A', right='A'
row 2: left=None, right=None
row 3: left='C', right=missing
differences: [(3, 'left-only')]
empty-side checks: passed较短一列结束后,迭代仍然继续
default fill包含三组配对,最后一组是C与None。zip_longest会继续到最长输入结束,并为已耗尽的一侧提供fillvalue,默认就是None。这里允许长度不同,目的是完整观察尾部;它没有将不等长视为应当报错的条件。
第二组的两个None来自输入,第三组右边的None来自填充。只看最终元组,已经没有足够信息判断第三项是否原本存在。如果来源允许这个值,就应在配对时保留区别,不能等转换成报告后再靠猜测补回。
用身份检查保留位置是否存在
missing等于一次object调用返回的独立对象,并作为fillvalue传入。它不属于本例允许的字符串或None输入,因而可以用is精确识别补位。程序断言第二行右值仍是None,第三行右值则是这个哨兵,两个状态不再合并。
不要在每次判断时重新写object,因为那会创建另一个身份;也不要仅选一个看起来罕见的字符串,例如“缺失”,再假设数据永远不会包含它。哨兵应由当前逻辑持有并复用,输入范围与内部标记的边界要清楚。
先判断存在性,再比较实际值
差异循环先检查左边是否缺位,再检查右边,只有两边都存在时才比较内容。因此第二行None与None被判定相同,第三行被记录为left-only。这个先后顺序能保住“有一条空值记录”与“少了一条记录”的实际差异。
describe只负责演示显示,把哨兵写成missing,把真实None按repr输出。正式结构化报告可以使用独立的存在性字段,而不是把字符串missing重新塞回数据列。展示文案与内部数据标记分开,也更方便下游继续处理。
同时为空与单侧为空都有明确结果
两边为空时结果也是空列表,不会额外制造一组哨兵。只有右边含一项None时,左边才补哨兵,右边继续保留真实None。末尾两个断言检查这些极小输入,避免差异生成器只在“双方都至少有一项”时才成立。
如果两边都已有相同长度,填充值不会出现,但这不能证明方案已经正确处理长度偏差。验收应至少包含左长、右长、等长与空列,且让None出现在真实数据中;否则把哨兵误写回None仍可能让全部样本通过。
逐位置配对不等于按编号合并
本文按第几项对应第几项比较,适合两列已经有相同顺序约定的材料。若一侧中间删除一条记录,之后所有位置都会错位。此时应先按稳定业务键对齐,再比较内容,不能指望zip_longest自动发现哪一条是被删除的记录。
生成逐行审阅清单时,可以同时保留位置编号与业务标识,让用户查回原始来源。长度差异被保留下来,只完成了报告完整性的一部分;输入顺序、重复标识与内容比较规则仍属于应用自己需要确定的条件。
哨兵只在当前处理范围内有身份意义
普通object哨兵不适合直接作为跨进程或JSON接口里的协议值。序列化之前,应把它转换成接口明确支持的缺失状态;反序列化后,也不能假定另一份对象与本地哨兵拥有同一身份。本文使用同一进程的一段短流程,边界是可见的。
zip_longest仍是惰性迭代器。如果其中一路无限长,整体不会自然结束,调用方必须限定读取数量或时间。本例先转列表是因为输入长度固定且很小;面对真实大输入,应逐项处理并保留完整性策略,避免为比较而无界占用内存。
参考资料
资料核验日期:2026年10月2日。以上输出来自固定输入的本地实跑,程序退出码为0。


