☰
KBEngine 内置 Python 的 lzma 模块详解:LZMA 压缩 API 全指南
2026/10/17 2:36:20 网站建设 项目流程
  • 游戏开发
  • 后端

【免费下载链接】kbengine

A MMOG engine of server.

项目地址:https://gitcode.com/gh_mirrors/kb/kbengine
点击查看免费下载

导读:本文基于 KBEngine 仓库内置 Python 发行版中lzma模块的官方文档(kbe/src/lib/python/Doc/library/lzma.rst)编写,系统讲解使用 LZMA 算法进行压缩/解压的完整 API,包括文件读写、内存一次性压缩、增量式压缩解压、三种容器格式(.xz、.lzma、RAW)与自定义滤镜链的实战用法。读完本文,你将掌握在 KBEngine 服务器脚本(kbe/res/scripts 下的 Python 资产)与内置 Python 环境中,如何借助lzma模块实现高压缩比的存档、日志与数据传输,并能读懂该模块在 Lib/lzma.py 与 Modules/_lzmamodule.c 中的底层实现。

一、模块定位:lzma 在 KBEngine 内置 Python 中的角色

lzma模块是 Python 标准库对liblzma压缩库的封装,提供了用于 LZMA 算法压缩与解压数据的类与便捷函数,并支持xz命令行工具所使用的.xz与旧版.lzma文件格式,以及原始(RAW)压缩流。该模块自 Python 3.3 引入(versionadded:: 3.3)。

在 KBEngine 仓库中,这个模块属于内置 Python 发行版的完整标准库一部分:

  • 模块的 Python 层实现位于 Lib/lzma.py(共 347 行),其文件头注释明确写着 "Interface to the liblzma compression library";
  • 底层的 C 扩展_lzma实现在 Modules/_lzmamodule.c,直接#include <lzma.h>并调用liblzma的 C API;
  • 模块构建时会在 setup.py 中检测系统是否提供lzma库:if self.compiler.find_library_file(lib_dirs, 'lzma'): exts.append(Extension('_lzma', ['_lzmamodule.c'], libraries=['lzma'])),若找不到liblzma,则把_lzma加入missing列表,此时该模块不可用——这提示我们在目标服务器上运行依赖lzma的脚本前,需要确认系统已安装liblzma开发库。

lzma模块提供的接口与bz2模块非常相似,但有一个重要区别:LZMAFile是非线程安全的(bz2.BZ2File则是线程安全的),因此如果需要在多线程中共享同一个LZMAFile实例,必须用锁保护。这一点在 Lib/lzma.py 和官方文档中均有明确说明。

模块通过__all__导出的完整 API 清单见 Lib/lzma.py,包括:

  • 格式常量:FORMAT_AUTO、FORMAT_XZ、FORMAT_ALONE、FORMAT_RAW;
  • 校验常量:CHECK_NONE、CHECK_CRC32、CHECK_CRC64、CHECK_SHA256、CHECK_ID_MAX、CHECK_UNKNOWN;
  • 滤镜常量:FILTER_LZMA1、FILTER_LZMA2、FILTER_DELTA以及FILTER_X86、FILTER_IA64、FILTER_ARM、FILTER_ARMTHUMB、FILTER_POWERPC、FILTER_SPARC等 BCJ(Branch-Call-Jump)滤镜;
  • 匹配查找器与模式常量:MF_HC3、MF_HC4、MF_BT2、MF_BT3、MF_BT4、MODE_FAST、MODE_NORMAL、PRESET_DEFAULT、PRESET_EXTREME;
  • 类与函数:LZMACompressor、LZMADecompressor、LZMAFile、LZMAError、open、compress、decompress、is_check_supported。

这些常量在 C 层由 Modules/_lzmamodule.c 的模块初始化函数PyInit__lzma通过PyModule_AddIntMacro/ADD_INT_PREFIX_MACRO逐一注入,并直接映射为liblzma头文件中的同名宏(如LZMA_CHECK_NONE、LZMA_FILTER_LZMA2等)。由于部分常量(如CHECK_SHA256)超过 32 位整型宽度,C 层专门提供了module_add_int_constant函数用PyLong_FromLongLong注入,以保证在 32 位long平台上取值正确。

