2026实战:用 tomlkit 优雅读写 TOML 配置,告别手写解析器
本文是一篇 Python 库用法教程,结合 2026年09月 的热门 / 新发布 / 高频实用包,讲清安装、核心语法和能直接抄走的写法。
安装与导入
tomlkit 的安装非常直接,它不依赖任何编译步骤,纯 Python 实现,所以无论是虚拟环境还是系统级 Python,都能在几秒内完成安装。推荐使用 pip 或 uv 这两种包管理器,命令如下:
bash
# 使用 pip(Python 3.8+)
pip install tomlkit
# 使用 uv(更快,推荐给新项目)
uv add tomlkit
如果你在团队协作或持续集成环境中,建议锁定版本号,例如 tomlkit==0.13.2,避免未来 API 变动影响你的解析逻辑。安装完成后,验证是否成功可以运行 pip show tomlkit 查看版本信息。
导入方式有两种,取决于你后续的调用习惯。最基础的是直接导入模块:
python
import tomlkit
# 检查版本(可选)
print(tomlkit.__version__)
如果你只打算使用 loads、dumps 和 parse 这几个高频函数,也可以按需导入:
python
from tomlkit import loads, dumps, parse
from tomlkit.api import document, table, aot, comment
这里有一个常见的易错点:tomlkit 的 parse 和 loads 返回的对象不是普通的 Python dict,而是 TOMLDocument 实例 。它继承自 dict,但内部携带了格式信息(注释、缩进、键值顺序)。这意味着你可以像操作 dict 一样读取它,但当你用 dict() 强制转换时,会丢失所有 TOML 特有的格式。例如:
python
import tomlkit
raw_toml = """
# 数据库配置
[database]
host = "localhost"
port = 5432
"""
doc = tomlkit.parse(raw_toml)
# 读取值
print(doc["database"]["host"]) # localhost
# 注意:这里返回的是普通 dict,但会丢失注释和原始格式
plain_dict = dict(doc)
print(type(plain_dict)) # <class 'dict'>
如果你需要保留注释并重新写回文件,必须保留 TOMLDocument 对象,不要转成普通 dict。另外一个易错点是:tomlkit 对键名的大小写敏感,且不支持数字开头的裸键 (不带引号的键)。例如 123abc = 1 会抛出解析错误,必须写成 "123abc" = 1。
在导入层面,还有一个值得留意的细节:tomlkit 内部使用了大量类型注解(PEP 561),所以如果你使用 mypy 做静态检查,需要确保安装了 tomlkit 的类型信息,这在 pip 安装时已经自动捆绑,无需额外配置。
对于项目的 pyproject.toml 文件,tomlkit 是 Poetry、PDM 等工具的核心依赖,因此你可能已经在环境中拥有了它。可以用以下代码快速确认:
python
import importlib.util
spec = importlib.util.find_spec("tomlkit")
if spec is None:
print("tomlkit 未安装,请先执行 pip install tomlkit")
else:
print(f"tomlkit 已安装,路径: {spec.origin}")
最后,建议在项目入口处尽早导入并处理异常。因为如果 TOML 文件语法错误,tomlkit.parse 会抛出 tomlkit.exceptions.ParseError,这个异常类不在标准库中,你需要显式捕获:
python
import tomlkit
from tomlkit.exceptions import ParseError
try:
with open("config.toml", "r", encoding="utf-8") as f:
config = tomlkit.load(f)
except ParseError as e:
print(f"TOML 解析失败: {e}")
except FileNotFoundError:
print("配置文件不存在")
注意 tomlkit.load 接受文件对象(已打开的文件),而 loads 接受字符串。两者返回值都是 TOMLDocument。从本节开始,后续所有示例都将基于 parse 或 loads 构建文档对象,确保你能看到注释和格式如何被保留。
核心对象与语法
安装 tomlkit 只需一行命令:pip install tomlkit。它不依赖任何第三方库,直接 import tomlkit 即可使用。本节我们重点掌握它的核心对象模型,以及两个最常用的函数:parse() 和 dumps()。
先看一段最基础的解析代码:
python
import tomlkit
raw_toml = """
title = "TOML Example"
[owner]
name = "Tom"
age = 30
[database]
ports = [8000, 8001, 8002]
active = true
"""
doc = tomlkit.parse(raw_toml)
print(type(doc)) # <class 'tomlkit.toml_document.TOMLDocument'>
print(type(doc["owner"])) # <class 'tomlkit.items.Table'>
print(type(doc["database"]["ports"])) # <class 'tomlkit.items.Array'>
print(type(doc["title"])) # <class 'tomlkit.items.String'>
tomlkit.parse() 接收一个字符串,返回 TOMLDocument 对象。它并不是普通的 dict,而是 dict 的子类,因此你可以用 doc["owner"] 的方式访问键值。但每个值的类型都经过包装:字符串是 String,数组是 Array,内嵌表是 Table。这种设计让 tomlkit 在重新序列化时能保留原始格式(注释、缩进、键的顺序)。
访问嵌套值也很直观。doc["owner"]["name"] 返回一个 String 对象,但你可以直接把它当作普通字符串使用:
python
print(doc["owner"]["name"]) # Tom
print(doc["owner"]["age"]) # 30(这是 Integer 类型)
注意:虽然 Table 支持 .get() 和 in 操作,但它与 dict 有一个关键差异------键顺序固定。tomlkit 严格记录插入顺序,这保证了写回文件时不会打乱原有结构。
创建文档同样简单。你可以从空文档开始,逐层添加内容:
python
from tomlkit import document, table, array, nl
doc = document()
doc["title"] = "My Config"
# 创建子表
owner = table()
owner["name"] = "Alice"
owner["age"] = 28
doc["owner"] = owner
# 添加数组
ports = array()
ports.extend([8080, 8081])
doc["server"] = table()
doc["server"]["ports"] = ports
# 添加空行优化可读性
doc.add(nl())
print(tomlkit.dumps(doc))
输出结果:
toml
title = "My Config"
[owner]
name = "Alice"
age = 28
[server]
ports = [8080, 8081]
这段代码展示了几个核心 API:document() 创建空文档,table() 创建空表,array() 创建空数组。nl() 是 tomlkit 独有的"换行符"对象,用于在序列化时插入空行。最关键的是 tomlkit.dumps():它接收 TOMLDocument(或任何 Item 容器),返回标准的 TOML 字符串。
序列化时有个易错点:不要用 str(doc) 。虽然 TOMLDocument 实现了 __str__,但它会丢失部分格式信息。始终使用 tomlkit.dumps(doc) 来保证输出符合 TOML 1.0 规范。
如果你想从已有的 dict 快速生成文档,可以直接赋值:
python
data = {"name": "Bob", "tags": ["a", "b"]}
doc2 = document()
doc2.update(data)
但注意:update() 不会递归转换内嵌字典。如果 data 里有 {"nested": {"key": 1}},内层 dict 会被当作普通值存储,序列化时会报错。正确做法是手动构建 Table,或使用 tomlkit.item() 显式转换:
python
from tomlkit import item
doc2["nested"] = item({"key": 1}) # 自动转为 Table
item() 是万能转换器:它能将 Python 原生类型(dict、list、str、int、bool、datetime 等)递归转换为对应的 tomlkit 对象。这是从普通数据结构构建 TOML 文档最省事的入口。
最后记住:parse() 和 dumps() 是双向的。dumps(parse(s)) 在理想情况下应返回与 s 等价的内容(注释和空白可能略有变动)。在日常开发中,你通常只需要 parse() 读配置、修改 doc 对象、再 dumps() 写回,完全不必关心 TOML 的底层语法细节。
常用 API 详解
先安装依赖:pip install tomlkit。所有 API 都从同一个顶层模块导入,无需额外子模块。
python
import tomlkit
raw = """\
[server]
host = "127.0.0.1"
port = 8080
[server.timeout]
connect = 3
read = 5
"""
parse() 接收字符串,返回 TOMLDocument 对象。它是 dict 的子类,但保留了注释、缩进和键值顺序,这是与 json.loads 返回普通字典最本质的区别。
python
doc = tomlkit.parse(raw)
print(type(doc)) # <class 'tomlkit.toml_document.TOMLDocument'>
print(doc["server"]["port"]) # 8080
注意 doc["server"] 返回的是 Table 对象而非普通 dict。若你只想要纯 Python 原生类型,用 tomlkit.loads(raw)------它内部调用 parse 后转为标准 dict,但代价是丢失格式信息。实际开发中,读配置用 loads 更省心,需要回写或保留格式则用 parse。
load() 与 dump() 对应文件操作。load 接受文件对象,dump 接受 TOMLDocument 和文件对象,返回写入的字符数。注意 dump 不会自动追加换行,需自行处理。
python
from io import StringIO
buf = StringIO()
tomlkit.dump(doc, buf)
buf.seek(0)
doc2 = tomlkit.load(buf)
assert doc2["server"]["host"] == "127.0.0.1"
接下来是文档的增删改查。add() 是 TOMLDocument 和 Table 共有的方法,返回被操作对象自身,因此可以链式调用。键值对会按添加顺序排列。
python
doc = tomlkit.document()
doc.add("title", "demo")
doc["server"] = tomlkit.table()
doc["server"].add("host", "localhost").add("port", 5432)
# 嵌套表用 append 或直接下标赋值
doc["server"]["timeout"] = {"connect": 3, "read": 5}
print(tomlkit.dumps(doc))
输出结果会保留你赋值时的层级结构。dumps() 与 dump() 类似,但返回字符串而非写文件。容易踩坑的是:add() 对已存在的键会抛 KeyError,而直接用下标赋值(如 doc["title"] = "new")则静默覆盖。要安全更新,推荐先判断再赋值或用 update()。
update() 接受 dict 或另一个 TOMLDocument,只更新已存在的键,不会新增。删除用 remove() 或 del。remove() 返回被删的值,且支持点路径:
python
doc = tomlkit.parse(raw)
doc["server"]["port"] = 9090 # 更新
doc.update({"server": {"host": "0.0.0.0"}}) # 递归更新,未提到的键保留
old_port = doc.remove("server.port") # 返回 8080
del doc["server"]["timeout"] # 等效删除
remove("server.port") 的点路径语法是 tomlkit 特有,普通 dict 不支持。若路径不存在会抛 NonExistentKey。批量更新多级配置时,update() 的递归行为比逐层赋值更安全------它不会误删同层其他键。
最后提一个易错点:TOMLDocument 继承自 dict,但 dict(doc) 会丢失表格类型信息,导致后续 add 方法不可用。需要复制时,用 tomlkit.parse(tomlkit.dumps(doc)) 做深拷贝,而不是 doc.copy()。
完整读写循环建议:parse -> 修改(add/update/remove) -> dumps 写回文件。这样既能保留原文件的注释与排版,又能精准改值。
完整小例子:配置文件读写
先准备一个带注释的示例配置 config.toml,内容覆盖字符串、数组、嵌套表和日期:
toml
# 服务基础配置
[server]
host = "127.0.0.1" # 监听地址
port = 8080
[server.ssl]
enabled = false
[database]
url = "postgresql://localhost/app"
pool_size = 10
[[items]]
name = "feature-a"
tags = ["ui", "backend"]
[[items]]
name = "feature-b"
tags = ["api"]
安装库(若未安装):pip install tomlkit。然后编写读写脚本 update_config.py:
python
from pathlib import Path
import tomlkit
CONFIG_PATH = Path("config.toml")
def load_config(path: Path) -> tomlkit.TOMLDocument:
"""读取 TOML 文件并返回 TOMLDocument 对象。"""
with path.open("r", encoding="utf-8") as f:
return tomlkit.parse(f.read())
def save_config(doc: tomlkit.TOMLDocument, path: Path) -> None:
"""将 TOMLDocument 写回文件,保留所有注释和格式。"""
with path.open("w", encoding="utf-8") as f:
f.write(tomlkit.dumps(doc))
# 读取现有配置
config = load_config(CONFIG_PATH)
print("原始端口:", config["server"]["port"])
# 修改已有值
config["server"]["port"] = 9090
config["server"]["ssl"]["enabled"] = True # 嵌套表直接赋值
# 添加新键(保留原有注释位置)
config["server"]["timeout"] = 30 # 自动追加到 server 表末尾
config["database"]["max_connections"] = 50
# 向数组表追加新元素
new_item = {"name": "feature-c", "tags": ["mobile"]}
config["items"].append(new_item)
# 删除不再需要的键
del config["database"]["pool_size"]
save_config(config, CONFIG_PATH)
print("更新完成,文件已写回。")
运行脚本后,config.toml 变为:
toml
# 服务基础配置
[server]
host = "127.0.0.1" # 监听地址
port = 9090
timeout = 30
[server.ssl]
enabled = true
[database]
url = "postgresql://localhost/app"
max_connections = 50
[[items]]
name = "feature-a"
tags = ["ui", "backend"]
[[items]]
name = "feature-b"
tags = ["api"]
[[items]]
name = "feature-c"
tags = ["mobile"]
关键语法点如下:tomlkit.parse() 返回 TOMLDocument,它继承自 dict,因此 config["server"]["port"] 的链式访问方式与普通字典一致。但内部元素是 tomlkit.items 模块中的专用类型(如 Integer、String),对这些元素赋值或读取时,tomlkit 会记录其原始行号、缩进和注释位置。写回时 tomlkit.dumps() 会按照这些元数据重建字符串,从而保留文件头部注释和行内注释。
注意两个易错点:第一,不要用 json.load() 或 configparser 读取 TOML 文件后再写回,那样会丢失全部注释和格式。第二,当你需要判断某个键是否存在时,使用 if "timeout" in config["server"]:,但新键的添加顺序会遵循 TOML 表内现有键的排列逻辑------新键追加到表末尾,而如果表本身有注释块,注释会保持在表头位置,不会被重复复制。若要精准控制新键插入到特定位置,可以使用 config["server"].insert(index, "key", value),其中 index 是整数位置,但日常追加场景更常用直接赋值。
另外,tomlkit 对日期时间、浮点数等类型的处理遵循 TOML 1.0 规范。例如修改布尔值时,直接赋 Python 的 True/False 即可,写回时会被序列化为 true/false(小写)。数组表的追加操作 config["items"].append(...) 会自动生成新的 [[items]] 段落,且不会干扰已有段落间的空行结构。实际项目中,你可以将 load_config 和 save_config 封装为工具函数,在应用启动时读取、运行时修改、退出前保存,实现无痛配置持久化。
进阶写法:保留格式与注释
先安装依赖:pip install tomlkit。上一节我们展示了 parse 与 loads 的差异,本节聚焦最关键的能力:原地修改文档对象 。tomlkit 的核心卖点在于它解析出的不是普通 dict,而是带有布局记忆的 TOMLDocument。直接对其赋值或调用方法,序列化时会保留原有注释、缩进和键值顺序。
python
import tomlkit
raw = """# 服务配置
[server]
host = "127.0.0.1" # 监听地址
port = 8000
[server.tuning]
workers = 4
"""
doc = tomlkit.parse(raw)
# 直接修改嵌套表的值
doc["server"]["port"] = 9000
# 为嵌套表新增键
doc["server"]["tuning"]["max_requests"] = 1000
# 添加新的嵌套表(自动创建中间层级)
doc["logging"] = {"level": "INFO", "file": "app.log"}
print(tomlkit.dumps(doc))
输出会保留原注释与 [server] 表头,仅改变 port 数值。注意 doc["logging"] = {...} 这里传入普通字典,tomlkit 会自动用 item() 包装成 InlineTable,序列化时表现为单行表 [logging] 还是内联表取决于构造方式------上述代码生成的是标准表头。若想强制内联,需显式调用 tomlkit.inline_table()。
关键点:不要用 doc = tomlkit.loads(tomlkit.dumps(doc)) 做"刷新" ,这会丢失所有格式元数据。正确做法是持有同一个 doc 对象反复修改,最后一次性 dumps。
数组追加同样安全。假设配置中有 allowed_hosts = ["localhost"]:
python
doc2 = tomlkit.parse("""
[web]
allowed_hosts = ["localhost"]
""")
# 方法一:直接 append(推荐)
doc2["web"]["allowed_hosts"].append("example.com")
# 方法二:先取出列表再整体赋值(会丢失注释)
hosts = doc2["web"]["allowed_hosts"]
hosts.append("test.dev") # 仍是原地操作
print(tomlkit.dumps(doc2))
tomlkit 的数组是 Array 对象,支持 append、extend、insert,且能记住每个元素后是否带逗号或注释。若你误用了 list(hosts) 转换,再赋回去,格式就没了。方法二中的 hosts 变量本身引用 Array,所以 append 依然原地生效。
对于自定义对象,item() 是统一入口。它能把 Python 原生类型或 tomlkit 容器包装成 TOML 元素。例如包装一个 datetime:
python
import datetime
from tomlkit import item
d = item(datetime.datetime(2026, 3, 1, 12, 0, 0))
print(d) # 2026-03-01T12:00:00
print(type(d)) # <class 'tomlkit.items.DateTime'>
item() 的规则:str → String,int → Integer,float → Float,bool → Bool,datetime 相关 → DateTime/Date/Time,dict → InlineTable,list → Array,None 会抛异常。手动调用 item() 的场景不多,但在构造复杂默认值时有用:比如想插入一个多行字符串而非普通字符串,需先 tomlkit.string("...", multiline=True),再赋给文档。
易错点一:不要对 TOMLDocument 使用 update() 方法 。它继承自 dict,但 update 会用普通 dict 覆盖内部结构,导致布局丢失。正确做法是逐键赋值。
易错点二:删除键用 del doc["key"] 没问题,但删除后空行可能残留。若想彻底清理,需操作 doc.body 内部结构,这超出本教程范围,日常直接 del 可接受。
易错点三:修改数组中的字符串时,doc["arr"][0] = "new" 会替换元素但保留其后的注释(如果有)。若该元素原本带注释,新值会继承原注释位置。这通常符合预期,但若不想保留,需先 del 再 insert。
最后,检查是否修改成功,不要用 == 比较两个 TOMLDocument------它没有实现值相等的语义化比较。直接比较 dumps 后的字符串,或者遍历键检查。保留格式的关键就一句话:始终在 parse 得到的原对象上操作,直到最终输出。
注意事项与常见坑
先明确一条边界:tomlkit 负责"读写并保留格式",而 Python 3.11+ 自带的 tomllib 只负责"只读解析"。两者不要混用。tomllib.loads() 返回的是普通 dict,所有键顺序、注释、缩进信息全部丢失;而 tomlkit.parse() 或 tomlkit.loads() 返回的是 TOMLDocument,它继承自 dict 但内部保存了原始布局。如果你用 tomllib 读文件,再用 tomlkit 去修改并写回,会丢掉所有注释和空白行。正确做法是:读和写都用 tomlkit。
python
import tomllib
import tomlkit
with open("config.toml", "rb") as f:
data = tomllib.load(f) # 纯 dict
data["new_key"] = 1
# 错误:data 是 dict,没有保留原格式
# with open("config.toml", "w") as f:
# tomlkit.dump(data, f) # 会丢失注释,且顺序可能变化
with open("config.toml", "r", encoding="utf-8") as f:
doc = tomlkit.load(f) # 保留格式
doc["new_key"] = 1
with open("config.toml", "w", encoding="utf-8") as f:
tomlkit.dump(doc, f)
第二个高频坑:日期时间类型。TOML 规范里有四种时间类型:offset datetime、local datetime、local date、local time。tomlkit 解析后返回的是 datetime.datetime、datetime.date、datetime.time 或 tomlkit.items.DateTime 等对象。如果你直接做字符串拼接或比较,容易出错。比如从配置里读出一个日期,想格式化输出,必须先确认类型。
python
import datetime
import tomlkit
doc = tomlkit.parse('start = 2026-01-15T08:30:00Z\n')
start = doc["start"]
print(type(start)) # <class 'datetime.datetime'>
print(start.year) # 2026
# 错误:直接 str() 会得到带 T 和 Z 的格式
# print("启动日:" + str(start.date())) # 可行但不够严谨
# 正确:显式转换
if isinstance(start, datetime.datetime):
print(start.strftime("%Y-%m-%d"))
注意 tomlkit.parse() 返回的顶层是 TOMLDocument,它实现了 dict 接口,但键顺序严格按文件中的出现顺序。如果你用 dict(doc) 强制转换,顺序会保留,但嵌套的 table 会变成普通 dict,内部顺序也可能丢失。更隐蔽的问题是:对不存在的键做 doc.get("missing", {}) 返回的是普通 dict,如果你再往里面赋值,写回文件时格式会和预期不同。推荐用 doc.setdefault() 或直接 doc["section"]["key"] = value 来创建。
python
import tomlkit
raw = """[server]
host = "localhost"
"""
doc = tomlkit.parse(raw)
# 错误:get 返回 dict,赋值后无法写回原文档
# port = doc.get("server", {}).setdefault("port", 8080) # 不会生效
# 正确:直接用下标创建
doc["server"]["port"] = 8080
print(tomlkit.dumps(doc))
# 输出:
# [server]
# host = "localhost"
# port = 8080
最后一个常见误区:不要用 str(doc) 或 str(tomlkit.dumps(doc)) 之外的任何方式直接序列化文档。TOMLDocument 的 __str__ 方法虽然能输出 TOML 文本,但如果你在 str() 之前对文档进行了某些操作(比如删除 key、修改数组),结果可能不合法。更安全的做法是始终用 tomlkit.dumps(doc) 获取字符串,再用 tomlkit.loads() 重新加载验证一次。另外,tomlkit 的数组默认是 Array 类型,支持 .append() 和 .extend(),但它不是普通 list,不要用 list.append 的返回值去赋值。比如 doc["list"].append(1) 返回 None,正确写法是直接调用,然后 tomlkit.dumps() 会反映修改。
python
import tomlkit
doc = tomlkit.parse('nums = [1, 2]\n')
arr = doc["nums"]
arr.append(3) # 正确:原地修改
arr.extend([4, 5]) # 正确
# 错误:arr = arr.append(6) # arr 变成 None
doc["nums"] = [1, 2, 3] # 也可以整体替换,但会丢失原格式
print(tomlkit.dumps(doc))
# nums = [1, 2, 3, 4, 5]
写回文件时,注意编码和换行符。tomlkit.dump() 默认用 \n 作为换行,如果你在 Windows 上打开原有文件是 \r\n,直接覆盖写会改变所有行尾。要么用二进制模式打开并保持原样,要么接受换行统一。此外,tomlkit 不会自动补全缺失的 table,比如 doc["a"]["b"]["c"] = 1 当 a 和 b 都不存在时会抛 KeyError,必须先逐级创建。
适用场景与总结
tomlkit 的定位非常明确:它不是通用 TOML 解析器,而是为"保留格式的编辑"而生的工具。理解这一点,你就能准确判断何时该用它,何时该换用 toml 或 tomli。
最适合的场景 首先是项目配置管理。当你需要为 Python 项目维护 pyproject.toml 时,tomlkit 几乎是唯一合理的选择。比如给 [project.optional-dependencies] 动态追加一个开发依赖组,或者修改 [tool.pytest.ini_options] 下的配置,直接操作字符串容易出错,而 toml.loads() 会丢失注释和空行,提交到 git 后 diff 会非常难看。tomlkit 的核心价值就在这里:
python
from tomlkit import parse
from tomlkit.container import Container
# 读取现有 pyproject.toml,保留所有注释与格式
with open("pyproject.toml", "r", encoding="utf-8") as f:
doc = parse(f.read())
# 确保 [tool.ruff] 表存在,不存在则创建(会插到文件末尾)
tool_ruff = doc.setdefault("tool", {}).setdefault("ruff", {})
tool_ruff["line-length"] = 120
# 修改 [project] 下的版本号,原位替换,注释不动
doc["project"]["version"] = "2.1.0"
# 写回文件,只有改动行发生变化
with open("pyproject.toml", "w", encoding="utf-8") as f:
f.write(doc.as_string())
注意 setdefault 的用法:tomlkit 的 Container 对象原生支持 setdefault,返回的是该 key 对应的 Table 对象。如果 tool 或 ruff 不存在,它会自动创建空表,但新表会追加到文档末尾,不会插入到逻辑位置。如果你在意顺序,应该先手动创建表再赋值。
第二个典型场景是 CI 脚本。比如在 GitHub Actions 或 GitLab CI 中,你需要根据环境变量调整构建参数,但不想把整个配置文件用模板引擎重写。tomlkit 可以像操作字典一样修改配置,然后原子写回。它也能处理 TOML 特有的类型------日期时间、浮点数、数组------不会像 json 模块那样把 1.0 变成 1。这一点在修改包含版本号或时间戳的配置时尤其重要。
不适合的场景 也很明确:超大型 TOML 文件,比如超过 10MB 的配置文件。tomlkit 的设计目标不是高性能,它要维护完整的语法树和格式信息,内存占用和解析速度都不如 tomli(只读)或 toml(读写但不保留格式)。如果你只需要读取配置、不修改,永远优先用 tomli,它的性能是 tomlkit 的几倍到几十倍。如果你需要频繁读写同一个超大文件,建议改为数据库或 YAML。
另一个容易踩的坑是混合使用 tomlkit 和 toml 库。如果你用 toml.load() 读入文件,再用 tomlkit 的 parse() 处理,两者返回的对象类型不兼容。toml.load() 返回普通 dict,而 tomlkit 返回 TOMLDocument(继承自 Container),后者的键访问返回的是 Item 子类(如 String、Integer),虽然支持 == 比较,但类型判断会失败。务必全程只用 tomlkit,不要混用。
还有一点:tomlkit 的 dumps() 方法接受 TOMLDocument 或 Container 对象,不接受普通 dict。如果你想从零构建一个 TOML 文件,用 tomlkit.document() 创建空文档,然后赋值:
python
from tomlkit import document, table, dumps
doc = document()
doc["title"] = "示例配置"
server = table(True) # True 表示内联表
server.add("host", "127.0.0.1")
server.add("port", 8080)
doc["server"] = server
print(dumps(doc))
# 输出:
# title = "示例配置"
# [server]
# host = "127.0.0.1"
# port = 8080
table(True) 创建的是标准表([server]),table(False) 或 inline_table() 则创建 {host = "..."} 形式的内联表。内联表不能跨行,且内部不能有注释,这在生成配置时容易忽略。
总结一下:如果你的代码需要"读懂并修改"一个已有的 TOML 文件,且希望改动最小化、保留原文件的所有注释和格式,选 tomlkit。如果你的场景是"程序启动时加载配置"或"批量处理大量 TOML",用 tomli 或 toml 更合适。最后记住一个判断标准:写 pyproject.toml 的自动化工具几乎都在用 tomlkit,这本身就是最好的背书。