CPython gzip 模块完全指南:GzipFile、open/compress/decompress API 与 python -m gzip 命令行压缩解压
本篇指南围绕 CPython 标准库中的 gzip 模块展开,系统讲解其面向 gzip 格式(RFC 1952)的压缩与解压接口:gzip.open()、GzipFile 类、compress()/decompress() 便捷函数,以及 python -m gzip 命令行工具。文中不仅完整梳理各 API 的参数语义、默认值与版本演进,还结合 Lib/gzip.py 源码与 Lib/test/test_gzip.py 测试用例,揭示 gzip 头部写入、CRC 校验、多成员(multi-member)处理等底层机制,帮助读者写出正确、可复现且性能合理的 gzip 压缩/解压代码。
gzip 模块是什么
gzip 模块为压缩与解压 gzip 格式文件提供了一组简单的接口,使用体验与 GNU 的 gzip/gunzip 程序一致:数据压缩由标准库的 zlib 模块承担(相关文档见 Doc/library/zlib.rst)。模块对外暴露:
GzipFile类:读写 gzip 格式文件,自动完成压缩/解压,使其看起来就是一个普通的 file object;open()、compress()、decompress()三个便捷函数。
需要说明的是:gzip 属于 optional module,若你的 CPython 发行版缺少该模块(通常意味着缺少底层 zlib 支持),请查阅你的发行商提供的文档,说明见 Doc/includes/optional-module.rst。此外,gzip/gunzip 程序还能解压 compress、pack 等工具生成的其他格式,这些格式本模块并不支持。
从当前仓库的源码布局看,gzip 的核心实现位于 Lib/gzip.py,其模块级 API 定义为 __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"](Lib/gzip.py);底层流工具(GzipFile 继承的 BaseStream、_GzipReader 继承的 DecompressReader 等)则抽取到 Lib/compression/_common/_streams.py 中供 bz2、lzma 等压缩模块共享。
模块级 API 总览
| 名称 | 类型 | 用途 |
|---|---|---|
gzip.open(filename, mode='rb', compresslevel=6, encoding=None, errors=None, newline=None, *, mtime=None) |
函数 | 以二进制或文本模式打开 gzip 压缩文件,返回 file object |
gzip.GzipFile(...) |
类 | 低层二进制读写类,模拟 file object(不支持 truncate()) |
gzip.compress(data, compresslevel=6, *, mtime=0) |
函数 | 一次性压缩内存中的 bytes,返回 bytes |
gzip.decompress(data) |
函数 | 一次性解压 bytes,支持多成员 gzip 数据 |
gzip.BadGzipFile |
异常 | 无效 gzip 文件引发的异常(3.8 新增),继承自 OSError |
gzip.open() 的实现位于 Lib/gzip.py,二进制模式下它等价于 GzipFile(filename, mode, compresslevel, mtime=mtime);文本模式则创建 GzipFile 后用 io.TextIOWrapper 包装。
gzip.open():打开压缩文件的推荐入口
filename 参数
- 可以是一个实际的文件名字符串(
str或bytes); - 也可以是
os.PathLike路径对象(3.6 起支持,内部会经过os.fspath); - 还可以是已打开的 file object(3.3 起支持)——例如
io.BytesIO,此时 gzip 数据直接从该对象读写。
mode 参数
mode 支持以下取值(默认 'rb'):
- 二进制模式:
'r'、'rb'、'a'、'ab'、'w'、'wb'、'x'、'xb'('x'为独占创建,文件已存在则抛FileExistsError,3.4 起支持); - 文本模式:
'rt'、'at'、'wt'、'xt'。
源码中模式校验逻辑为:若 mode 同时含 't' 与 'b',抛出 ValueError;二进制模式下若提供了 encoding、errors 或 newline,同样抛出 ValueError(见 Lib/gzip.py),对应测试 test_bad_params(Lib/test/test_gzip.py)。
compresslevel、encoding/errors/newline 与 mtime
compresslevel:0~9 的整数,语义与GzipFile构造函数一致(1 最快但压缩率最低,9 最慢但压缩率最高,0 表示不压缩);encoding/errors/newline:仅文本模式可用,分别控制TextIOWrapper的字符编码、错误处理方式与行结束符处理;mtime:仅关键字参数(keyword-only),Unix 时间戳,会被透传给GzipFile构造函数,用于控制写入 gzip 头的 MTIME 字段。
版本演进
- 3.3:
filename支持 file object;新增文本模式与encoding、errors、newline参数; - 3.4:新增
'x'、'xb'、'xt'模式; - 3.6:
filename接受 path-like object; - 3.15:默认压缩级别由 9 下调为 6——这是大多数压缩工具的默认级别,也是速度与压缩率之间更好的折中;
- next:新增仅关键字参数
mtime,透传给GzipFile。
源码中默认压缩级别定义为常量 _COMPRESS_LEVEL_TRADEOFF = 6,并与 _COMPRESS_LEVEL_FAST = 1、_COMPRESS_LEVEL_BEST = 9 一起构成三种典型档位(Lib/gzip.py),open() 的默认参数即取自该常量。
GzipFile 类深入
构造函数参数语义
GzipFile(filename=None, mode=None, compresslevel=6, fileobj=None, mtime=None)
fileobj 与 filename 二选一(至少其一要给出非平凡值):
- 新实例基于
fileobj工作,它可以是普通文件、io.BytesIO,或任何模拟 file 的对象;fileobj=None时,内部用builtins.open(filename, mode or 'rb')打开文件(打开的内部文件对象记录在myfileobj,close 时会被一并关闭,见 Lib/gzip.py); - 当指定了
fileobj,filename仅用于写入 gzip 头部的 FNAME 字段(原始未压缩文件名)。它默认取fileobj.name(若可辨认),否则为空字符串,此时头部不含原始文件名; filename若为 path-like,会经os.fspath()规整后保存为公开的name属性。
mode:可取 'r'/'rb'/'a'/'ab'/'w'/'wb'/'x'/'xb'。默认取 fileobj.mode(若可辨认),否则为 'rb'。源码还做了两条规范化处理(Lib/gzip.py):传入含 't' 或 'U' 的 mode 直接抛 ValueError;传入不含 'b' 的 mode(如 'w')会自动补成 'wb'。注意:
- 官方文档明确提示:未来版本将不再使用
fileobj的模式,写入时最好总是显式指定mode; - 从 3.9 起,为写入而打开
GzipFile却未指定mode已被标记为弃用(源码在 3.15 快照中仍以FutureWarning形式发出警告,Lib/gzip.py)。
compresslevel:0~9 整数。写入时它除了决定 zlib 压缩强度,还会影响 gzip 头部的 XFL 字段:level 9 写 \x02(最大压缩)、level 1 写 \x04(最快压缩)、其余写 \x00(见 Lib/gzip.py 及测试 test_compresslevel_metadata,Lib/test/test_gzip.py)。
mtime:请求写入 gzip 头的 Unix 时间戳(1970-01-01 00:00:00 UTC 起的秒数):
- 传
0可生成与创建时间无关、可复现的压缩流; - 省略或传
None时使用当前时间; - 当系统当前时间落在 1970-01-01 00:00:00 UTC 至 2106-02-07 06:28:15 UTC 之外,或显式传入的
mtime超出0到2**32-1区间时,实际写入值回退为0。这一边界在源码_write_gzip_header()中以if not 0 <= mtime < 2**32: mtime = 0实现(Lib/gzip.py),测试见test_mtime_out_of_range(Lib/test/test_gzip.py)。
流式与关闭语义
GzipFile 读取时使用 io.BufferedReader,写入时使用 io.BufferedWriter(缓冲大小为 4 * io.DEFAULT_BUFFER_SIZE,见 Lib/gzip.py)。值得注意的关闭行为:
- 调用
GzipFile.close()不会关闭底层的fileobj,这是有意设计——你可能希望在压缩数据之后继续追加内容,也正因如此,可以传入一个以写模式打开的io.BytesIO作为fileobj,压缩结束后用BytesIO.getvalue()取回内存缓冲区; - 写入模式的 close 会依次:刷新缓冲、写出
compress.flush()的尾流、再写入 4 字节 CRC32 与 4 字节ISIZE(未压缩长度对2**32取模),见 Lib/gzip.py; GzipFile支持io.BufferedIOBase接口,包括迭代与with语句,仅truncate()未实现;_WriteBufferStream内部类把缓冲刷新回调转发给GzipFile,保证 CRC 与长度的统计与数据落盘同步(Lib/gzip.py)。
专有成员:peek、mode、mtime、name
| 成员 | 说明 | 版本 |
|---|---|---|
peek(n) |
读取 n 个未解压字节但不推进文件位置;返回字节数可能多于或少于请求。注意:它不改变 GzipFile 的位置,却可能改变底层 fileobj 的位置(尤其用 fileobj 构造时) |
3.2 新增 |
mode 属性 |
读返回 'rb',写返回 'wb'(3.13 之前是整数 1/2) |
3.13 变更 |
mtime 属性 |
解压时,保存最近一次读取的头部中的 MTIME 时间戳(Unix epoch 秒数的整数);读取任何头部前初始为 None |
3.1 新增 |
name 属性 |
gzip 文件在磁盘上的路径(str 或 bytes),等价于对原始输入路径调用 os.fspath() 的输出,不做任何额外规范化、解析或展开 |
3.12 起替代被移除的 filename 属性 |
mtime 属性的行为有完整测试覆盖:test_mtime 验证写入指定 mtime=123456789 后回读属性得到相同值、读取前属性为 None(Lib/test/test_gzip.py)。
GzipFile 版本历史一览
- 3.1:支持
with语句,新增mtime构造参数与mtime属性; - 3.2:支持零填充(zero-padded)文件与不可 seek(unseekable)的文件;
- 3.3:实现
io.BufferedIOBase.read1(); - 3.4:支持
'x'、'xb'模式; - 3.5:支持写入任意 bytes-like object;
read()接受None参数; - 3.6:接受 path-like object;
- 3.9:写入时不指定
mode弃用; - 3.12:移除
filename属性,改用name; - 3.15:默认压缩级别由 9 降为 6。
compress() 与 decompress():内存态的一站式压缩/解压
compress(data, compresslevel=6, *, mtime=0)
将 data 压缩为 gzip 格式的 bytes。与流式接口的关键差异在于 mtime 默认是 0(3.14 起;之前默认当前时间),从而保证输出可复现;若要恢复旧行为(用当前时间),显式传 mtime=None。
实现与优化路径(Lib/gzip.py):
- 3.11 起改为一次性整体压缩而非流式压缩,显著提速:
mtime=0时直接委托zlib.compress(data, level=..., wbits=31)(wbits=31 自动携带 gzip 头与尾),再重写头部的 MTIME 与 OS 字节; - 由于委托 zlib 产生头部,3.11~3.12 期间输出的 OS 字节可能是 zlib 提供的非 255 值;3.13 起保证 OS 字节恒为 255(unknown);
- 当
mtime越界(不在0到2**32-1)时自动回退为 0。
compresslevel 与 mtime 语义与 GzipFile 构造函数相同。测试 test_compress_mtime_default 验证默认输出可复现(两次 gzip.compress(data) 输出完全一致,Lib/test/test_gzip.py),test_compress_correct_level 则验证不同 compresslevel 的字节级正确性(Lib/test/test_gzip.py)。
decompress(data)
解压 data 并返回未压缩的 bytes。该函数能够处理多成员(multi-member)gzip 数据,即多个 gzip 块首尾拼接在一起的文件:decompress() 内部循环读取每个成员的头部、以 wbits=-MAX_WBITS 原始 deflate 解压、逐段校验 CRC32 与 ISIZE,最后拼接返回全部成员的解压结果(Lib/gzip.py)。
性能提示:当数据确定只含单个成员时,直接用 zlib.decompress(data, wbits=31) 更快。3.11 起 decompress() 也改为在内存中一次性解压各成员,取代此前的流式处理。多成员场景的测试覆盖见 test_decompress(Lib/test/test_gzip.py)。
异常与损坏数据检测
BadGzipFile(3.8 新增,继承OSError):文件头不是 gzip 魔数、压缩方法未知、头部 CRC 不匹配、解压 CRC 校验失败、长度不符等场景均会抛出;- 文件在流结束标记前截断时抛
EOFError(如_read_exact在 Lib/gzip.py 中检测); zlib.error也可能在底层解压失败时出现。
这些检测逻辑由 _read_gzip_header()(Lib/gzip.py)与 _read_eof()(Lib/gzip.py)承载:后者读出并比对存储的 CRC32 与 ISIZE(未压缩长度对 2³² 取模),随后还会吞掉 gzip 文件尾部允许存在的零填充字节。对应测试见 test_gzip_BadGzipFile_exception、test_bad_gzip_file、test_corrupted_gzip_header 与 test_read_truncated 等(Lib/test/test_gzip.py、Lib/test/test_gzip.py)。
官方使用示例(可复制运行)
读取一个压缩文件:
import gzip
with gzip.open('/home/joe/file.txt.gz', 'rb') as f:
file_content = f.read()
创建压缩的 GZIP 文件:
import gzip
content = b"Lots of content here"
with gzip.open('/home/joe/file.txt.gz', 'wb') as f:
f.write(content)
把磁盘上已有的文件整体 GZIP 压缩(流式拷贝,内存占用可控):
import gzip
import shutil
with open('/home/joe/file.txt', 'rb') as f_in:
with gzip.open('/home/joe/file.txt.gz', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
压缩内存中的二进制字符串:
import gzip
s_in = b"Lots of content here"
s_out = gzip.compress(s_in)
文本模式的实战补充
按文档约定,读取文本类压缩内容时应使用 gzip.open 的文本模式,或手动用 io.TextIOWrapper 包装 GzipFile。例如按 UTF-8 读、写压缩文本,并显式处理换行:
import gzip
# 文本写入(自动编码)
with gzip.open('/home/joe/log.txt.gz', 'wt', encoding='utf-8') as f:
f.write('line one\nline two\n')
# 文本读取(自动解码,支持通用换行)
with gzip.open('/home/joe/log.txt.gz', 'rt', encoding='utf-8', newline=None) as f:
for line in f:
print(line, end='')
注意文本模式与二进制模式的差异:文本模式下写入 str、读出 str,且 encoding/errors/newline 参数生效;二进制模式下这些参数必须省略(传入即抛 ValueError)。测试 test_text_modes、test_encoding、test_encoding_error_handler 与 test_newline 完整覆盖了上述行为(Lib/test/test_gzip.py)。
在内存中读写(BytesIO + GzipFile)
由于 GzipFile.close() 不会关闭传入的 fileobj,用 io.BytesIO 即可把压缩数据留在内存中:
import gzip, io
# 压缩到内存
buf = io.BytesIO()
with gzip.GzipFile(fileobj=buf, mode='wb', mtime=0) as gz:
gz.write(b'some payload')
compressed = buf.getvalue()
# 从内存解压
with gzip.GzipFile(fileobj=io.BytesIO(compressed), mode='rb') as gz:
print(gz.read())
这种模式常用于网络传输前的就地压缩,也是 Lib/test/test_gzip.py 中大量用例验证的核心场景(见 TestGzip.test_write_read_with_pathlike_file 等)。open() 同样接受 file object 作为第一参数,例如 gzip.open(io.BytesIO(compressed), 'rt', encoding='utf-8')(测试见 test_fileobj,Lib/test/test_gzip.py)。
命令行接口:python -m gzip
gzip 模块自带简单 CLI,用于压缩/解压文件。执行后 输入文件会被保留(这与 GNU gzip 删除输入文件的行为不同)。入口为 main()(Lib/gzip.py),模块 docstring 将其描述为"像 gzip 一样工作,但不删除输入文件"。
调用方式:
python -m gzip [--fast | --best] [file ...]
python -m gzip -d | --decompress [file ...]
python -m gzip -h | --help
命令行选项
| 选项 | 说明 |
|---|---|
file |
待处理的文件;若未指定文件,则从 sys.stdin 读取数据(CLI 中用 - 表示 stdin) |
--fast |
使用最快的压缩方法(压缩率最低),即压缩级别 1 |
--best |
使用最慢的压缩方法(压缩率最高),即压缩级别 9 |
-d, --decompress |
解压给定文件,行为类似 gunzip |
-h, --help |
显示帮助信息 |
默认压缩级别为 6(3.8 新增该 CLI 时即为默认)。--fast、--best 与 -d 三者互斥,由 argparse 的 add_mutually_exclusive_group 保证;同时传入会直接报错退出,测试见 test_compress_fast_best_are_exclusive 与 test_decompress_cannot_have_flags_compression(Lib/test/test_gzip.py)。
CLI 行为细节
- 压缩文件:为输入
foo生成foo.gz; - 解压文件:要求输入文件名以
.gz结尾,否则输出错误filename doesn't end in .gz: 'xxx'并以退出码 1 结束(Lib/gzip.py,测试见 Lib/test/test_gzip.py),解压结果写入去掉.gz后缀的同名文件; - 标准输入/输出:不指定文件(或使用
-)时压缩读 stdin 写 stdout;-d配合 stdin 时从 stdin 解压到 stdout; - 压缩流块大小为
READ_BUFFER_SIZE = 128 * 1024(Lib/gzip.py)。
命令行行为由 TestCommandLine 类系统验证,覆盖 stdin/stdout 往返、文件到文件、默认输出名、--fast/--best 互斥等(Lib/test/test_gzip.py)。
从源码看 gzip 文件格式的落盘细节
GzipFile 写入时按 RFC 1952 构造文件头,_write_gzip_header()(Lib/gzip.py)依次写入:
- 魔数
\x1f\x8b(\037\213)与压缩方法\x08(deflate); - FLG 标志字节:仅当可写入文件名时置 FNAME 位(原始文件名按 RFC 1952 要求为 Latin-1 编码,无法表示的文件名会被静默跳过;文件名若以
.gz结尾会去掉该后缀再写入头部); - MTIME:4 字节小端时间戳(越界时回退 0,见上文 mtime 规则);
- XFL:9→
\x02、1→\x04、其他→\x00; - OS 字节:恒为
\xff(255,unknown); - 可选 FNAME:以
\x00结尾的原始文件名。
测试 test_metadata 逐字节断言了头部魔数、CM=8、FLG、小端 MTIME、XFL 与 OS 字节 \xff,并核对了尾部 CRC32 与 ISIZE(Lib/test/test_gzip.py)。
读取端 _read_gzip_header() 则会解析并校验上述各字段:魔数不符抛 BadGzipFile('Not a gzipped file')、压缩方法非 8 抛 BadGzipFile('Unknown compression method');对 FLG 无标志(gzip.compress、zlib.compress 的产物)或仅 FNAME 的常见情况做提前返回优化,其余复杂标志(FEXTRA/FCOMMENT/FHCRC)则完整解析并做头部 CRC16 校验。
相关主题与进一步阅读
- 数据压缩基础层:gzip 格式的底层 deflate 压缩由
zlib模块完成,相关文档见 Doc/library/zlib.rst;zlib.compress/zlib.decompress配合wbits=31即可生成/解析单成员 gzip 流; - 同族压缩模块:
bz2与lzma模块与本模块共享底层流基础设施(compression._common._streams),如果追求更高压缩率可参考它们的文档(见 Lib/compression/ 目录); - 压缩成为瓶颈时:标准库文档建议关注采用 Intel ISA-L 加速的
python-isal兼容包(API 与gzip基本一致,可作性能替换方案),可依据实际场景自行评估,本仓库源码与测试并不涉及该第三方实现; - 参考实现:Lib/gzip.py(完整模块实现,含头部读写、CRC 校验与 CLI)、Lib/test/test_gzip.py(覆盖 API 行为、文本模式、字节级元数据、CLI 等约数十个测试用例)。
本文所描述的默认压缩级别 6、mtime 默认 0、GzipFile 相关弃用与变更,均以当前仓库(对应文档标注的 3.15 及更新版本行为)为准;如果读者运行的是更早的 Python 版本,请以对应版本的官方文档与实际行为为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00