Python3.11 - tomllib
PEP 680: tomllib
这个模块提供了一个解析 TOML (Tom's Obvious Minimal Language, https://toml.io)接口。该模块不支持写 TOML。
这个模块定义了以下函数:
tomllib.load(fp, /, *, parse_float=float)
读取一个 TOML 文件。第一个参数应该是一个可读的二进制文件对象。返回 dict。使用 转换表 将 TOML 类型转换为 Python。
对每个要解析的 TOML 浮点数字符串调用 parse_float。默认情况下,这相当于 float(num_str)。这可以用于为 TOML 浮点数使用另一种数据类型或解析器(例如:decimal.Decimal)。可调用对象不能返回 dict 或 list,否则将引发 ValueError。
对无效的 TOML 文档将引发 TOMLDecodeError。
tomllib.loads(s, /, *, parse_float=float)
从 str 对象中加载 TOML。返回 dict。使用 转换表 将 TOML 类型转换为 Python类型。参数 parse_float 与 load() 中的意义相同。
[T]oml
[Tom's Obvious Minimal Language]
[Tom 的(语义)明显、(配置)最小化的语言]
为人而生的配置文件格式。
以其创造者 Tom Preston-Werner 的名字谦逊地命名。
# This is a TOML document
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 }
[servers]
[servers.alpha]
ip = "10.0.0.1"
role = "frontend"
[servers.beta]
ip = "10.0.0.2"
role = "backend"
它:
- 语义明显易于阅读
- 能无歧义地映射为哈希表
- 易于解析成各种语言中的数据结构
与 Python 相近的语法和设计理念,使它迅速成为 Python 社区的首选配置文件格式。它还被指定为项目元数据存储格式:pyproject.toml,详见 PEP 621 和 Declaring project metadata — Python Packaging User Guide(当然这些也有可能与 PyPA 成员,PSF 元老,TOML 核心成员 Pradyun Gedam 在 2018 年成为 toml-lang/toml 标准的主要维护者有关系)。
因此,有人说Python 和 TOML 是新的最好的朋友,这也是一篇很好的 TOML 配合 Python 使用的英文教程。
但是,TOML 在很长一段时间里缺席标准库。因此,社区中出现了这些
第三方库
toml
最早且最广泛使用的 TOML 读写库之一。
toml.load()
f 可以是文件路径,文件路径的列表(将被合并为一个对象)或者文件描述符。返回一个 _dict 的实例。
例如可以传入 _dict=collections.OrderedDict 来保持键的顺序。
对无效的 TOML 文档将引发 TomlDecodeError。
toml.loads()
类似于 toml.load,但是接受一个字符串。
toml.dump()
将 o 转换为 TOML 格式的字符串,写入 f。encoder 是一个 TomlEncoder 的实例。返回 TOML 格式的字符串。
toml.dumps()
类似于 toml.dump,但是只返回 TOML 格式的字符串。
tomli
简洁(只有约 800 行代码),快速(速度是 tomlkit 的 16 倍,toml 的 2.3 倍)且测试完备(100% 测试覆盖率)的 TOML 读取库。
tomli.load()
第一个参数应是一个可读二进制文件对象。返回一个 dict。会通过 转换表将 TOML 类型转换为 Python 类型。
对每一个 TOML 浮点数都会以字符串形式调用 parse_float。默认情况下,这相当于 float(num_str)。这可以用于为 TOML 浮点数使用另一种数据类型或解析器(如 decimal.Decimal)。
可调用对象不能返回 dict 或 list,否则将引发 ValueError。
对无效的 TOML 文档将引发 TOMLDecodeError。
tomli.loads()
类似于 tomli.load,但是接受一个 str。
tomli.TOMLDecodeError
ValueError 的子类。
转换表
TOML | Python |
---|---|
table | dict |
string | str |
integer | int |
float | float(可通过 parse_float 配置) |
boolean | bool |
offset date-time | datetime.datetime(tzinfo 为 datetime.timezone ) |
local date-time | datetime.datetime(tzinfo 为 None ) |
local date | datetime.date |
local time | datetime.time |
array | list |
tomli-w
由 tomli 作者 Taneli Hukkinen 开发的 TOML 写入库。非常简洁(只有一个不到 200 行的代码文件)
tomli_w.dump()
将 obj 转换为 TOML 格式的字符串,写入 fp。multiline_strings 为 True 时,将使用多行字符串。
注意:输出的字符串不保证是有效的 TOML 文档。如果输入数据可能是错误的而且有校验输出是否有效的需要,请使用 tomli.loads() 解析一次字符串以确保是有效的 TOML 文档。
tomli_w.dumps()
类似于 tomli_w.dump,但是只返回 TOML 格式的字符串。
tomlkit
保留注释和样式的 TOML 读写库。支持 TOML 1.0.0。
tomlkit.load()
fp 是一个可读字符串文件对象。返回一个 TOMLDocument 实例。
tomlkit.loads()
类似于 tomlkit.load,但是接受一个 str。
tomlkit.dump()
将 data 转换为 TOML 格式的字符串,写入 fp。如果 sort_keys 为 True,则将键按字母顺序排序。
tomlkit.dumps()
类似于 tomlkit.dump,但是只返回 TOML 格式的字符串。
tomlkit.TOMLDocument
可以通过 tomlkit.document() 创建。是一个对底层数据对象的映射,并且保留了样式信息。可以使用 tomlkit.items 系列 API 从零开始创建 TOMLDocument。
可以看出:
toml 过于老旧。
为了提供对过往 Python 版本的支持,提供了 _dict 以支持 OrderedDict。这在 Python 3.7+ 毫无必要,因为 dict 是有序的。除此之外,对于时区,它使用自定义的 toml.tz.TomlTz 而不是 datetime.timezone;对于无效的 TOML 文档,它引发 toml.TomlDecodeError 而不是符合 PEP 8 的命名 TOMLDecodeError。最重要的是,它有复杂但效果不尽如人意的写入 API。
这些问题使它没有成为 tomllib 的参考实现,也使 toml 不能成为新的标准库的名字(因为这会带来破坏性更改)。无论如何,都不推荐使用这一过时而缺乏维护的库。
tomli 提供了类似于内置库 marshal 和 pickle 的读取 API,简洁而易于维护。
你可能注意到了,它的 API 和 tomllib 一致。实际上它就是 tomllib 的参考实现,而且 tomllib 就是它的一个版本。即使在它成为标准库之后,它也会继续作为第三方库存在,为 Python 3.11- 提供向后移植。
你可以如此指定依赖:
tomli >= 1.1.0 ; python_version < "3.11"
然后在代码如此使用:
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
tomli-w 提供了类似于内置库 marshal 和 pickle 的写入 API,简洁而易于维护。
它不保留注释和样式,而且也无法保证生成有效的 TOML 文档。不过,因为简单且统一的 API,它依然是官方文档中推荐使用的写入库。推荐在写入没有注释或样式的简单 TOML 文档时搭配 tomli 或 tomllib 使用。
tomlkit 提供强大的保留注释和格式的读写功能,被许多知名包管理工具(大多有 TOML 保留格式写入需求)使用,如 hatch、poetry 和 pdm,但是 API 复杂,不易维护和使用。
它的读取速度非常慢,而且类型提示和代码补全也很糟糕,所以非常不建议使用它读取 TOML 文档,除非需要修改文档。不过,因为是(据我所知的)唯一的保留注释和格式的库,它也是官方文档推荐使用的读写库。推荐只使用它创建或修改文档。
其他第三方库
rtoml 基于 toml-rs 的 Rust 扩展实现。由 pydantic 作者 Samuel Colvin 开发。性能优异,但对 TOML 主要使用场景(配置文件)而言可有可无,且不支持 TOML 1.0.0,安装困难,不推荐使用。
pytomlpp 基于 toml++ 的 C++ 扩展实现。性能优异,但对 TOML 主要使用场景(配置文件)而言可有可无,不推荐使用。
性能
以下测试均在 Raspberry Pi 4 Model B Rev 1.4 + Ubuntu 22.04.1 LTS (Linux-5.15.0-1017-raspi-aarch64-with-glibc2.35) + GCC 11.3.0 + CPython 3.11.0 上进行。
读取(tomli benchmark):
parser | exec time | performance (higher is better) |
---|---|---|
rtoml | 1.84 s | baseline (100%) |
pytomlpp | 2.79 s | 66.14% |
tomllib | 15.2 s | 12.12% |
tomli | 15.2 s | 12.16% |
toml | 36.9 s | 5.00% |
qtoml | 38.7 s | 4.77% |
tomlkit | 283 s | 0.65% |
写入(tomli-w benchmark):
parser | exec time | performance (higher is better) |
---|---|---|
rtoml | 1.64 s | baseline (100%) |
pytomlpp | 2.04 s | 80.39% |
tomli-w | 4.95 s | 33.15% |
toml | 5.79 s | 28.33% |
qtoml | 12.7 s | 12.93% |
tomlkit | 53.8 s | 3.05% |
PEP 680: tomllib
动机
根据 PEP 571、PEP 518 和 PEP621,TOML 是 Python 打包的首选格式。Python 构建工具为了能够自举(在没有其他构建工具的情况下安装自身,此时无法下载安装其他第三方库),必须内置 TOML 解析库或者使用其他变通方法解析 TOML 文档。这给重打包者(如 Debian 系的 apt)和下游使用者带来了严重的问题。
此外,许多 Python 工具使用 TOML 作为配置文件格式,例如 black、mypy、pytest、tox、pylint、isort 和 flake8(可以发现其中不少是 Python 核心开发者维护的库)。
选择 tomli 的理由
纯 Python 实现:TOML 解析不太可能成为项目瓶颈,并且需要更高性能的用户可以选择第三方库(就像 JSON,即使 CPython 提供了一个标准 C 扩展库);可以避免许多 C 特有的安全问题,例如缓冲区溢出。
便于维护:作者愿意帮助它融入标准库并维护它,Python 核心开发者 Petr Viktorin 也愿意维护读取 API。相较之下,tomlkit 的作者都认为这个库不适合成为标准库,没有 Python 核心开发者表示愿意维护写入 API。
虽然当下 tomllib 没有写入 API,但是如果有更好的 API 和相关的使用用例,以后可能会有新的 PEP 引入它们。为了性能,以后也可能会使用 C 扩展重新实现 tomllib。
不过,考虑到大多数使用场景只是读取 TOML 文档,现在的 tomllib 已经足够了。