二、异常与基础使用:LZMAError 与第一个压缩例子

2.1 LZMAError 异常

lzma.LZMAError在压缩、解压或初始化压缩器/解压器状态发生错误时抛出。在 C 层,它由 Modules/_lzmamodule.c 通过PyErr_NewExceptionWithDoc("_lzma.LZMAError", "Call to liblzma failed.", ...)创建,其异常消息直接来源于对liblzma调用返回码的映射(catch_lzma_error函数中可见诸如 "Unsupported integrity check"、"Compression failed" 等错误文案)。

2.2 快速上手:一次性压缩与解压

最简单的用法是调用lzma.compress(data)与lzma.decompress(data)两个便捷函数:

import lzma # 一次性压缩:默认使用 .xz 格式 data_in = b"Insert Data Here" data_out = lzma.compress(data_in) # 一次性解压:默认 FORMAT_AUTO,可自动识别 .xz 与 .lzma data_back = lzma.decompress(data_out) assert data_back == data_in

从 Lib/lzma.py 的实现可以看到,compress()本质上是"创建一个LZMACompressor,调用一次compress(data)再调用flush(),然后拼接结果"的语法糖;而 decompress() 内部则是一个循环:反复创建LZMADecompressor解压当前流,把解出的数据累积到结果列表,再用unused_data继续处理后续流,直到没有剩余数据为止。

三、读写压缩文件:lzma.open 与 LZMAFile

3.1 lzma.open:二进制/文本双模式文件接口

函数签名:

lzma.open(filename, mode="rb", *, format=None, check=-1, preset=None, filters=None, encoding=None, errors=None, newline=None)

要点说明:

  • filename可以是真实的文件名(str、bytes或 path-like 对象),此时会打开命名文件;也可以是已打开的文件对象,此时直接对其读写。
  • mode在二进制模式下可以是"r"、"rb"、"w"、"wb"、"x"、"xb"、"a"、"ab"(默认"rb");文本模式则使用"rt"、"wt"、"xt"、"at"。
  • 读模式下,format与filters的含义与LZMADecompressor一致,不应使用check与preset;
  • 写模式下,format、check、preset、filters的含义与LZMACompressor一致。
  • 二进制模式下该函数等价于LZMAFile(filename, mode, ...)构造器,此时不能提供encoding、errors、newline参数;文本模式下,则会创建一个LZMAFile对象,再用io.TextIOWrapper按指定的编码、错误处理与换行方式包装。
  • 历史变更:3.4 加入"x"/"xb"/"xt"独占创建模式;3.6 起接受 path-like 对象。

这些约束在源码中有严格校验:见 Lib/lzma.py,二进制模式传入encoding/errors/newline会抛出ValueError。

3.2 LZMAFile:二进制文件接口

类签名:

class lzma.LZMAFile(filename=None, mode="r", *, format=None, check=-1, preset=None, filters=None)

要点说明:

  • mode可为"r"(默认,读)、"w"(覆盖写)、"x"(独占创建)、"a"(追加),等价写法"rb"、"wb"、"xb"、"ab"。
  • 若filename传入的是文件对象而非文件名,则"w"模式不会截断文件,行为等价于"a"追加。
  • 读模式打开时,输入文件可以是多个独立压缩流的拼接,这些流会被透明地当作单一逻辑流解码。
  • 读模式时format/filters与LZMADecompressor语义相同,不使用check/preset;写模式时与LZMACompressor语义相同。
  • LZMAFile支持io.BufferedIOBase定义的全部成员,但detach()与truncate()除外;支持迭代与with语句。
  • 额外方法peek(size=-1):返回缓冲中的数据而不推进文件位置;除非到达 EOF,否则至少返回一个字节,具体返回字节数未指定(size参数被忽略)。注意:调用peek不会改变LZMAFile的文件位置,但可能改变底层文件对象的位置(当LZMAFile是用文件对象构造时)。

从实现看,LZMAFile 继承自_compression.BaseStream,构造时:

  • 读模式:用_compression.DecompressReader把LZMADecompressor适配成 RawIOBase 读取器,再包一层io.BufferedReader;
  • 写模式:直接构造LZMACompressor并在 write() 中把压缩输出写到底层文件对象;
  • close() 时会把压缩器flush()的结果写入文件再关闭底层文件(若LZMAFile负责关闭它)。

