[Python3.11]新版本前瞻详解 - 第三期:tomllib
ProgramRipper
2022年11月15日 08:00
收录于文集
共3篇

PEP 680: tomllib

省流:

tomllib.load(fp%2C%20%2F%2C%20*%2C%20parse%5C_float%3Dfloat)

tomllib.loads(s%2C%20%2F%2C%20*%2C%20parse%5C_float%3Dfloat)

用法类似于 json.load 和 json.loads

EOF.


好吧,开个玩笑。

[T]oml

[Tom's Obvious Minimal Language]

[Tom 的(语义)明显、(配置)最小化的语言]

为人而生的配置文件格式。

以其创造者 Tom Preston-Werner 的名字谦逊地命名。

代码块
YAML
自动换行
复制代码
# 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%5E4(当然这些也有可能与 PyPA 成员,PSF 元老,TOML 核心成员 Pradyun Gedam 在 2018 年成为 toml-lang/toml 标准的主要维护者有关系)

因此,有人说Python 和 TOML 是新的最好的朋友,这也是一篇很好的 TOML 配合 Python 使用的英文教程。

但是,TOML 在很长一段时间里缺席标准库。因此,社区中出现了这些

第三方库

toml

最早且最广泛使用的 TOML 读写库之一。

toml.load(s%2C%20%5C_dict%3Ddict)

f 可以是文件路径,文件路径的列表(将被合并为一个对象)或者文件描述符。返回一个 _dict 的实例。

例如可以传入 _dict=collections.OrderedDict 来保持键的顺序。

对无效的 TOML 文档将引发 TomlDecodeError

toml.loads()

类似于 toml.load,但是接受一个字符串。

toml.dump()

o 转换为 TOML 格式的字符串,写入 fencoder 是一个 TomlEncoder 的实例。返回 TOML 格式的字符串。

toml.dumps()

类似于 toml.dump,但是只返回 TOML 格式的字符串。

tomli%5E8

简洁(只有约 800 行代码),快速(速度是 tomlkit 的 16 倍,toml 的 2.3 倍)且测试完备(100% 测试覆盖率)的 TOML 读取库。

tomli.load(fp%2C%20%2F%2C%20*%2C%20parse%5C_float%3Dfloat)

第一个参数应是一个可读二进制文件对象。返回一个 dict。会通过 [转换表](#转换表)将 TOML 类型转换为 Python 类型。

对每一个 TOML 浮点数都会以字符串形式调用 parse_float。默认情况下,这相当于 float(num_str)。这可以用于为 TOML 浮点数使用另一种数据类型或解析器(如 decimal.Decimal)。

可调用对象不能返回 dict 或 list,否则将引发 ValueError

对无效的 TOML 文档将引发 TOMLDecodeError

tomli.loads(s%2C%20%2F%2C%20*%2C%20parse%5C_float%3Dfloat)

类似于 tomli.load,但是接受一个 str

exception tomli.TOMLDecodeError

ValueError 的子类。

转换表

代码块
Python
自动换行
复制代码
| 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%5E9

tomli 作者 Taneli Hukkinen 开发的 TOML 写入库。非常简洁(只有一个不到 200 行的代码文件)

tomli_w.dump()

obj 转换为 TOML 格式的字符串,写入 fpmultiline_strings 为 True 时,将使用多行字符串。

注意:输出的字符串不保证是有效的 TOML 文档。如果输入数据可能是错误的而且有校验输出是否有效的需要,请使用 tomli.loads() 解析一次字符串以确保是有效的 TOML 文档。

tomli_w.dumps()

类似于 tomli_w.dump,但是只返回 TOML 格式的字符串。

tomlkit%5E%7B11%7D

保留注释和样式的 TOML 读写库。支持 TOML 1.0.0。

tomlkit.load()

fp 是一个可读字符串文件对象。返回一个 TOMLDocument 实例。

tomlkit.loads()

类似于 tomlkit.load,但是接受一个 str

tomlkit.dump()

data 转换为 TOML 格式的字符串,写入 fp。如果 sort_keys True,则将键按字母顺序排序。

tomlkit.dumps(data%2C%20*%2C%20sort%5C_keys%3DFalse)

类似于 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- 提供向后移植。

你可以如此指定依赖:

代码块
Python
自动换行
复制代码
tomli >= 1.1.0 ; python_version < "3.11"
复制成功

然后在代码如此使用:

代码块
Python
自动换行
复制代码
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 主要使用场景(配置文件)而言可有可无,不推荐使用。

  • qtoml 仅支持 TOML 0.5.0,缺乏维护,不推荐使用。

性能

以下测试均在 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):

代码块
YAML
自动换行
复制代码
| 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):

代码块
YAML
自动换行
复制代码
| 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 571PEP 518PEP621,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 已经足够了。


  1. TOML:Tom 的(语义)明显、(配置)最小化的语言 (https://toml.io/cn/)

  2. Tom Preston-Werner (https://tom.preston-werner.com/)

  3. PEP 621 – Storing project metadata in pyproject.toml | peps.python.org (https://www.python.org/dev/peps/pep-0621/)

  4. Declaring project metadata — Python Packaging User Guide (https://packaging.python.org/en/latest/specifications/declaring-project-metadata/)

  5. toml-lang/toml: Tom's Obvious, Minimal Language (https://github.com/toml-lang/toml)

  6. Python and TOML: New Best Friends – Real Python (https://realpython.com/python-toml/)

  7. uiri/toml: Python lib for TOML (https://github.com/uiri/toml)

  8. hukkin/tomli: A lil' TOML parser (https://github.com/hukkin/tomli)

  9. hukkin/tomli-w: A lil' TOML writer (counterpart to https://github.com/hukkin/tomli) (https://github.com/hukkin/tomli-w)

  10. hukkin (Taneli Hukkinen) (https://github.com/hukkin)

  11. sdispater/tomlkit: Style-preserving TOML library for Python (https://github.com/sdispater/tomlkit)

  12. samuelcolvin/rtoml: A fast TOML library for python implemented in rust. (https://github.com/samuelcolvin/rtoml)

  13. toml-rs/toml-rs: A TOML encoding/decoding library for Rust (https://github.com/alexcrichton/toml-rs)

  14. bobfang1992/pytomlpp: A python wrapper for tomlplusplus (https://github.com/bobfang1992/pytomlpp)

  15. toml++ TOML for C++ (https://marzer.github.io/tomlplusplus/)

  16. alethiophile/qtoml: Another Python TOML encoder/decoder (https://github.com/alethiophile/qtoml)

  17. tomli/benchmark at master · hukkin/tomli (https://github.com/hukkin/tomli/tree/master/benchmark)

  18. tomli-w/benchmark at master · hukkin/tomli-w (https://github.com/hukkin/tomli-w/tree/master/benchmark)

  19. PEP 680 – tomllib: Support for Parsing TOML in the Standard Library | peps.python.org (https://peps.python.org/pep-0680/)

  20. Please consider pushing tomli into stdlib · Issue #141 · hukkin/tomli (https://github.com/hukkin/tomli/issues/141#issuecomment-998018972)

  21. Adopting/recommending a toml parser? - #88 by encukou - Packaging - Discussions on Python.org (https://discuss.python.org/t/adopting-recommending-a-toml-parser/4068/88)