在python中,该选择哪个toml包呢?

1. 关于TOML

TOML (Tom's Obvious, Minimal Language)是由Tom Preston-Werner, Pradyun Gedam等人制订的一种配置文件语言,就像json和yaml一样。TOML的目标是保持配置文件的小体积并不失其可读性。它的设计可使配置的键和值很容易的映射到哈希表中,这也使其很容易被解析成不同语言对应的数据结构。

1.1 和其他配置语言的比较

TOML和其他配置语言有很多的共同点。比如TOML和JSON都有简单且通用的数据类型,这使得他们很容易被机器编码或者识别。TOML和YAML都很注意可读性。比如他们都支持注释。但是TOML是取二者之长,允许注释(JSON不允许)和保持简单(YAML结构看起来稍微复杂)。

INI看起来很你TOML,但是它没有一个统一的标准,没有数据类型,也不支持嵌套。

2. TOML在python中的应用

TOML已经被广泛应该在python中。最著名的应该是 PEP 621 - Storing project metadata in pyproject.toml , 这个PEP已经final了,也就是说官方鼓励使用pyproject.toml来保存python项目的元信息。越来越多的python工具也都支持从pyproject.toml中读取配置,例如poetry, mypy, pytest, black等等,就连pip也开始支持了。

2022年1月, PEP 680 - tomllib: Support for Parsing TOML in the Standard Library 也已经被接受了。也就是说在python3.11中,一个新的内置库tomllib将加入用来解析toml配置文件。

3. python中的toml包

目前已经有很多包可以解析和写入toml配置文件,包括:

  • toml是一个完全python解析的toml库
  • rtoml的底层则是用rust实现的
  • pytomlpp是用c++来解析toml
  • tomli也是用python来解析的,值得一提的是PEP680也将主要由该包的作者贡献
  • qtoml也是由python来解析。作者写这个包纯粹是觉得uiri/toml太慢了
  • tomlkit也是由python解析。该包是由大名鼎鼎的poetry的作者所写,也是poetry的依赖之一

4. 该用哪个包呢?

由于每个项目的应该场景不同,每个开发者关心的点也不同,所以在使用的时候就需要做出取舍。下面我们将从几个不同的方面来讨论:

4.1 版本

文章中讨论所用到的包的版本如下:

4.2 对 None 的处理

标准的TOML是不支持None的 github.com/toml-lang/to

那么如果要写入一个含有None值的配置,各个包是如何处理的呢?

  • 如果单纯的只dump None,也就是 <package>.dumps(None) :

rtoml会给出一个 "null" 字符串,而其他的包都是报错

  • 如果是dump一个值为None的键值对,也就是 <package>.dumps({"key": None})

toml会忽略这个键值对,给出一个空字符串;rtoml会给出 'key = "null"\n' ;而其他的包都会报错

  • 如果None在一个列表中 ( <package>.dumps({"key": [1, 2, 3, None, 5]})

toml给出 'key = [ 1, 2, 3, "None", 5,]\n' ; rtoml给出 'key = [1, 2, 3, "null", 5]\n' ; 而其余的包则均报错

  • 有的包会把None dump成 "None" 或者 "null" , 那么反过来呢,这些特殊的字符串在被解析的时候会不会解析成None呢?然而都不会,所有的包都会保留为字符串,不会解析成python中的None

4.3 键的有序性保留

我们知道,从python3.7开始,字典中键的顺序是保留的,也就是说 {"a": 1, "b": 2} 的键的顺序始终为 ["a", "b"] 。那么当我们dump这个字典的时候,是否一定是

a = 1
b = 2

而不是

b = 2
a = 1

呢?

那么反过来呢?如果toml配置,其中 "b" 键在前(上面最后那个例子),那么加载的时候,是加载成 {"b": 2, "a": 1} 还是随机的呢?

在这些包中,除子 pytomlpp,其他的包都保留了键的顺序。并且pytomlpp并不打算实现这个功能: github.com/bobfang1992/

4.4 对unicode的支持

经过测试,所有的包都支持unicode。值得一提的是,toml配置文件必须是utf-8编码的:

A TOML file must be a valid UTF-8 encoded Unicode document.

当加载一个配置文件时, tomli.load(f) 必须值 "rb" 模式打开。 open(file, "r", encoding="utf-8") 是不被允许的。

4.5 对TOML标准的兼容性

为了测试对TOML标准的兼容性,

专门收集了一些针对TOML标准的toml配置文件,包括一些有效合法的toml文件和不合TOML标准的toml文件。也就是说要做到对TOML标准的兼容,一个包不仅仅要正确的解析那些合法的toml文件,还需要在解析不合标准的toml文件时给出错误提示。下面使用toml-test v1.1.0 进行测试。

  • 对合法的toml文件的解析
    • toml: 86/99 (86.87%) passed
    • rtoml: 99/99 (100%) passed
    • pytomlpp: 99/99 (100%) passed
    • tomli: 99/99 (100%) passed
    • qtoml: 95/99 (95.96%) passed
    • tomlkit: 99/99 (100%) passed
  • 对非法toml文件的解析 (通过则说明在解析时给出了适当的错误提示)
    • toml: 169/214 (78.97%) passed
    • rtoml: 209/214 (97.66%) passed
    • pytomlpp: 214/214 (100%) passed
    • tomli: 214/214 (100%) passed
    • qtoml: 95/99 (95.96%) passed
    • tomlkit: 208/214 (97.20%) passed

从上面的结果可以看到,pytomlpp和tomli是兼容TOML标准对好的包,rtoml和qtoml次之。

4.6 速度

解析速度也是很多人比如关心的一个特性。我们使用了3个toml文件进行评估。所有的测试结果都是对数据文件进行5000次读取所产生的。

rtoml在速度上胜出,pytomlpp的差距也很小。tomlkit最慢。

5. 总结

总体来看,各个包各有优缺点。但无论是从兼容性还是速度上,toml和tomlkit都不推荐。如果你不需要保留键的顺序,那么pytomlpp是一个不错的选择。如果你考虑到将来要兼容python11的tomllib,那tomli也是不错的选择。但我个人不喜欢的一点是,它把读(tomli)和写(tomli_w)分成了两个包。rtoml是我目前的选择。

需要提到的是,对于rtoml和pytomlpp,目前在win,linux,macos是有编译好的binary的,在没有binary的平台上,要从头编译c++或者rust,也是需要考虑的一个问题。

文中所有的测试代码和报告来自:

发布于 2022-05-14 02:49