_compression模块(Lib/_compression.py)同时被gzip、lzma、bz2三个模块共享,其中的DecompressReader为处理多流拼接与向后seek()会为每个流创建新的解压器对象。

3.3 官方示例:读写压缩文件

读取压缩文件:

import lzma with lzma.open("file.xz") as f: file_content = f.read()

创建压缩文件:

import lzma data = b"Insert Data Here" with lzma.open("file.xz", "w") as f: f.write(data)

向已打开的文件中写入压缩数据(混写场景):

import lzma with open("file.xz", "wb") as f: f.write(b"This data will not be compressed\n") with lzma.open(f, "w") as lzf: lzf.write(b"This *will* be compressed\n") f.write(b"Not compressed\n")

3.4 文本模式的编码控制

文本模式("rt"/"wt"等)下,encoding、errors、newline三个参数直接透传给io.TextIOWrapper。仓库测试 Lib/test/test_lzma.py 中的test_encoding、test_encoding_error_handler、test_newline等用例验证了这些参数的行为,例如可以指定encoding="utf-8"、errors="replace"或自定义换行newline="\r\n"来读写文本型压缩存档。

四、内存中的压缩与解压:增量式 API 与一次性 API

4.1 LZMACompressor:增量压缩

类签名:

class lzma.LZMACompressor(format=FORMAT_XZ, check=-1, preset=None, filters=None)
  • format:指定容器格式。可选值:
    • FORMAT_XZ:.xz容器格式,默认值;
    • FORMAT_ALONE:旧版.lzma容器格式,功能更受限——不支持完整性校验,也不支持多滤镜;
    • FORMAT_RAW:不使用任何容器格式的原始数据流,不支持完整性校验,且压缩与解压都必须指定自定义滤镜链;此外,以 RAW 压缩的数据不能用FORMAT_AUTO解压。
  • check:指定压缩数据中携带的完整性校验类型,用于解压时确认数据未被损坏:
    • CHECK_NONE:无校验,是FORMAT_ALONE与FORMAT_RAW的默认(也是唯一)可选值;
    • CHECK_CRC32:32 位循环冗余校验;
    • CHECK_CRC64:64 位循环冗余校验,是FORMAT_XZ的默认值;
    • CHECK_SHA256:256 位安全散列算法。
    • 若指定的校验类型不受支持,抛出LZMAError。
  • 压缩设置二选一:通过preset指定压缩级别,或通过filters指定自定义滤镜链。preset应为 0~9 的整数,可选与PRESET_EXTREME按位或;两者都未提供时默认使用PRESET_DEFAULT(即级别 6)。级别越高输出越小,但压缩越慢。
    • 注意:更高预设不仅更耗 CPU,还需要更多内存(解压时同样需要更多内存)。例如预设 9 时,一个LZMACompressor对象的开销可高达 800 MiB,因此通常建议使用默认预设。
  • 方法:
    • compress(data):压缩data(bytes对象),返回包含至少部分输入压缩结果的bytes对象。部分data可能被内部缓冲,供后续compress()与flush()调用使用;返回数据应与之前所有compress()调用的输出拼接。
    • flush():结束压缩过程,返回压缩器内部缓冲中的剩余数据。调用后压缩器不能再使用。

官方增量压缩示例:

import lzma lzc = lzma.LZMACompressor() out1 = lzc.compress(b"Some data\n") out2 = lzc.compress(b"Another piece of data\n") out3 = lzc.compress(b"Even more data\n") out4 = lzc.flush() # 拼接所有部分结果: result = b"".join([out1, out2, out3, out4])

C 层实现要点(Modules/_lzmamodule.c):

  • 使用PyArg_ParseTupleAndKeywords解析参数;若format != FORMAT_XZ且check != -1且check != LZMA_CHECK_NONE,抛出ValueError("Integrity checks are only supported by FORMAT_XZ");
  • 若同时提供preset与filters,抛出ValueError("Cannot specify both preset and filter chain");
  • 每个压缩器对象内部含有一把PyThread_type_lock锁(ACQUIRE_LOCK/RELEASE_LOCK宏,见 Modules/_lzmamodule.c),说明 C 层为并发安全做了锁保护,但文档仍然声明高层LZMAFile非线程安全,需要调用方自行加锁;
  • 按格式分别调用Compressor_init_xz(lzma_easy_encoder/lzma_stream_encoder)、Compressor_init_alone(lzma_alone_encoder)或Compressor_init_raw(lzma_raw_encoder+ 滤镜链解析);
  • preset与filters均未指定时,preset的 C 层默认值为LZMA_PRESET_DEFAULT(Modules/_lzmamodule.c)。

