创建时间: 2026-08-28最后更新: 2026-08-28

1. TOML

Python 项目里经常会出现 pyproject.toml、ruff.toml、config.toml。它们的文件名不同,但都使用 TOML 语法。

TOML 是一种配置文件格式。它的目标把一组有相对比较复杂的数据保存下来,让程序或开发工具读取。例如,一个 FastAPI 服务可能需要这些配置:

config.toml
1
[app]
2
name = "FastAPI LangChain"
3
debug = true
4
5
[server]
6
host = "127.0.0.1"
7
port = 8000

从人的视角看,这份文件分成了应用和服务器两组配置;从 Python 的视角看,它最终会变成一层层字典:

result.py
01
{
02
"app": {
03
"name": "FastAPI LangChain",
04
"debug": True,
05
},
06
"server": {
07
"host": "127.0.0.1",
08
"port": 8000,
09
},
10
}

因此,学习 TOML 时可以始终抓住一个核心:TOML 正在描述一棵由键、值、字典和列表组成的数据结构。

2. 读取

首先,我们要了解的是项目是如何读取 .toml 配置文件的

文件或区域读取者是否需要应用自己接入
pyproject.toml 中的 [project]uv、构建工具等不需要,运行对应工具时读取
pyproject.toml 中的 [tool.fastapi]FastAPI CLI不需要,执行 fastapi dev 或 fastapi run 时读取
自定义 config.toml没有默认读取者需要使用 tomllib 或配置库显式接入

默认的 fastapi 项目中,有一个叫做 pyproject.toml 的配置文件:

pyproject.toml
1
[tool.fastapi]
2
entrypoint = "app.main:app"

当我们执行下面的命令时,FastAPI CLI 会读取这段配置,找到 app/main.py 导出的 app 对象:

start-fastapi.bash
1
uv run fastapi dev

至于 .env 配置,我们之前已经在 app/core/config.py 中接入了 .env:

app/core/config.py
01
from functools import lru_cache
02
03
from pydantic_settings import BaseSettings, SettingsConfigDict
04
05
06
class Settings(BaseSettings):
07
model_config = SettingsConfigDict(
08
env_file=".env",
09
env_file_encoding="utf-8",
10
extra="ignore",
11
)
12
13
app_name: str = "FastAPI LangChain"
14
deepseek_model: str = "deepseek-v4-flash"
15
16
17
@lru_cache
18
def get_settings() -> Settings:
19
return Settings()

这里不是 FastAPI 自动读取 .env,而是创建 Settings() 时,Pydantic Settings 根据 env_file 读取文件并校验字段。@lru_cache 让同一进程复用配置对象,避免每个请求都重新读取。

如果我们新增一个位于项目根目录的 config.toml,最直接的接入方式是标准库 tomllib:

app/core/config_loader.py
01
from pathlib import Path
02
from typing import Any
03
import tomllib
04
05
PROJECT_ROOT = Path(__file__).resolve().parents[2]
06
CONFIG_PATH = PROJECT_ROOT / "config.toml"
07
08
09
def load_config() -> dict[str, Any]:
10
with CONFIG_PATH.open("rb") as file:
11
return tomllib.load(file)

然后在创建应用时主动调用它:

app/main.py
01
from fastapi import FastAPI
02
03
from app.core.config_loader import load_config
04
05
config = load_config()
06
app_config = config["app"]
07
08
app = FastAPI(
09
title=app_config["name"],
10
debug=app_config["debug"],
11
)

这段代码能说明读取过程,但直接操作多层字典不适合大型项目。后面的“应用配置”和“环境”两篇会把 TOML 接入 Pydantic Settings:由 TomlConfigSettingsSource 负责读取,再由 Pydantic 校验字段和类型。它仍然需要我们显式配置,只是不必自己维护解析和类型转换逻辑。

tomllib 从 Python 3.11 开始进入标准库。当前项目使用 Python 3.13,可以直接导入,无需安装依赖。Python 3.13 的 tomllib 解析 TOML 1.0.0,读取文件时必须使用二进制模式 rb。仍在使用 Python 3.10 的项目可以安装兼容包 tomli,但当前项目不需要。

