Python TOML

在本教程中,我们将了解 TOML,它是一种 Tom's Obvious Minimal Language。它是一种相当新的配置文件格式,被 Python 社区广泛使用。我们将讨论 TOML 的语法,使用tomli和tomllib解析 TOML 文档,并使用tomli_w将数据结构编写为 TOML。

使用 TOML 作为配置格式
TOML 是 Tom's Obvious Minimal Language 的缩写,是一种配置文件格式,以其创建者 Tom Preston-Werner 的名字命名。其设计目标是创建一种可以轻松解析为多种编程语言的数据结构的格式。

配置和配置文件
配置几乎在每个应用程序或系统中都起着至关重要的作用,可以在不更改源代码的情况下灵活地修改设置和行为。配置用于各种场景,例如指定连接到数据库或云存储等外部服务的基本细节。此外,配置设置有助于在项目中定制用户体验,使用户能够根据自己的喜好定制应用程序。

在项目中使用配置文件有很多好处,包括将代码与其设置分离。它会提示您仔细确定系统的哪些方面应该真正可配置,并提供一种为值分配有意义的名称的方法,否则这些值在源代码中会被视为“魔法”。现在让我们考虑在假设的井字游戏中配置文件的使用。

player_x_color = blue  
player_o_color = green  
board_size     = 3  
server_url     = https://tictactoe.example.com/  

虽然标准的井字游戏传统上是在三乘三的网格上玩的,但棋盘尺寸的可配置性是不确定的。现有逻辑可能不兼容不同的电路板尺寸。然而,在配置文件中包含电路板尺寸值仍然是有益的。这种方法有两个目的:为值提供一个有意义的名称并使其可见,即使它对于标准游戏保持不变。

部署应用程序时,项目 URL 非常重要。虽然对于普通用户来说可能不会发生变化,但高级用户可能需要能够在不同的服务器上重新部署游戏。

为了提供清晰度并适应不同的用例,相应地组织配置文件可能是有益的。一种流行的方法是将配置分成多个文件,每个文件解决一个不同的问题。或者,您可以将相关的配置值分组在一起以更好地组织。以下是假设的配置文件的可能重组:

[user]  
player_x_color = blue  
player_o_color = green  
  
[constant]  
board_size = 3  
  
[server]  
url = https://tictactoe.example.com  

根据操作系统和具体要求的不同,有多种指定配置的方法。例如,Windows 传统上使用 INI 文件,这与前面提供的配置文件示例很相似。在 Unix 系统中,通常使用纯文本配置文件,不过不同服务的具体格式可能有所不同。无论采用哪种格式,重点通常都是保持文件的可读性和简洁性,以便于理解和修改。

随着时间的推移,越来越多的应用程序采用 XML、JSON 或 YAML 等定义明确的格式来满足其配置要求。这些格式最初是作为数据交换或序列化格式开发的,主要用于计算机通信目的。不过,由于这些格式具有结构化的特点,而且广泛支持各种编程语言,因此在表示配置设置时也很有用。它们的采用简化了不同系统和编程语言之间的解析、操作和互操作性,使其成为配置文件格式的热门选择。

TOML - Tom显而易见的极简语言
TOML 是一种相对较新的格式,其初始规范(0.1.0 版)发布于 2013 年。从一开始,TOML 就优先考虑成为一种最小化、人类可读的配置文件格式。TOML 网站概述的目标如下:

"TOML旨在成为一种最小化的配置文件格式,其语义清晰,易于阅读。TOML 的设计目标是明确映射到哈希表。TOML 应易于解析为各种语言的数据结构。(来源,着重部分由作者标明)"

让我们在 TOML 中定义上述文件。

[user]  
player_x.color = "blue"  
player_o.color =
"green"  
  
[constant]  
board_size = 3  
  
[server]  
url =
"https://tictactoe.example.com"  


TOML 从传统配置文件的语法中汲取灵感。不过,与 Windows INI 文件和 UNIX 配置文件相比,TOML 的显著优势在于其定义明确的规范。与同类产品不同的是,TOML 有一个全面的规范,该规范精确地概述了 TOML 文档中允许使用的元素,并就如何解释不同的值提供了明确的指导。该规范经过不断发展,已趋于稳定和成熟,并于 2021 年初推出了 1.0.0 版本。