4.2 LZMADecompressor:增量解压

类签名:

class lzma.LZMADecompressor(format=FORMAT_AUTO, memlimit=None, filters=None)
  • format:默认FORMAT_AUTO,可自动解压.xz与.lzma文件;其他可选值FORMAT_XZ、FORMAT_ALONE、FORMAT_RAW。
  • memlimit:以字节为单位限制解压器可使用的内存上限。指定后,若在给定内存限制内无法解压输入,解压将抛出LZMAError失败。
  • filters:指定创建该流时所用的滤镜链。format为FORMAT_RAW时必须提供,其他格式不应使用。
  • 重要限制:该类不像decompress()与LZMAFile那样透明处理包含多个压缩流的输入;要解压多流输入,必须为每个流创建新的解压器。
  • 属性:
    • check:输入流使用的完整性校验 ID;在解码出足够输入以确定校验类型之前,可能是CHECK_UNKNOWN。
    • eof:是否已到达流结束标记。
    • unused_data:压缩流结束标记之后发现的数据;流结束前为b""。
    • needs_input:若decompress()在需要新输入前还能产出更多解压数据,则为False。
  • 方法decompress(data, max_length=-1):
    • 解压data(bytes-like 对象),返回解压后的字节;部分数据可能被内部缓冲供后续调用使用,返回数据应与此前所有调用结果拼接。
    • max_length非负时,最多返回max_length字节;若达到该限制且还能继续产出输出,needs_input会置为False,此时下次调用decompress()可传b''以取得更多输出。
    • 若全部输入数据都已解压并返回(无论因小于max_length还是max_length为负),needs_input置为True。
    • 到达流结束标记后再尝试解压会抛出EOFError;流结束之后发现的数据被忽略并存入unused_data。

C 层实现要点(Modules/_lzmamodule.c):

  • 使用LZMA_TELL_ANY_CHECK | LZMA_TELL_NO_CHECK解码标志,以便在输入未携带校验或校验未知时仍能获知信息(对应check属性);
  • memlimit未指定时默认UINT64_MAX;FORMAT_RAW与memlimit同时指定会抛ValueError("Cannot specify memory limit with FORMAT_RAW");
  • FORMAT_RAW未提供filters抛ValueError("Must specify filters for FORMAT_RAW");其他格式提供filters抛ValueError("Cannot specify filters except with FORMAT_RAW");
  • 按格式分别调用lzma_auto_decoder、lzma_stream_decoder、lzma_alone_decoder或lzma_raw_decoder;
  • check初始化为LZMA_CHECK_UNKNOWN,needs_input初始化为 1,unused_data初始化为空bytes;
  • decompress_buf(Modules/_lzmamodule.c)中,每次lzma_code返回LZMA_GET_CHECK或LZMA_NO_CHECK时通过lzma_get_check更新check属性,返回LZMA_STREAM_END时置eof = 1。

4.3 一次性便捷函数

lzma.compress(data, format=FORMAT_XZ, check=-1, preset=None, filters=None) lzma.decompress(data, format=FORMAT_AUTO, memlimit=None, filters=None)
  • compress(data):压缩data(bytes),返回压缩结果bytes;参数含义与LZMACompressor相同。
  • decompress(data):解压data(bytes),返回解压结果;若data是多个独立压缩流的拼接,则全部解压并返回结果的拼接;参数含义与LZMADecompressor相同。

一次性解压多流、以及鲁棒处理压缩流之后的非 LZMA 垃圾数据,均有对应的仓库测试用例:test_decompress_multistream 验证lzma.decompress(COMPRESSED_XZ + COMPRESSED_ALONE)能完整还原两段输入;test_decompress_trailing_junk与test_decompress_multistream_trailing_junk验证压缩流后附带垃圾字节时仍能正确解压出有效部分。

