Python ConfigParser 键名:Path 和 path 为什么会被判成同一个配置项
一份工具配置同时出现 Path 和 path,开发者认为它们是两个名字,ConfigParser 却报告重复选项;只保留一个再写回,首字母又变成小写。这不是写文件时随机改了拼写,而是选项名称在进入解析器时就经过了归一化。修复前先确定配置格式究竟要不要区分大小写。
下面代码在 CPython 3.12.14 执行,只处理内存文本。保存为 demo.py,运行 python demo.py。示例分别建立默认解析器和保留大小写的解析器,避免同一个对象先读入数据、再改规则,让已有名称和后来名称处在不同约定下。
默认归一化也参与重复判断
optionxform 默认把选项名称转成小写,因此同一节里的 Path 和 path 进入同一规范名称。默认严格模式把单份输入中的这种重复视为错误。代码明确捕获 DuplicateOptionError,验证问题发生在选项身份判断阶段,而不是路径值本身有什么非法字符。
AI概念示意图:两种原始钥匙经过归一化通道成为同一形状,旁边的通道保留两种区别。图形只辅助理解名称规则。
from configparser import ConfigParser, DuplicateOptionError
from io import StringIO
text = "[Job]\nPath = upper\npath = lower\n"
try:
ConfigParser().read_string(text)
except DuplicateOptionError:
print("default: duplicate")
else:
raise AssertionError("case collision was accepted")
exact = ConfigParser()
exact.optionxform = str
exact.read_string(text)
assert list(exact["Job"]) == ["Path", "path"]
assert exact["Job"]["Path"] == "upper"
assert exact["Job"]["path"] == "lower"
assert "PATH" not in exact["Job"]
output = StringIO()
exact.write(output)
assert "Path = upper\n" in output.getvalue()
assert "path = lower\n" in output.getvalue()
print("preserved:", list(exact["Job"]))
late = ConfigParser()
late.read_string("[Job]\nDataRoot = /demo\n")
late.optionxform = str
assert list(late["Job"]) == ["dataroot"]
assert "DataRoot" not in late["Job"]
assert late["Job"]["dataroot"] == "/demo"
print("late change:", list(late["Job"]))保留拼写,也会改变查找契约
设置 optionxform = str 后,解析器保留原名称,Path 和 path 可同时存在。注意赋的是可调用对象 str,没有在此处调用它。循环列出两个键,分别取回 upper 与 lower;接着用 StringIO 写出配置,再检查两行都保留了各自的拼写。
此时 PATH 不再自动等价于 Path。迁移既有程序时,需要同时检查配置生产者、读取代码和默认项,不能只看输出文件好看了就结束。若使用者原本可以随意写大小写,改成严格区分会改变接口;应先整理实际使用的键名,再决定采用哪一种约定。
键名变换发生在读取、获取和设置等操作中,不只是序列化时的装饰。自定义函数应具有幂等性:对已经规范化的名称再调用一次,结果保持不变。若每次都追加后缀,同一个键在不同操作中可能越变越长,这会破坏稳定查找。
读完以后改规则,无法找回原拼写
最后一组先用默认规则读取 DataRoot,再把 optionxform 换成 str。列表里仍然只有 dataroot,DataRoot 查不到。原始大小写在第一次归一化时已经丢失,后改函数不会从内部名称推回输入文本;正确做法是建立配置好规则的新解析器,重新读取可信原文。
也不建议为了消除报错直接关闭 strict。宽松模式会允许同一规范键被后来的值覆盖,这会把本应发现的两项配置压成一项。只有格式明确规定覆盖规则时才考虑这种行为,并测试最后保留哪个值。示例保持严格检查,让冲突在启动时就暴露出来。
名字规则与配置层次分开处理
optionxform 处理选项名,并不把节名一起转成小写。本例使用 Job 节,不能据此认为 job 一定是相同入口。读取多个配置来源时,后一个来源覆盖前一个来源还有另外的规则;单份输入中的重复检查,不能替代多来源优先级的设计。
上线前至少保留原名读回、不同大小写是否等价、大小写冲突、写回结果四类样本。如果配置里还用名称引用其他选项,引用规则也应一起检查。先把“哪些名字代表同一个配置项”写清楚,路径、数值和业务校验才能建立在稳定的字段身份上。


