关于本文
本文是通过利用生成式 AI 的自动化工作流创建的。我们确认了 Python 官方内置函数 zip 与 strict 参数的规范,并整理了使用虚拟资产数据发现行数偏差的步骤。验证状态:📘 已确认 Python 官方规范,发布的代码未进行实际运行验证
Python 的 zip 会导致数据丢失?使用 strict=True 检测行数不一致
当我们将两个列表组合在一起时,有时仅存在于其中一个列表中的数据会在没有任何警告的情况下消失。虽然 Python 的 zip 非常方便,但默认情况下它会根据较短的那一方来结束处理。这是正常的行为,并不意味着程序崩溃了。问题在于,操作人员误以为“所有数据都已组合”。在将设备编号与负责部门进行关联的工作中,即使漏掉最后一条数据也很难察觉,这就需要事后进行核对工作。本文将使用两个虚拟列表,对比默认行为与 strict=True 的差异。在实验中我们要确认的不仅是组合的数量,还有在哪个阶段能够发现不一致。此外,还将梳理事前数量检查与执行中检查的区别、长度未知的数据以及无限输入处理的相关内容。
排列了两个列表,却唯独少了一个
假设有三个设备编号,两个负责部门。部门不够的原因可能是输入错误,或者是创建列表时的条件不同。无论哪种情况,都不能擅自决定最后一个设备编号对应的部门。然而,如果使用普通的 zip,则只会返回可以组合的两条数据,多余的编号不会被输出。由于不会发生错误,仅凭正常结束的日志无法判断是否存在缺失。
这里重要的一点是,zip 不是“确认两个列表一致性的函数”,而是“按顺序将相同位置的元素组合在一起的函数”。如果我们把两个列表看作表格的列,那么第一行与第一行、第二行与第二行就会成对。如果其中一方没有第三行,就无法构成该行。默认情况下,也不会用空字符串或 None 来填充缺失的值。
用安全的数据进行初次实验
目标环境为 Python 3.10 及更高版本。strict 参数是在 Python 3.10 中引入的。无需附加包、外部通信或文件写入。请勿复制真实的员工姓名或资产台账,仅通过以下虚拟字符串进行确认。你可以在 Python 的交互式环境或新脚本中从上到下依次执行。
numbers = ["PC-01", "PC-02", "PC-03"]
teams = ["営業", "総務"]
pairs = list(zip(numbers, teams))
print("通常:", pairs)
print("組になった件数:", len(pairs))
print("元の件数:", len(numbers), len(teams))
try:
checked = list(zip(numbers, teams, strict=True))
print("厳密:", checked)
except ValueError as error:
print("不一致を検出:", type(error).__name__)
在普通的 zip 中,预计只会显示两组数据。而在附加了 strict=True 的一侧,由于输入长度不同,预计会引发 ValueError。此处的输出是基于官方规范的预期结果,并非通过运行此发布代码获得的实际测量日志。实际运行操作时,请务必仔细比对原始数量与组合后的数量。
用 try 和 except 环绕错误处理的原因是,不希望实验中途停止,而是为了显示检测到的异常类型。如果在生产处理中同样捕获异常却什么都不做直接继续,就会漏掉不一致问题。在实际工作中,应该停止处理并检查输入列表,或者将异常传递给上层处理,并将其记录为异常终止。
zip 内部发生了什么
zip 从每个输入中逐个取出下一个元素并将它们组合后返回。它并不是将两个数组从头到尾完整连接后再比较长度的处理。它是一个按顺序消耗输入的迭代器,会在需要时创建下一组。因此,即使数据源不是列表,而是逐行读取文件的处理,也可以使用它。
下图展示了处理长度不同的两个输入时的判断逻辑。图的核心是,当较短的一方结束时,普通模式与 strict 模式有何不同。
flowchart TD
A[二つの入力から次の値を取得] --> B{両方に値があるか}
B -->|はい| C[一組を返す]
C --> A
B -->|いいえ| D{strict指定}
D -->|False| E[反復を終了]
D -->|True| F{全入力が同時に終了したか}
F -->|はい| E
F -->|いいえ| G[ValueErrorで不一致を通知]
图中展示了一组一组推进处理的过程。strict=True 会在发现长度不一致的输入时通过异常发出通知。但这并不是一项保证“输入内容正确”的功能。例如,即使各有三件,如果负责部门的排列顺序错了一行,也可能会产生错误的组合。数量的一致性与对应关系的正确性是两项不同的检查。
更改一个条件并重新确认
在最初的实验中有两个部门。这次请在部门末尾添加一个“开发”,使其变为三个。numbers 保持不变。在这种状态下,预计普通的 zip 和 strict=True 都会返回三组数据。你可以借此确认 strict=True 并不是总是引发异常,而只在长度不同时才提示不一致。
numbers = ["PC-01", "PC-02", "PC-03"]
teams = ["営業", "総務", "開発"]
for number, team in zip(numbers, teams, strict=True):
print(number, team)
即使在这里故意调换第二和第三个部门,strict=True 也不会报错,因为两个列表的长度是一样的。在行对应关系很重要的数据中,必须先确定可以信任顺序的条件。如果共同的设备 ID 同时存在于两者中,那么有时用 ID 而不是位置作为键在字典或数据库中进行关联会更安全。
从这个比较中可以看出,strict=True 是一个“发现缺失的工具”,而不是一个“保证正确结合的工具”。根据较短的一方进行截断的规范有时是有意为之的。例如,在两个无限延伸的输入中,只读取所需范围的处理,要求长度一致的设计本身并不一定总是合适的。
与事先用 len 确认的方法的区别
如果两者都是列表,可以在组合之前用 len 比较条目数量。条目数量不同就不开始处理的设计很容易理解,也可以将两个输入分别有多少条记录记录到日志中。那么 strict=True 是多余的吗?并非如此。因为 zip 也可以用于列表以外的迭代器,所以存在处理事先长度不明的输入的场景。无需一次性读取所有记录就能检测出不一致,这一点很有意义。
但是,需要注意抛出异常的时机。在创建 zip 时,并不会检查所有的输入。在从迭代器中逐个取出值的过程中,才会发现长度的不同。在用 list 获取所有记录的示例中,处理过程中会抛出异常,但如果通过 for 循环将部分结果写入了外部,那么先写入的行可能会保留下来。不能因为加上了 strict=True,中途进行的更新就会自动撤销。
这种性质如果只是在屏幕上显示可能没问题,但在 CSV 输出或数据库更新中却非常重要。如果要确认所有记录的一致性后再保存,可以考虑在临时区域生成结果,验证后再确定的结构。在大数据量且无法全部加载到内存中的情况下,则需要假设中途失败的事务或重新执行的步骤。
区分失败条件
如果出现 ValueError,首先要确认各个输入的生成条件。如果日期范围、空行排除条件、是否去重等条件不同,只有一侧的条目数量会发生变化。在单纯填补不足部分之前,请先对齐什么是“一件”的计数方式。即使两个输入具有相同的条目数量,它们是否以相同的顺序排列了相同的对象也是另一个问题。
在 Python 3.9 之前,无法指定 strict 参数。如果在该环境中使用相同的代码,在检查长度之前就会拒绝该参数,因此首先要确认 Python 的版本。如果无法升级版本,对于有限列表,使用 len 进行事先确认可以作为替代方案。如果是迭代器,则需要明确指定取出方式和结束条件的单独验证处理。
如果刻意只想使用较短的一侧,也可以选择普通的 zip。不过,将“丢弃剩余部分”作为规范记录在案,并能够确认丢弃的数量会更安全。相反,如果想用缺失值填补较长一侧的剩余部分,标准库 itertools 中还有另一个名为 zip_longest 的函数。虽然它会用指定的值填补不足的位置,但需要采取措施避免将缺失混淆为有意义的值。
业务中的用法区分
如果契约规定两个列表必须具有相同的顺序和相同的条目数量,那么基本选择是使用 strict=True,并在异常时停止处理。如果是将原始数据发送到其他系统的处理,捕获异常后不应视为成功,而应记录输入条目数量和发生位置。不要直接将机密信息或个人姓名输出到日志中,仅凭条目数量和处理 ID 也有助于调查。
如果列表的顺序可能会改变,则考虑设计通过公共 ID 进行核对,而不是使用 zip。如果是允许一侧输入较短的业务,则应先决定如何处理缺失,然后选择 zip_longest 或其他结合方式。哪个是正确的取决于将较短的输入视为异常还是作为正常的缺失处理。
最后再次回想一下普通 zip 与 strict=True 的区别。前者进行到能组成元组的地方为止,后者检测长度的不一致。两者都不会检查行的含义或对应的正确性。处理正常结束这一事实本身,并不能说明所有记录都正确对应了,这就是本次实验想要掌握的重点。
