Python JSON 配置读取:让错误在程序启动时就被发现
配置文件能把参数和业务代码分开,但“能解析为 JSON”并不等于“配置可用”。例如,端口写成字符串、根节点误写成数组、超时时间为负数,都可能在程序运行很久之后才触发难懂的错误。对小型脚本来说,在启动时完成一层显式校验,往往比引入复杂框架更容易维护。
一、先定义配置契约
假设程序只需要 host、port 和 timeout 三项。约定 host 为非空字符串,port 为 1 到 65535 的整数,timeout 为大于零且有限的数字。host 与 port 必填,timeout 缺省取 5 秒。下面的示例只依赖 Python 标准库,不调用任何云服务,也不需要密钥。
二、区分读取失败与解析失败
from pathlib import Path
import json
import math
def load_config(path):
try:
text = Path(path).read_text(encoding="utf-8")
except (OSError, UnicodeError) as exc:
raise ValueError("配置文件无法读取或不是 UTF-8 编码") from exc
try:
cfg = json.loads(text)
except json.JSONDecodeError as exc:
raise ValueError(f"JSON 格式错误:第 {exc.lineno} 行,第 {exc.colno} 列") from exc
if not isinstance(cfg, dict):
raise ValueError("配置根节点必须是对象")
host = cfg.get("host")
port = cfg.get("port")
timeout = cfg.get("timeout", 5)
if not isinstance(host, str) or not host.strip():
raise ValueError("host 必须是非空字符串")
if type(port) is not int or not 1 <= port <= 65535:
raise ValueError("port 必须是 1 到 65535 的整数")
if type(timeout) not in (int, float):
raise ValueError("timeout 必须是数字")
try:
valid = timeout > 0 and math.isfinite(timeout)
except OverflowError:
valid = False
if not valid:
raise ValueError("timeout 必须是有限的正数")
return {"host": host.strip(), "port": port, "timeout": timeout}
这里没有把配置原文拼进错误消息,避免把可能出现的口令写入日志。端口判断使用 type(port) is int,而不是 isinstance(port, int),是因为 Python 的 bool 是 int 的子类,true 不应被当成端口 1。timeout 同样排除了布尔值,并显式拒绝无穷大与 NaN;Python 的 JSON 解码器默认可能接受这些非标准常量,不能只依赖语法解析。
三、用最小样例检查边界
正常文件可以写为:
{"host": "127.0.0.1", "port": 8080}
随后分别把 port 改成 "8080"、true、0 和 65536,把 timeout 改成 -1 或 "5",确认每种错误都有可理解的提示。也要检查空文件、数组根节点和乱码文件。示例采用严格类型策略,不自动把字符串转数字,这是为了尽早暴露手工编辑时的错误。
四、明确这不是完整配置框架
这个函数没有检查重复键和未知字段,也没有实现环境变量覆盖。若团队需要禁止拼写错误导致的多余字段,可以先比较字段集合,再决定报错还是兼容;涉及多个环境时则应明确覆盖顺序,而不是悄悄采用某个值。密码、令牌不宜随普通配置提交代码仓库。
小结
把读取、解析、结构校验与业务使用分开,能让故障更靠近原因发生的位置。即使只有几十行的脚本,也值得在启动阶段明确类型、范围和默认值。配置校验越清楚,后续排查日志时就越少依赖猜测。
- 点赞
- 收藏
- 关注作者
评论(0)