文章

TOML:迄今最适合 Python 项目的配置语言

TOML:迄今最适合 Python 项目的配置语言

Python、Java 用来编写和运行程序;JSON、YAML 和 TOML 则主要用来表示数据。写 Python 项目的配置文件时,我尤其喜欢 TOML:语法简洁,数据类型也能自然地映射到 Python。Python 项目常用的 pyproject.toml,就是一个典型例子。

下面从一份 config.toml 开始,看看它的基本语法,以及如何在 Python 中读取它。

TOML 的基本语法

如果你使用 VS Code,可以按需安装 Even Better TOML 扩展,获得更丰富的编辑体验。

注释与键值对

TOML 用 # 表示注释,既可以单独占一行,也可以写在键值对后面。键和值用 = 连接:

1
2
3
4
5
6
# config.toml
name = 'Astroinn'
version = 1
enabled = true
topics = ['Python', 'TOML', 'blogging']
created_at = 2024-12-30T00:00:00Z

单引号和双引号都能表示字符串,但两者并不完全相同。双引号字符串支持 \n 等转义序列;单引号字符串按字面保留内容。例如:

1
description = "Astroinn says: \"Hello!\""

这里的整数、布尔值、字符串和数组都很直观。created_at 也不是普通字符串,而是带 UTC 时区的日期时间值。更多类型和语法细节可以查阅 TOML 规范。

用 Python 读取 TOML

Python 3.11 起,标准库提供了只负责读取 TOML 的 tomllib。把上面的内容保存为 config.toml,再运行:

1
2
3
4
5
6
7
8
# read_toml.py
import tomllib
from pprint import pp

with open('config.toml', 'rb') as f:
    config = tomllib.load(f)

pp(config)

tomllib.load() 接收以二进制模式打开的文件,所以这里使用 'rb'。输出类似下面这样:

1
2
3
4
5
{'name': 'Astroinn',
 'version': 1,
 'enabled': True,
 'topics': ['Python', 'TOML', 'blogging'],
 'created_at': datetime.datetime(2024, 12, 30, 0, 0, tzinfo=datetime.timezone.utc)}

可以看到,TOML 的整数、布尔值和数组分别变成了 Python 的 int、bool 和 list,带时区的日期时间变成了 datetime.datetime。如果使用 Python 3.10 或更早版本,可以选择第三方解析库;上面的 tomllib 代码需要 Python 3.11 或更新版本。具体类型映射见 Python 官方文档。

内联表

TOML 的表对应 Python 的字典。内容较少时,可以用花括号写成内联表:

1
shape = { height = 1, weight = 50 }

解析后,config['shape'] 是 {'height': 1, 'weight': 50}。内联表适合简短的数据;内容变多时,普通表更容易阅读。

普通表与嵌套表

用方括号声明表,后面的键值对就属于这个表,直到遇到下一个表头:

1
2
3
[account]
bilibili = 'astroinnB'
youtube = 'astroinnY'

这会得到 {'account': {'bilibili': 'astroinnB', 'youtube': 'astroinnY'}}。如果一个平台需要保存多个字段,可以用点号创建嵌套结构:

1
2
3
4
[account]
bilibili.username = 'astroinnB'
bilibili.followers = 100
youtube = 'astroinnY'

此时 config['account']['bilibili'] 是 {'username': 'astroinnB', 'followers': 100}。字段多了之后,还可以改用嵌套表,让同一平台的配置放在一起:

1
2
3
4
5
6
[account]
youtube = 'astroinnY'

[account.bilibili]
username = 'astroinnB'
followers = 100

上面两段配置会解析成相同的结构。它们是两种独立写法,不要同时放进同一个文件,否则会重复定义键。

表数组

如果每个平台都需要相同的字段,可以把它们组织成一组表。TOML 使用双层方括号 [[account]] 表示表数组:

1
2
3
4
5
6
7
[[account]]
platform = 'bilibili'
username = 'astroinnB'

[[account]]
platform = 'youtube'
username = 'astroinnY'

解析后,config['account'] 是一个列表,里面依次放着两个字典:

1
2
3
4
[
    {'platform': 'bilibili', 'username': 'astroinnB'},
    {'platform': 'youtube', 'username': 'astroinnY'},
]

这一段也是独立示例:普通表 [account] 和表数组 [[account]] 不能在同一份 TOML 文件中定义为同一个键。

为什么使用专门的配置格式

直接用 Python 文件写配置也可以,但导入模块时会执行其中的代码。TOML 是数据格式,解析配置时不会执行任意 Python 语句;同时,它的类型明确,适合保存项目设置。不过,数据格式本身并不能保证配置内容可信,也不适合把真实密码直接写进会提交到仓库的文件。

对 Python 项目来说,TOML 的实用之处在于:写起来清楚,读入后又能方便地得到字典、列表和日期时间等 Python 对象。需要项目配置时,可以先从一份简单的 config.toml 开始。

本文由作者按照 CC BY 4.0 进行授权