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 必须双引号,写起来有点啰嗦。

⚠️ 踩坑记录:我曾经在一个项目里用 JSON 管理数据集路径,有同事误把 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,否则还得做一次转换。

一条经验原则:如果配置只给你自己看,选你看着顺眼的;如果团队用,选 YAML(大部分人能看懂);如果机器用,选 JSON。

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 中的位置

下面这张图展示了从用户输入到模型训练的完整配置流转路径。记住,配置文件不是终点,它最终要被你的代码“吃掉”,变成内存中的字典或对象。

配置在训练 Pipeline 中的流转与选择 配置文件源 argparse JSON YAML TOML Python Dict 或 Namespace DataLoader / Optimizer / Model 箭头表示数据流向:配置文件 → 解析 → 内存对象 → 下游训练组件

看完这张图,你应该明白了:无论你选哪种格式,最终都会落地成 Python 对象。格式的选择影响的是编写体验、可维护性和团队协作效率,不影响 Pipeline 的核心逻辑。

2.7 小结

这一章我们不是单纯罗列语法,而是从“解决问题”的角度出发,对比了 argparse、JSON、YAML、TOML 的优缺点。我个人在项目中通常这样取舍:

  • 小脚本 → argparse
  • 复杂训练 → YAML(加上注释和分层结构)
  • 需要与外部系统交换 → JSON
  • Rust/C++ 混合项目 → TOML