Python ZIP 重名成员:按文件名读取之前,先确认压缩包里有几份

10-01 3阅读

压缩包不是文件名字典

收到一个资料包,程序读取 report.txt 后就开始生成摘要,看起来很自然。但 ZIP 可以保存多个名字相同的成员。界面只展示名称时,它们很容易被误看成一份;程序若先把成员列表转成以名称为键的字典,又可能在检查之前就覆盖掉一项。导入器需要先决定重复名称是否允许,再决定怎样读内容。

这个实验不解压到磁盘,只在内存中生成一个很小的 ZIP,再逐项读取。这样能把“归档成员身份”这个问题单独拿出来观察,不夹杂目录覆盖行为。示例在 Python 3.12.14 验证,使用两个同名 notes.txt,内容分别表示旧稿和新稿。

先保留成员对象,再读字节

infolist 返回归档中的 ZipInfo 对象列表,每个对象描述一个具体成员。read 除了接收文件名,也能接收这个对象。遍历时把对象直接传回 read,就能读取这一项对应的数据,不必再按一个可能重复的名字重新查找。日志可同时记录成员序号、名称和大小,以便对照导入来源。

写入第二个同名成员会发出 UserWarning。本例用局部 catch_warnings 捕获它,只为稳定演示并确认警告确实发生;它并不是一个建议全局关闭警告的生产方案。读入已有包时,即使之前无人看见写入警告,也应自行检查成员清单。

Python ZIP 重名成员:按文件名读取之前,先确认压缩包里有几份

AI概念示意图:同一个ZIP中两张名称标签相同的文档仍保存着不同内容。图片用于解释概念,不是运行截图。

from collections import Counter
from io import BytesIO
from zipfile import ZipFile
import warnings

buffer = BytesIO()
with warnings.catch_warnings(record=True) as seen:
    warnings.simplefilter("always", UserWarning)
    with ZipFile(buffer, "w") as archive:
        archive.writestr("notes.txt", "first draft")
        archive.writestr("notes.txt", "revised draft")
assert len(seen) == 1 and issubclass(seen[0].category, UserWarning)

buffer.seek(0)
with ZipFile(buffer) as archive:
    infos = archive.infolist()
    rows = [(i, info.filename, archive.read(info).decode("utf-8"))
            for i, info in enumerate(infos)]
    print(rows)
    assert [row[2] for row in rows] == ["first draft", "revised draft"]
    counts = Counter(info.filename for info in infos)
    duplicates = sorted(name for name, count in counts.items() if count > 1)
    print("duplicates:", duplicates)
    assert duplicates == ["notes.txt"]
    print("by name:", archive.read("notes.txt").decode("utf-8"))
    if duplicates:
        print("import rejected: duplicate names")
    else:
        raise AssertionError("duplicate check failed")

把观察结果和导入规则分开

第一行会列出序号零和一,名称相同,但内容分别是 first draft 与 revised draft。接着的重复清单包含 notes.txt。本次验证中按名称读取返回 revised draft;不要把这个观察结果当成业务上“最后一份必然最新”的保证,归档顺序并不能证明文档时间或可信程度。真正应依赖的是前面的精确成员读取和明确规则。

示例选择发现重复就拒绝导入。这很适合每种材料只能有一份的资料包。如果业务允许重复,则可给每项分配内部编号,连同原始成员序号保存,交给后续处理者逐项确认。不能先使用名称字典合并,再向用户声称已经检查过完整清单,那时丢失的信息已经无法从字典恢复。

重复检查的边界要与目的地一致

这里检查的是完整成员名的精确相等。目录不同、大小写不同,或者文字使用不同 Unicode 表示,都会影响比较。若以后要导入到一个大小写不敏感的文件系统,应按那个目的地的命名规则额外评估冲突;也不要只取文件名的最后一段,否则不同目录下的合法成员会被误合并。

本例使用自造的小包,read 会一次性返回完整成员内容。处理外来大包时,还要设定成员数、累计解压字节和单项读取上限,必要时用 open 分块读。重复名称检查只解决身份歧义,不证明内容安全,也不替代路径校验。把这些验收条件分别记录,失败原因才容易说明。

参考资料

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