登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python zipfile.ZipFile.open 怎样按二进制流读取压缩成员

来源:17golang原创

时间:2026-11-02 13:04:08 340浏览 收藏

如果只想读取 ZIP 里的一个文件,zipfile.ZipFile.open() 比先解压到临时目录更直接:它按成员名或 ZipInfo 定位对象,返回一个可读取的二进制文件式对象。最稳妥的写法是把外层 ZipFile 和内层返回对象都放进 with,读取时按块消费,文本内容再单独解码。

官方文档:https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile.open

记住三层边界:ZipFile 管理压缩包,ZipFile.open() 负责定位成员,返回的 ZipExtFile 负责提供二进制读取接口。不要把它直接当成已经解码好的字符串。

先区分 ZipFile、成员名和 ZipExtFile 的职责

ZipFile 是压缩包容器;name 参数是包内成员名,也可以传入一个 ZipInfo 对象;open() 返回的对象则是本次读取的流。默认读取模式是 r,读取对象按二进制文件接口工作。

ZipFile、成员名、ZipInfo、ZipFile.open 与 ZipExtFile 的静态职责关系说明图
图1:静态结构说明图,区分 ZipFile、成员定位与 ZipExtFile 的职责边界。

这个划分很重要:包对象负责打开和关闭整个归档,流对象负责读取当前成员。文章中的“读取某个文件”实际上是先完成成员定位,再从流对象取得字节。

用 mode='r' 按块读取成员二进制流

对于图片、证书、压缩配置或未知编码的数据,先保留字节最安全。下面的示例只读取指定成员,不把完整压缩包一次性展开到磁盘:

from pathlib import Path
from zipfile import ZipFile

def read_member(zip_path: Path, member_name: str) -> bytes:
    # 外层 with 负责关闭 ZIP 容器,内层 with 负责关闭成员流。
    with ZipFile(zip_path, mode="r") as archive:
        with archive.open(member_name, mode="r") as member:
            chunks: list[bytes] = []
            while True:
                # 分块读取可以避免把大成员的全部内容一次压进内存。
                chunk = member.read(64 * 1024)
                if not chunk:
                    break
                chunks.append(chunk)
            return b"".join(chunks)

这里的 mode="r" 是读取压缩成员,返回对象支持 read()、readline()、迭代和定位等文件式操作。示例为了展示返回值使用了字节列表;如果文件可能很大,更适合在循环中直接把每个块写入另一个二进制流,而不是最后再拼接。

成员名不稳定时改用 ZipInfo

同一个 ZIP 中可能出现重复成员名。此时直接传字符串会让“按名称打开”变得不明确,可以先从 infolist() 得到具体的 ZipInfo,再把对象传给 open():

from zipfile import ZipFile, ZipInfo

def read_first_report(zip_path: str) -> bytes:
    # 用 ZipInfo 保留具体条目,而不是只依赖可能重复的名称。
    with ZipFile(zip_path, mode="r") as archive:
        report: ZipInfo | None = next(
            (item for item in archive.infolist() if item.filename.endswith("report.bin")),
            None,
        )
        if report is None:
            # 缺少目标成员时,把问题转换成调用方可处理的异常。
            raise FileNotFoundError("ZIP 中没有 report.bin")
        with archive.open(report, mode="r") as member:
            # read() 只负责取得字节,解码或解析应由上一层决定。
            return member.read()

如果成员名不存在,定位阶段通常会抛出 KeyError;如果业务希望统一错误语义,可以像示例一样先列举成员并主动抛出更明确的异常。这里仍然没有声称“读取成功”或展示运行输出,代码只是说明调用关系。

把读取、解码和资源关闭分开

ZIP 成员是二进制流,文本文件要在流外再加一层解码。不要用字符串方法替代字节读取,也不要假设所有成员都是 UTF-8:

ZipExtFile 二进制读取、io.TextIOWrapper 文本解码与 with 资源边界说明图
图2:静态边界说明图,展示二进制流、文本解码与关闭动作的职责关系。
import io
from zipfile import ZipFile

def read_text_member(zip_path: str, member_name: str, encoding: str = "utf-8") -> str:
    # ZipFile.open() 先给出二进制流,再由 TextIOWrapper 负责字符解码。
    with ZipFile(zip_path, mode="r") as archive:
        with archive.open(member_name, mode="r") as binary_member:
            with io.TextIOWrapper(binary_member, encoding=encoding) as text_member:
                # 文本层只消费 str,编码错误由调用方按业务策略处理。
                return text_member.read()

关闭顺序由嵌套的 with 管理,调用方不必手写多个 close()。外层 ZipFile 关闭后,不应继续使用成员流;官方文档把这种情况定义为 ValueError。这也是把读取逻辑放在上下文块内部的原因。

发布前记住这几个边界

场景建议原因
未知内容类型先按 bytes 读取避免错误假设字符编码
成员名可能重复使用 ZipInfo保留具体归档条目
成员很大循环分块消费控制单次内存占用
文本成员显式指定编码再解码把数据读取与字符转换分离
资源生命周期嵌套使用 with避免容器或流泄漏

相关问题

为什么不直接用 zipfile.Path?

zipfile.Path 更适合把压缩包成员组织成路径式对象;本篇关注的是明确调用 ZipFile.open() 后得到二进制流,并控制读取、解码和关闭边界。两者解决的问题不同。

读取 ZIP 文本时能不能直接得到字符串?

ZipFile.open() 的读取对象是二进制文件式对象。应先读取字节,再使用 io.TextIOWrapper 或 bytes.decode() 按已知编码转换,不能把默认编码当成文件事实。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>