五、自定义滤镜链:精细控制压缩行为

5.1 滤镜链规格(Filter Chain Specifier)

滤镜链规格是字典的序列,每个字典包含单个滤镜的 ID 与选项。每个字典必须包含"id"键,可包含额外的滤镜相关选项键。合法的滤镜 ID 如下:

  • 压缩滤镜:
    • FILTER_LZMA1(用于FORMAT_ALONE)
    • FILTER_LZMA2(用于FORMAT_XZ与FORMAT_RAW)
  • Delta 滤镜:
    • FILTER_DELTA
  • BCJ(分支-调用-跳转)滤镜:
    • FILTER_X86
    • FILTER_IA64
    • FILTER_ARM
    • FILTER_ARMTHUMB
    • FILTER_POWERPC
    • FILTER_SPARC

约束:滤镜链最多包含 4 个滤镜,且不能为空;链中最后一个滤镜必须是压缩滤镜,其余滤镜必须是 delta 或 BCJ 滤镜。

5.2 压缩滤镜选项

压缩滤镜支持以下选项(作为字典中的额外键指定):

  • preset:压缩预设,为未显式指定的选项提供默认值来源;
  • dict_size:字典大小(字节),应在 4 KiB 到 1.5 GiB(含)之间;
  • lc:字面上下文位数(literal context bits);
  • lp:字面位置位数(literal position bits),要求lc + lp之和不超过 4;
  • pb:位置位数(position bits),最大为 4;
  • mode:MODE_FAST或MODE_NORMAL;
  • nice_len:匹配的"理想长度",应不超过 273;
  • mf:匹配查找器,可选MF_HC3、MF_HC4、MF_BT2、MF_BT3、MF_BT4;
  • depth:匹配查找器的最大搜索深度,0(默认)表示根据其他滤镜选项自动选择。

5.3 Delta 与 BCJ 滤镜选项

  • Delta 滤镜存储字节之间的差值,在某些场景下为压缩器制造更重复的输入。仅支持一个选项dist:被减字节之间的距离,默认 1,即取相邻字节的差值。
  • BCJ 滤镜用于机器码:将代码中的相对分支、调用与跳转转换为绝对寻址,从而增加压缩器可利用的冗余度。仅支持一个选项start_offset:映射到输入数据起始处的地址,默认 0。

5.4 官方示例:使用自定义滤镜链

import lzma my_filters = [ {"id": lzma.FILTER_DELTA, "dist": 5}, {"id": lzma.FILTER_LZMA2, "preset": 7 | lzma.PRESET_EXTREME}, ] with lzma.open("file.xz", "w", filters=my_filters) as f: f.write(b"blah blah blah")

该示例展示了"Delta 预处理器 + LZMA2 主压缩器"的经典组合:先用dist=5的 Delta 滤镜预处理数据,再用preset=7配合PRESET_EXTREME的 LZMA2 滤镜做实际压缩。

从 C 层看,滤镜链由parse_filter_chain_spec解析为lzma_filter[]数组(LZMA_FILTERS_MAX + 1个槽位),并对FORMAT_RAW直接调用lzma_raw_encoder/lzma_raw_decoder(见 Modules/_lzmamodule.c 与 L1090-L1103)。仓库测试 Lib/test/test_lzma.py 的test_bad_filter_spec系列验证了非法滤镜规格(缺id键、未知id、未知选项键等)会抛出TypeError/ValueError;test__encode_filter_properties则验证dist超出合法范围(如 9001)会触发LZMAError。

六、杂项工具:is_check_supported

函数签名:

lzma.is_check_supported(check)

返回给定完整性校验类型在当前系统上是否受支持。CHECK_NONE与CHECK_CRC32总是受支持;CHECK_CRC64与CHECK_SHA256在liblzma以受限特性集编译时可能不可用。

因此,在跨服务器部署 KBEngine 时,若代码依赖CHECK_SHA256,建议先调用is_check_supported做运行时探测,避免在特性受限的liblzma上抛出LZMAError。仓库测试 test_is_check_supported 印证了文档结论:CHECK_NONE与CHECK_CRC32恒为真,而CHECK_ID_MAX + 1与CHECK_UNKNOWN恒为假。