与 TOML 不同,INI 格式缺乏正式规范。相反,它包括许多变体和方言,通常由特定的实现来定义。例如,Python 在其标准库中通过 ConfigParser 支持读取 INI 文件。尽管 ConfigParser 相当灵活,但它可能并不支持 INI 文件的所有变体。

TOML 与许多传统格式的另一个显著区别是,TOML 为其值指定了类型。在所提供的示例中,"blue "被解释为字符串,而 3 则被视为数字。对 TOML 的一个潜在批评是,人类作者在编写 TOML 文档时需要注意这些类型。在较简单的格式中,这种责任通常由解析配置文件的程序员来承担

TOML 模式验证
目前,TOML还没有包含一种可在TOML文档中指定必填字段和可选字段的模式语言。关于增加这一功能的各种建议已经提出,但仍无法确定其中任何建议是否会在不久的将来被接受和实施。

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}")  

在处理更复杂的 TOML 文档时,仅依赖 TOML 格式的方法可能无法有效扩展。此外,如果目标是提供翔实的错误信息,还需要额外的努力。一个更好的替代方案是利用 pydantic 这样的库,它利用类型注释在运行时执行数据验证。通过使用 pydantic,人们可以从其精确而有用的内置错误信息中获益。这种方法不仅简化了数据验证,还增强了处理 TOML 配置的可扩展性和可用性。

关于 TOML 的更多信息:键值对
TOML 以键值格式存储值。TOML 值有不同的类型。值必须是以下类型之一。

  • String
  • Integer
  • Float
  • Boolean
  • Offset date-time
  • Local date
  • Local time
  • Array
  • Inline table

键值对是 TOML 文档的基本构件。我们用 key=value 语法来表示它们,其中 key 与 value 之间用等号隔开。下面是一个包含键值对的 TOML 文档示例。

message = "Hello Example"  

在 TOML 中,键始终被解释为字符串,无论其周围是否有引号。让我们看下面的例子来说明这一点。

message = "Hello, Example!  
"12 = "Life, the universe, and everything"  

在 TOML 中,"12 "这样的键被认为是有效的,但它将被解释为字符串而不是数字。通常情况下,建议尽可能使用裸键。裸键由 ASCII 字母、数字、下划线和破折号组成。正如前面提供的示例所示,这些键可以不带引号。通过使用裸键,可以提高 TOML 配置文件的可读性和简洁性。

TOML 文档必须使用 UTF-8 Unicode 编码,这为表示值提供了相当大的灵活性。虽然裸键有一些限制,但在定义键时仍可使用 Unicode 字符。不过,使用 Unicode 键时必须用引号将其括起来。值得注意的是,使用 Unicode 键还需要使用引号来分隔它们。

在 TOML 中,点(.)在键中使用时有特殊意义。虽然可以在不带引号的键中加入点,但这样做会导致值分组。带点的键将在每个点处被分割,从而在数据结构中创建嵌套分组。让我们通过下面的示例来说明这种行为。

match_x.symbol = "X"  
match_x.color = "purple"  

在给出的示例中,有两个键包含点。由于这两个键都以 "match_x "开头,因此它们将被归入一个名为 "match_x "的部分。符号 "和 "颜色 "键将嵌套在该部分中,以便对相关数据进行结构化组织。

String, Numbers, 和Boolean
TOML 使用与 Python 类似的数据类型。唯一不同的是布尔值是小写的:true 和 false。在 TOML 中,通常使用双引号(")来定义字符串。在字符串中,您可以使用反斜线来转义特殊字符。例如,"\u03c0 is less than four "包含转义序列 \u03c0,它代表 Unicode 字符,编码点为 U+03c0,对应于希腊字母 π。 解释时,该字符串将显示为 "π is less than four"。

在 TOML 中,指定字符串的另一种方法是使用单引号(')。这些单引号字符串被称为字面字符串,其行为类似于 Python 中的原始字符串。在字面字符串中,没有字符被转义或解释,这意味着"\u03c0 是 π 的 Unicode 代号点 "将把首字符"\u03c0 "视为字面字符,而不是将其解释为 Unicode 转义序列。