读取已经在内存中的 TOML 字符串时,使用 loads():

read_string.py
1
import tomllib
2
3
content = """
4
[server]
5
port = 8000
6
"""
7
8
config = tomllib.loads(content)
9
print(config["server"]["port"])

load() 接收文件对象,loads() 接收字符串,这与 json.load() 和 json.loads() 的区别相似。

解析失败时,tomllib 会抛出 TOMLDecodeError:

handle_error.py
01
from pathlib import Path
02
from typing import Any
03
import tomllib
04
05
06
def load_config(path: Path) -> dict[str, Any]:
07
try:
08
with path.open("rb") as file:
09
return tomllib.load(file)
10
except FileNotFoundError as error:
11
raise RuntimeError(f"配置文件不存在:{path}") from error
12
except tomllib.TOMLDecodeError as error:
13
raise RuntimeError(f"TOML 语法错误:{error}") from error

标准库 tomllib 只负责读取,不提供写入功能。需要生成 TOML 时可以使用 tomli-w;需要在修改文件时保留注释和排版,可以使用 tomlkit。FastAPI 服务通常在启动时读取配置,而不是在运行过程中反复改写配置文件,所以只读能力已经覆盖大多数场景。

3. 基础值

TOML 最基本的写法是 键 = 值。等号左侧是配置名称,右侧是配置内容:

values.toml
1
title = "FastAPI LangChain"
2
port = 8000
3
temperature = 0.7
4
debug = false

键名区分大小写。model 和 MODEL 是两个不同的键,不建议在同一个项目中混用大小写风格。

不加引号的键只能使用英文字母、数字、下划线和连字符。键名包含空格、点号等特殊字符时,需要加引号:

keys.toml
1
model_name = "deepseek-v4-flash"
2
"request timeout" = 30
3
"api.example.com" = "enabled"

引号只是键名语法的一部分。"api.example.com" 是一个完整键;不加引号的 api.example.com 则是点号键,会创建嵌套结构。

常用值可以对应到下面这些 Python 类型:

TOML示例Python
字符串model = "deepseek-v4-flash"str
整数port = 8000int
浮点数temperature = 0.7float
布尔值debug = truebool
带时区日期时间released_at = 2026-08-27T10:30:00Zdatetime
本地日期release_date = 2026-08-27date
本地时间start_at = 10:30:00time
数组origins = ["http://localhost:3019"]list
表[server]dict

字符串

双引号字符串支持 \n、\t 等转义字符;单引号字符串更接近「原样保存」,适合正则表达式或 Windows 路径:

strings.toml
1
message = "第一行\n第二行"
2
pattern = '^/api/v1/'
3
windows_path = 'C:\projects\fastapi-langchain'

多行文本可以使用三个双引号或三个单引号。配置中只有确实存在较长文本时才需要这样做,普通模型名称、URL 和文件路径使用单行字符串更容易阅读。

数字与布尔值

整数和浮点数不加引号。加上引号后,它们就会变成字符串:

numbers.toml
1
port = 8000
2
port_text = "8000"
3
temperature = 0.7
4
enabled = true

TOML 的布尔值只能写成小写的 true 或 false。True、FALSE、yes 都不是合法布尔值。

TOML 没有 null。一个字段如果没有值,通常有三种处理方式:不写该字段、提供明确默认值,或者使用业务约定的空字符串。不要写成 api_key = null,解析器会直接报错。

4. 数组与表

数组用方括号表示,元素之间使用逗号分隔:

arrays.toml
1
allow_methods = ["GET", "POST", "OPTIONS"]
2
retry_delays = [0.5, 1.0, 2.0]

数组可以分成多行,并允许最后一个元素后保留逗号。这种写法更适合依赖列表和较长的域名列表:

multiline-array.toml
1
allow_origins = [
2
"http://localhost:3019",
3
"https://langchainpy.usehook.cn",
4
]

TOML 1.0 允许数组包含不同类型,但应用配置中通常不建议这样做。保持同一个数组元素类型一致,更容易交给 Pydantic 校验,也更方便读者理解字段含义。

