Python LZMA 增量解压:输入已经喂完,为什么还要继续传入空字节
为了避免解压一次产生太多内容,把max_length设为四。整份压缩字节已经送入,结果却只出现前四个字符;如果此时等下一块输入,后面的内容可能一直留在解码器内部。输入是否还需要补充与输出是否全部取走,需要读取不同状态才能判断。
本例在Linux、CPython 3.12.14实跑。保存为demo.py,运行python3 demo.py。程序只压缩自己创建的十二字节内容,压缩等级为零,解码器内存上限为八MiB;排空最多八次,累计输出不超过十二字节,不读取外部压缩文件。
AI模型生成概念插图:外部卷带暂停后,腔体内部仍有图块等待分批放出;不是压缩软件或性能测量界面。
完整程序与本地结果
完整可运行程序
import lzma
payload = b"ABCDEFGHIJKL"
compressed = lzma.compress(payload, preset=0)
decoder = lzma.LZMADecompressor(memlimit=8 * 1024 * 1024)
pieces = [decoder.decompress(compressed, max_length=4)]
assert pieces[0] == b"ABCD"
assert decoder.needs_input is False and decoder.eof is False
print("first:", repr(pieces[0]))
print("needs input after first:", decoder.needs_input)
print("finished after first:", decoder.eof)
for attempt in range(8):
if decoder.eof:
break
if decoder.needs_input:
raise RuntimeError("complete test input should already be buffered")
piece = decoder.decompress(b"", max_length=4)
pieces.append(piece)
if sum(map(len, pieces)) > 12:
raise ValueError("output budget exceeded")
else:
raise RuntimeError("bounded drain did not finish")
assert b"".join(pieces) == payload
assert decoder.eof and decoder.unused_data == b""
print("all pieces:", pieces)
print("finished:", decoder.eof)
print("same payload:", b"".join(pieces) == payload)
try:
decoder.decompress(b"")
except EOFError:
print("after end: EOFError")
else:
raise AssertionError("a finished decoder must reject another call")本次实际输出(以下为结果,不是程序)
first: b'ABCD' needs input after first: False finished after first: False all pieces: [b'ABCD', b'EFGH', b'IJKL'] finished: True same payload: True after end: EOFError
四字节上限只管这次返回多少
第一次得到ABCD,needs_input与eof都为False。max_length控制本次最多交付的解压字节数,不表示只消费四字节压缩输入,也不表示整个内容应该只有四字节。解码器已经持有足够的状态,可以继续产生后面的内容。
不要把一次调用返回正常等同于整个流已经完成,也不要根据返回量小就推断输入不足。本例将完整压缩数据只送入一次,后续观察专门针对同一流内部的缓冲,不需要创建另一个解码器或重新发送原始压缩字节。
needs_input为假时先取内部待产出内容
循环在尚未eof时检查needs_input。为False就传入空bytes,并继续把本次输出限制为四;最终pieces包含ABCD、EFGH、IJKL三块,拼接后与原始负载逐字节相同。空输入在这里是继续处理已持有内容的调用方式。
如果本例在未结束时出现needs_input=True,就抛出RuntimeError,因为完整测试输入早已提供。真实分块输入遇到这个状态时,可以读取下一块;若外部输入也已结束,却仍未达到eof,就不能报告完整成功,应按截断或协议失败处理。
流结束以后不再调用同一个解码器
达到结束标记后,eof变为True,循环停止。程序另外故意再调用一次,确认得到EOFError。这个反例说明“传入空字节”不是可无限重复的刷新操作;每次继续之前都要确认当前解码器尚未完成。
最后的unused_data为空,因为实验只构造一个压缩流,没有附带尾部数据。它描述结束标记之后的输入,与此前needs_input=False时内部仍能产出内容不是同一状态。这里不以拼接多个压缩成员作为续读方案。
单次输出限制不能替代总预算
即使每次只返回四字节,无限多次调用仍可能积累很大的结果。示例因此另算pieces的累计长度,并设置明确的循环次数上限。真实实现可以边产出边交给消费者,同时检查总字节数,不能只依赖每块大小限制。
这里累计计算反复扫描极短列表,便于读者直接看清预算。处理很多块时更适合维护独立累计计数,并在接纳下一块之前检查剩余额度。块数、总输出量与输入总量各控制不同问题,应按业务约定分别记录。
解码器内存与产出内容也属于不同额度
memlimit约束解码所需的内部内存,不会自动限制应用把返回字节累计成多大的列表。反过来,输出总量很小也不代表解码字典一定很小。本例显式选择低等级压缩并给出足够的解码上限,让资源设置与固定测试相匹配。
对外部数据还需要限定输入大小、处理耗时及输出目的地。本文没有构造或处理危险的大压缩包,不能把通过这个小实验解释成已经完成全面资源防护。它证明的是在明确预算内如何正确推进一个解码器。
用状态变化定位停在哪里
排查“只解出开头”时,记录本次输入字节数、本次输出量、累计输出与两个状态,通常比反复加大读取块更有效。输入已全送入而needs_input=False,说明下一步可以继续排空;外部结束而仍需要输入,则是另一类问题。
验收需要同时核对最终内容和eof,不能只看输出长度恰好等于预期。示例还断言没有意外尾部数据,且结束后调用确实失败。把内容、结束与预算三类条件放在一起,才不会把一次成功调用误当作完整交付。
参考资料
资料核验日期:2026年10月2日。以上输出来自固定输入的本地实跑,程序退出码为0。


