2、配置文件的作用与常见格式:YAML、JSON、TOML、argparse 的对比与选择
不知道你有没有这种经历:调一个 Batch Size,改完代码忘了改回,跑了一整天才发现。或者一份超参数散落在脚本的七八个地方,改一个漏一个。嗯,这些坑我都踩过。配置文件就是来解决这些问题的——把可变参数从代码中剥离开,集中管理。
今天这一章,我们就把主流的四种配置方式掰开揉碎聊透了。它们分别是 YAML、JSON、TOML 和 argparse。我不会只说“各有利弊”,而是结合我自己的生产经验,告诉你什么场景选哪个。
2.1 配置文件到底解决了什么问题
先想一下:没有配置文件,Pipeline 长什么样?
def train(batch_size=32, lr=1e-3, epochs=100, ...):
pass
每次想跑新实验就得改函数参数,或者靠环境变量传递。一旦参数超过五六个,代码里就全是魔法数字。万一提交 Git 时没注意,带上了调试用的 batch_size=8,队友就得帮你擦屁股。
配置文件把“这轮的实验参数”和“算法逻辑”彻底解耦。你跑实验时只需要更换一个文件,或者传一个路径。说白了,配置就是实验的 DNA,保存下来就能复现。
config.yaml,训练完成后自动归档。三个月后回看,一眼就知道当时用了什么参数。
2.2 四大配置方式逐个看
2.2.1 argparse —— 最简单直接的 CLI 方案
argparse 是 Python 自带的命令行解析库。你想想看,每次执行 python train.py --batch_size 32,背后就是它在工作。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--batch_size', type=int, default=32)
parser.add_argument('--lr', type=float, default=1e-3)
args = parser.parse_args()
优点?零依赖,上手即用。但缺点也很明显:参数多了(比如上百个超参数),命令行会长得像裹脚布;而且你不能在脚本间共享同一份配置。
2.2.2 JSON —— 通用但“刻板”
JSON 几乎无处不在。它的数据结构清晰,跟 Python 的 dict 天然对应。
{
"batch_size": 32,
"lr": 1e-3,
"optimizer": {
"name": "Adam",
"beta1": 0.9,
"beta2": 0.999
}
}
不过,JSON 不支持注释。当你需要解释某个参数为什么取这个值时,就只能写外部文档。另外,它要求 key 必须双引号,写起来有点啰嗦。
trailing comma 加进去了,解析直接崩溃。JSON 对格式要求极为严格。
2.2.3 YAML —— 可读性的王者
YAML 是我个人最常用的配置格式。它用缩进表示层级,支持注释,看着就像一份文档。
batch_size: 32
lr: 1e-3
optimizer:
name: Adam
beta1: 0.9
# Adam 的默认 beta2 在 0.999 附近
beta2: 0.999
# 实验备注:用这个 batch size 是为了适配 A100 显存
notes: "初赛 baseline"
可读性无敌,嵌套结构一目了然。但 YAML 的缩进规则很敏感:使用空格还是 Tab、缩进几格,稍不留神就报错。另外,它会把 1e-3 自动转为字符串,需要显式声明类型。
2.2.4 TOML —— 专为配置而生
TOML 是后起之秀,Rust 社区用得很多。它的语法有点像 INI,但更强大。
[train]
batch_size = 32
lr = 1e-3
[optimizer]
name = "Adam"
beta1 = 0.9
beta2 = 0.999
TOML 支持原生类型(整数、浮点、日期等),也支持注释。它的表(Table)结构清晰,没有 YAML 的缩进陷阱。但是,嵌套超过三层时,写起来会有些烦躁。
2.3 四者对比一览
| 特性 | argparse | JSON | YAML | TOML |
|---|---|---|---|---|
| 学习成本 | 极低 | 低 | 中 | 中 |
| 可读性(人类) | 差(命令行) | 中 | 优 | 良 |
| 注释支持 | 无(可借助 # 在 shell 注释) | 否 | 是(#) | 是(#) |
| 嵌套结构 | 不擅长 | 良好 | 极佳 | 良好(表嵌套) |
| 类型系统 | 弱(靠 type 参数) | 强(数字、字符串、null) | 弱(自动推断,有坑) | 强(原生类型) |
| Python 解析库 | argparse(内置) | json(内置) | PyYAML / ruamel.yaml | tomli / tomllib (3.11+) |
| 常见场景 | 快速原型、少量参数 | API 交互、序列化 | 复杂超参数、多环境配置 | 项目级配置、rust 生态 |
2.4 我如何选择?—— 三个决策指南
你可能会问:到底用哪个?我建议按下面三步走。
2.4.1 参数数量 ≤ 5,且是临时脚本
直接用 argparse,省事。比如你写个数据预处理的小工具,传个输入路径和输出路径就够了。
2.4.2 需要长期维护,参数可能超过 20 个
上 YAML。或者如果你特别讨厌缩进陷阱,就用 TOML。我个人的习惯:训练脚本用 YAML,工具链配置用 TOML。为什么?训练脚本往往要写注释解释超参数含义,YAML 的 # 很方便。工具链配置(比如模型结构、输入输出路径)结构固定,TOML 更安全。
2.4.3 配置文件需要与外部系统交换
用 JSON。比如你对接 MLflow 或者云端调度平台,它们普遍接受 JSON 格式。这时候别硬上 YAML,否则还得做一次转换。
2.5 避坑指南(我踩过的雷)
讲几个真实案例,希望能帮你省点 Debug 时间。
- YAML 缩进混用 Tab 和空格:我曾经花了半小时才找到问题——一个参数的缩进用了 Tab,其他都是空格。现在我的编辑器会自动把 Tab 转成空格。
- JSON 最后多一个逗号:虽然 Python 的
json.loads会直接抛异常,但如果你从其他语言生成的 JSON 文件,末尾逗号还挺常见的。记得加个json.loads(text, strict=False)?不行,Python 不允许。只能自己写预处理去掉它。 - argparse 参数名冲突:
--config和--conf容易混淆。我建议团队统一前缀或使用短选项时小心。 - TOML 的浮点数科学计数法:TOML 支持
1e-3,但某些解析器会把正负号解析成加减运算符。尽量用0.001代替。
2.6 整个流程:配置在 Pipeline 中的位置
下面这张图展示了从用户输入到模型训练的完整配置流转路径。记住,配置文件不是终点,它最终要被你的代码“吃掉”,变成内存中的字典或对象。
看完这张图,你应该明白了:无论你选哪种格式,最终都会落地成 Python 对象。格式的选择影响的是编写体验、可维护性和团队协作效率,不影响 Pipeline 的核心逻辑。
2.7 小结
这一章我们不是单纯罗列语法,而是从“解决问题”的角度出发,对比了 argparse、JSON、YAML、TOML 的优缺点。我个人在项目中通常这样取舍:
- 小脚本 → argparse
- 复杂训练 → YAML(加上注释和分层结构)
- 需要与外部系统交换 → JSON
- Rust/C++ 混合项目 → TOML