表使用 [名称] 声明。它相当于 Python 字典中的一层键:

tables.toml
1
[server]
2
host = "127.0.0.1"
3
port = 8000
4
5
[server.cors]
6
allow_credentials = false
7
allow_origins = ["http://localhost:3019"]

解析后,cors 会成为 server 下面的子字典:

tables-result.py
01
{
02
"server": {
03
"host": "127.0.0.1",
04
"port": 8000,
05
"cors": {
06
"allow_credentials": False,
07
"allow_origins": ["http://localhost:3019"],
08
},
09
},
10
}

点号键也可以创建嵌套结构:

dotted-keys.toml
1
server.host = "127.0.0.1"
2
server.port = 8000

它与 [server] 表达的结果相同,但一个文件中最好采用一致的组织方式。配置较多时,使用表通常更清晰。

内联表

少量、紧密相关的数据可以写成内联表:

inline-table.toml
1
database = { host = "localhost", port = 5432, name = "agent" }

内联表必须在一行内写完,也不能在末尾保留多余逗号。如果内容开始变长,应改成普通表。

表数组

双层方括号 [[...]] 表示「由多个表组成的数组」。例如配置多个模型:

model-list.toml
1
[[models]]
2
name = "deepseek-v4-flash"
3
provider = "openai"
4
5
[[models]]
6
name = "deepseek-v4-pro"
7
provider = "openai"

解析结果是一个字典,其中 models 对应一个列表,列表中的每一项都是字典。这种结构适合真正需要配置多份同类对象的场景。如果应用永远只使用一个默认模型,普通的 [model] 表更简单。

5. 选择

TOML、JSON、YAML 和 .env 都能保存配置,但它们解决的问题并不完全相同:

格式更适合局限
TOMLPython 项目配置、工具配置、层次清晰的应用默认值不支持 null,复杂数据能力有限
JSONAPI 数据交换、程序生成的数据不支持注释,手写长配置较累
YAMLKubernetes、CI 等复杂声明文件缩进和隐式类型更容易造成误解
.env密钥、部署环境变量、少量覆盖值只有字符串,不适合复杂嵌套结构

在我们的 FastAPI + LangChain 项目中,会采用这样的分工:

  • pyproject.toml 保存项目元数据、依赖和工具配置;
  • config.toml 保存可以提交到仓库的应用默认配置;
  • .env 保存本机或部署环境提供的密钥与覆盖值;
  • uv.lock 保存 uv 解析出的精确依赖版本,由 uv 自动维护。

配置格式不是越统一越好。把 API Key 写入 pyproject.toml,或把几十项嵌套配置压成 .env 字符串,都会让职责变得更混乱。

6. 错误

初学 TOML 时,最常见的错误不是字段含义,而是把其他格式的习惯带进来。

重复定义

同一个键或表不能定义两次:

duplicate.toml
1
# 错误示例
2
[server]
3
port = 8000
4
5
[server]
6
host = "127.0.0.1"

应把字段写进同一个 [server] 中。

缺少引号

普通字符串必须加引号:

invalid-string.toml
1
# 错误示例
2
model = deepseek-v4-flash

deepseek-v4-flash 不是数字、布尔值或日期,解析器无法判断它是什么。

错误注释

TOML 只使用 # 注释,不使用 JavaScript 的 //:

comments.toml
1
# 这是合法注释
2
port = 8000 # 行尾也可以注释

路径与当前目录

Path("config.toml") 是相对于进程当前工作目录,而不是相对于 config.py 文件。开发环境从项目根目录启动时可能正常,测试或部署切换工作目录后就可能找不到文件。后续文章会使用明确的项目根路径解决这个问题。

配置不是自动生效的

即使 TOML 能成功解析,拼错字段名也不一定立即报错。直接读取字典时,错误可能一直拖到业务代码访问该字段才出现。生产项目应使用 Pydantic 模型校验配置的字段、类型和范围。

正在验证登录状态
请稍候,验证完成后将继续显示文章内容