

原文:《Python and TOML: New Best Friends》[1]

toml
TOML[2](Tom's Obvious Minimal Language)是一种相当新的配置文件格式。Python社区在过去几年中已经接受了它,许多流行的工具都使用TOML 进行配置,您将在构建和分发自己的包时可能就会使用 pyproject.toml 。
TOML最初目标是成为一种易于人类阅读和编写的配置文件格式。
之前,已经有了许多配置文件格式,如JSON、YAML、INI等。但是它们不是很适合人类读写。JSON多层嵌套时,很难阅读。YAML的缩进可能很混乱。INI没有同一的规范,且只适合简单的配置。--《https://zhuanlan.zhihu.com/p/50412485》
几乎任何程序都需要配置。例如,使用配置文件指定数据库信息。 为项目使用配置文件是将代码与其设置分开的好方法。 现在,考虑一个井字游戏的配置文件:
player_x_color = blue
player_o_color = green
board_size = 3
server_url = https://tictactoe.example.com/您可以直接在源代码中对此进行编码。但通过单独的配置文件可以实现以下几点:
仔细观察这些配置,可能会发现这些配置需要分成不同部分:
color信息可能是用户配置的,而board_size可能是固定的,server_url是服务器相关的。
一种选择是将配置分成多个文件,另一种是进行分组:
[user]
player_x_color = blue
player_o_color = green
[constant]
board_size = 3
[server]
url = https://tictactoe.example.com文件的组织使每个配置项的角色更加清晰。您还可以向配置文件添加注释使得配置项更更加清晰。
TOML是一种相当新的格式。第一个格式规范 0.1.0 版于 2013 年发布。从一开始,它就专注于成为人类可读的最小配置文件格式。TOML文档描述其目标如下:
TOML旨在成为一种最小的配置文件格式,语义明显,易于阅读。TOML 旨在明确映射到哈希表。TOML应该很容易解析为各种语言的数据结构。
TOML非常流行,越来越多的 Python 工具(包括 Black、pytest、mypy 和 isort)使用 TOML 进行配置。
回想一下上一小节中的配置。 在TOML中表达它的方式如下:
[user]
player_x.color = "blue"
player_o.color = "green"
[constant]
board_size = 3
[server]
url = "https://tictactoe.example.com"在实践中,给定的 TOML 文件也可能带有一些非语法要求。这些是架构要求(schema requirements)。例如,井字游戏应用程序可能要求配置文件包含server URL。另一方面,player color可能是可选的,因为应用程序定义了默认颜色。
目前,TOML不包含可以在TOML文档中指定必需和可选字段的架构语言。存在几个提案,尽管目前尚不清楚是否会很快接受其中任何一个。
在简单的应用程序中,您可以手动验证 TOML 配置。例如,您可以使用 Python 3.10 中引入的结构模式匹配(Structural Pattern Matching)。假设您已将配置解析为 Python 并将其命名为 config 。然后,您可以按如下方式检查其结构:
match config:
case {
"user": {"player_x": {"color": str()}, "player_o": {"color": str()}},
"constant": {"board_size": int()},
"server": {"url": str()},
}:
pass
case _:
raise ValueError(f"invalid configuration: {config}")第一个 case 语句阐明了您期望的结构。如果 config 匹配,则用于 pass 继续代码。否则,将引发错误。
如果您的 TOML 文档更复杂,则此方法可能无法很好地扩展。如果你想提供良好的错误消息,你还需要做更多的工作。更好的选择是使用 pydantic,它利用类型注释在运行时进行数据验证。pydantic 的一个优点是它内置了精确且有用的错误消息。
还有一些工具可以利用 JSON 等格式的现有架构验证。例如,Taplo 是一个 TOML 工具包,可以根据 JSON 模式验证 TOML 文档。Taplo 也可用于 Visual Studio Code,捆绑在 Even Better TOML 扩展中。
TOML 是围绕键值对构建的,这些键值对很好地映射到哈希表数据结构。TOML 值具有不同的类型。每个值可以使以下类型之一:
此外,还可以将table 和array of tables作为集合来组织多个键值对。您将在本节的其余部分了解有关所有这些内容的更多信息,以及如何在 TOML 中指定它们。 注:TOML 支持与 Python 语法相同的注释(
#)。
如前所述,键值对是 TOML 文档中的基本构建块。您可以使用 <key> = <value> 语法:
greeting = "Hello, TOML!"在此示例中, greeting 是键,而 "Hello, TOML!" 是值。值具有类型。在此示例中,该值是一个文本字符串。键始终被解释为字符串,即使不用引号括起来也是如此。请看以下示例:
greeting = "Hello, TOML!"
42 = "Life, the universe, and everything"此处42是一个 有效的键,它被解释为字符串,而不是数字。通常,您希望使用裸键。这些键仅由 ASCII 字母和数字以及下划线和破折号组成。所有这些键都可以不带引号地编写,如上面的示例所示。
TOML 文档必须以 UTF-8 Unicode 编码。这为您提供了极大的灵活性,可以代表各种值。 您也可以在键中使用 Unicode。若要使用 Unicode 键,必须在它们两边添加引号:
"realpython.com" = "Real Python"
"blåbærsyltetøy" = "blueberry jam"
"Tom Preston-Werner" = "creator"点(.)在 TOML 键中起着特殊作用。您可以在不带引号的键中使用点,在这种情况下,它们将通过拆分每个点的点键来触发分组:
player_x.symbol = "X"
player_x.color = "purple"在这里,您指定两个点键(dotted key)。由于它们都以 player_x 开头,因此键 symbol 和 color 将组合在名为 player_x 的部分中。当您开始浏览表时,您将了解有关点键的更多信息。
接下来,将注意力转向值。在下一节中,您将了解 TOML 中最基本的数据类型。
TOML 对基本数据类型使用熟悉的语法。来自Python的您能够识别字符串、整数、浮点数和布尔值:
string = "Text with quotes"
integer = 42
float = 3.11
boolean = trueTOML 和 Python 之间的直接区别在于 TOML 的布尔值是小写的:
true和false。
TOML 字符串通常应使用双引号,并可以用反斜杠(\)转义字符。还可以使用单引号指定字符串。单引号字符串称为文字字符串,其行为类似于 Python 中的原始字符串。最后,还可以使用三引号( """ 或 ''' )指定字符串。三引号字符串允许您在多行上编写字符串,类似于 Python 多行字符串:
partly_zen = """
Flat is better than nested.
Sparse is better than dense.
"""TOML 中的数字可以是整数,也可以是浮点数。整数表示整数,并指定为纯数字字符。与 Python 一样,您可以使用下划线来增强可读性:
number = 42
negative = -8
large = 60_481_729您已经了解了 TOML 文档由一个或多个键值对组成。当用编程语言表示时,它们应存储在哈希表数据结构中。在 Python 中,这将是一个字典或其他类似字典的数据结构。要组织键值对,您可以使用表。
TOML 支持三种不同的表指定方式。
[user]
player_x.color = "blue"
player_o.color = "green"
[constant]
board_size = 3
[server]
url = "https://tictactoe.example.com"在此示例中,您有三个表:user、constant 和 server。每个表都有一个标头,用方括号括起来。
您还可以在上面的配置中找到点键表(dotted key tables)。在 user中:
[user]
player_x.color = "blue"
player_o.color = "green"键中的点 (.)创建一个由点之前的键部分命名的表(player_x)。您还可以通过嵌套常规表实现相同效果:
[user]
[user.player_x]
color = "blue"
[user.player_o]
color = "green"缩进在 TOML 中并不重要。您可以在此处使用它来表示表的嵌套。您可以看到该 user 表包含两个子表, player_x 以及 player_o 。每个子表都包含一个键值对。
请注意,您需要在嵌套表的标头中使用点键,并命名所有中间表。这使得 TOML 标头规范非常冗长。在类似的规范中,例如 JSON 或 YAML,您只需指定子表名称,而不重复外部表的名称。同时,这使得 TOML 非常明确,并且更难在深度嵌套的结构中迷失方向。
现在,您将通过为每个玩家添加标签或符号来扩展 user 表格。您将以三种不同的形式表示此表,首先仅使用常规表,然后使用点键表,最后使用内联表。
常规表:
[user]
[user.player_x]
symbol = "X"
color = "blue"
[user.player_o]
symbol = "O"
color = "green"点键表:
[user]
player_x.symbol = "X"
player_x.color = "blue"
player_o.symbol = "O"
player_o.color = "green"这比上面的嵌套表更短、更简洁。但是,结构现在不太清晰。
内联表:
[user]
player_x = { symbol = "X", color = "blue" }
player_o = { symbol = "O", color = "green" }内联表使用大括号 {}定义,这些大括号 用逗号分隔的键值对换行。在此示例中,内联表在可读性和紧凑性之间取得了很好的平衡,因为玩家表的分组变得清晰。
TOML 文档由一个无名根表(root table)表示,该根表包含所有其他表和键值对。在 TOML 配置顶部(在任何表头之前)写入的键值对直接存储在根表中:
title = "Tic-Tac-Toe"
[constant]
board_size = 3在此示例中, title 是根表中的键, constant 是嵌套在根表中的表,board_size是constant表中的键。
请注意,表包含在其标头和下一个表标头之间写入的所有键值对。在实践中,这意味着您必须在属于该表的键值对下方定义嵌套子表。请考虑以下文档:
[user]
[user.player_x]
color = "blue"
[user.player_o]
color = "green"
background_color = "white"(看上去)缩进表明这应该 background_color 是 user 表中的键。但事实上,TOML 会忽略缩进,只检查表头。在此示例中, background_color 是 user.player_o表的一部分。要更正此问题, background_color 应在嵌套表之前定义:
[user]
background_color = "white"
[user.player_x]
color = "blue"
[user.player_o]
color = "green"TOML 有四种日期和时间类型:
TOML 基于 RFC 3339 表示时间和日期。本文档定义了一种时间和日期格式,该格式通常用于表示 Internet 上的时间戳。完全定义的时间戳如下所示: 2021-01-12T01:23:45.654321+01:00。时间戳由多个字段组成,由不同的分隔符分隔。
2021-01-12T01:23:45.654321+01:00对应的含义为: 年-月-日T时:分:秒.微秒+时区与UTC的偏移量 其中T可以用空格替换,微秒是可选的,偏移量可用Z表示与UTC时间相同。
TOML数组是值的有序列表,可用[]创建,类似Python的列表。
packages = ["tomllib", "tomli", "tomli_w", "tomlkit"]players = [
{ symbol = "X", color = "blue", ai = true },
{ symbol = "O", color = "green", ai = false },
]您可以将内联表放在方括号内。但是内联表不能很好地扩展。如果要表示表较大的表数组,则可以考虑表数组:
[[players]]
symbol = "X"
color = "blue"
ai = true
[[players]]
symbol = "O"
color = "green"
ai = false此表数组等效于您上面编写的内联表数组。方括号双括号定义表数组,而不是常规表。
对于更广泛的示例,请考虑以下摘录自 TOML 文档,其中列出了测验应用程序的问题:
[python]
label = "Python"
[[python.questions]]
question = "Which built-in function can get information from the user"
answers = ["input"]
alternatives = ["get", "print", "write"]
[[python.questions]]
question = "What's the purpose of the built-in zip() function"
answers = ["To iterate over two or more sequences at the same time"]
alternatives = [
"To combine several strings into one",
"To compress several files into one archive",
"To get information from the user",
]此例中,python表包含两个键:label 和 questions。questions 键是一个表数组,其中每个元素都是一个表。每个questions 表都包含question, answers 和 alternatives。
[1] 《Python and TOML: New Best Friends》: https://realpython.com/python-toml/
[2] TOML: https://toml.io/cn/