Python gzip 串接成员:两段压缩数据放在一起,为什么一种解压只拿到前半段
两段日志各自压缩后直接串接,gzip.decompress 可以还原两段文本;换成 zlib.decompress 并指定 gzip 格式,却只得到第一段。输入未必损坏:gzip 文件可以包含多个连续成员,而一次 zlib 解压处理的是一个压缩流。
下面创建两段很小的固定内容,在内存中压缩、串接并解压。保存为 demo.py,运行 python demo.py。这里先掌握成员边界,再决定调用方是否允许多成员输入。
AI模型生成概念示意:上路打开两个相连的压缩成员,下路只打开首个并留下余段;不是压缩工具实测截图。
import gzip
import zlib
first = gzip.compress(b"alpha\n", mtime=0)
second = gzip.compress(b"beta\n", mtime=0)
joined = first + second
all_data = gzip.decompress(joined)
one_member = zlib.decompress(joined, wbits=31)
print("gzip:", repr(all_data))
print("zlib:", repr(one_member))
assert all_data == b"alpha\nbeta\n"
assert one_member == b"alpha\n"
decoder = zlib.decompressobj(wbits=31)
head = decoder.decompress(joined)
print("first complete:", decoder.eof)
print("remainder is second member:", decoder.unused_data == second)
print("unconsumed tail:", len(decoder.unconsumed_tail))
assert head == b"alpha\n" and decoder.eof
assert decoder.unused_data == second
assert decoder.unconsumed_tail == b""
tail = gzip.decompress(decoder.unused_data)
assert head + tail == all_data
print("remaining payload:", repr(tail))
print("runtime zlib:", zlib.ZLIB_RUNTIME_VERSION)成员结束,不等于所有输入结束
前两行分别显示 gzip: b'alpha\nbeta\n' 和 zlib: b'alpha\n'。每次 gzip.compress 都产生一份完整成员,包含自己的头、压缩数据和尾部。直接串接保留这两份边界,并不是把两个数据块塞进同一个尚未结束的流。
wbits=31 让 zlib 解码器识别 gzip 包装。它不会因此自动循环处理所有后续成员。若只检查调用没有抛异常,却不核对解压结果和剩余输入,就可能把前半段误当成完整日志。协议只允许单成员时,更应拒绝意外余段,不能静默忽略。
用两个状态区分剩余数据
first complete: True 表示第一个流已经正常结束;remainder is second member: True 则说明后面的压缩字节仍保存在 unused_data。它们不是坏字节,也不是已经解压好的 beta,而是完整的第二个成员。最后用新的解压入口处理它,得到 remaining payload: b'beta\n'。
unconsumed tail: 0 讲的是另一件事:本例没有设置输出长度限制,因而没有由于输出上限留下尚未处理的当前流输入。unconsumed_tail 和 unused_data 的意义不同,不能看到一个为空,就认定整个输入已消费完毕。
输出最后记录本机 zlib 运行库版本,便于复现实验;本次为 1.3.2。代码只断言内容与成员关系,没有要求压缩字节在其他版本完全一致。跨环境验收应分别检查格式兼容、解压内容与余段处理策略。
gzip.decompress 一次返回全部解压字节,适合这里的小数据。外部压缩包可能展开得很大,实际管线需要累计输出预算与输入规模限制。若逐成员处理,也要限制成员数量,并验证每次确实推进,避免把这个两成员示例直接扩成没有边界的循环。
资料核对日期:2026年10月2日。代码在本地 Python 3.12.14 实际运行并通过断言;结果对应文中固定输入。