七、多流拼接行为:三套 API 的差异速查

这是最容易踩坑的知识点,官方文档与源码、测试三方相互印证:

API是否透明处理多个拼接的压缩流依据
LZMAFile(读模式)是,透明解码为单一逻辑流Lib/lzma.py 经_compression.DecompressReader处理;测试 test_read_multistream 等
lzma.decompress()是,解压全部流并拼接结果Lib/lzma.py 的循环实现;测试 test_decompress_multistream
LZMADecompressor否,遇到流结束标记后剩余数据存入unused_data,需为每个流新建解压器测试 test_decompressor_multistream 明确断言unused_data中保存第二个流

其中LZMADecompressor的行为在测试中非常直观:LZMADecompressor()解压COMPRESSED_XZ + COMPRESSED_ALONE后,unused_data恰为COMPRESSED_ALONE(Lib/test/test_lzma.py)。

八、内存与安全性注意事项

  1. 高预设的内存开销:preset=9时单个LZMACompressor对象开销可高达约 800 MiB(官方文档明确警告),对 KBEngine 服务器这种长期运行的进程尤其要谨慎,避免在热点路径上使用高预设,或对解压器使用memlimit防止内存失控。
  2. 多线程使用LZMAFile需加锁:官方文档明确LZMAFile非线程安全(与bz2.BZ2File不同)。虽然 C 层每个压缩器/解压器内部各有锁(见 Modules/_lzmamodule.c),但高层的文件对象接口仍需调用方自行串行化。
  3. 解压炸弹防护:memlimit与LZMADecompressor.decompress(data, max_length)是控制解压侧资源消耗的主要手段;LZMAFile的内部缓冲限制也有专门测试保护(test_decompress_limited 验证解压出的数据不会超过1 + DEFAULT_BUFFER_SIZE的缓冲上限)。
  4. 文本模式参数冲突:二进制模式下传encoding/errors/newline会抛ValueError;"rt"等文本模式才会启用这些参数(Lib/lzma.py)。
  5. RAW 格式的约束:FORMAT_RAW压缩与解压都必须显式提供相同滤镜链,无法用FORMAT_AUTO自动探测,也不支持完整性校验与memlimit。

九、实战建议:在 KBEngine 场景中的应用

在 KBEngine 项目中,lzma模块最常见的落地场景包括:

  • 存档与备份压缩:在服务器脚本中把实体数据库导出内容、日志批次写入.xz文件,利用preset在压缩率与 CPU/内存之间权衡——日常归档用默认PRESET_DEFAULT,冷数据归档可尝试PRESET_EXTREME;
  • 跨进程/跨机传输前的预压缩:对体积较大的消息载荷先lzma.compress,接收端lzma.decompress还原;由于decompress()自动处理多流拼接,分片传输后拼接再解压也安全;
  • 文本配置与脚本资源打包:使用lzma.open("xxx.xz", "wt", encoding="utf-8")直接读写文本型资源,借助io.TextIOWrapper的换行与编码控制保持跨平台一致性。

需要提醒的是:在调用任何依赖_lzma的代码前,请确认目标机器已安装liblzma(构建时 setup.py 依赖系统库),否则import lzma会失败;同时建议通过lzma.is_check_supported探测高版本校验类型的可用性。

十、参考索引

  • 模块文档(本文主题):kbe/src/lib/python/Doc/library/lzma.rst
  • Python 层实现:kbe/src/lib/python/Lib/lzma.py
  • C 扩展实现:kbe/src/lib/python/Modules/_lzmamodule.c
  • 共享压缩基础设施:kbe/src/lib/python/Lib/_compression.py
  • 完整测试套件:kbe/src/lib/python/Lib/test/test_lzma.py
  • 构建检测逻辑:kbe/src/lib/python/setup.py
  • KBEngine 服务器脚本目录:kbe/res/scripts
  • 游戏开发
  • 后端

【免费下载链接】kbengine

A MMOG engine of server.

项目地址:https://gitcode.com/gh_mirrors/kb/kbengine
点击查看免费下载
上一篇:ESP-Claw完整指南:如何用一块几美元的ESP32芯片跑起真正的AI Agent
下一篇:4条命令产出游戏可用的精灵图集:sprite-gen 入门用户的Atlas管线实操指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询