PyYAML load() 报“unsafe load”错误是因为默认禁用不安全解析器,防止执行任意Python对象;应改用 safe_load(),它仅支持基础类型且拒绝自定义标签。

PyYAML load() 为什么报错 “unsafe load”
新版 PyYAML 默认禁用不安全的解析器,直接调用 load() 会抛出 yaml.constructor.ConstructorError 或提示 “unsafe load”,不是你写错了,是它故意拦住你。
根本原因是 load() 允许执行任意 Python 对象(比如 !!python/object:os.system),有严重安全隐患。生产环境必须绕过它,而不是降级 PyYAML 版本。
- 安全替代方案:一律改用
safe_load()—— 它只支持基础类型(str、int、list、dict等),拒绝所有自定义标签和对象构造 - 如果真要解析带自定义标签的 YAML(极少见),得显式传入
Loader=yaml.CLoader或自定义SafeLoader子类,但绝大多数项目不需要 - 别用
load(stream, Loader=yaml.Loader),这等于手动开后门,跟不用safe_load()没区别
读取文件时 FileNotFoundError 或编码乱码怎么办
YAML 文件路径写错、没权限、或含中文路径/内容时,open() 就会先崩,根本轮不到 safe_load() 发挥。
常见错误现象包括:FileNotFoundError、UnicodeDecodeError、读出来字段全空、中文变成 \u4f60\u597d。
立即学习“Python免费学习笔记(深入)”;
- 务必用
with open("config.yaml", encoding="utf-8") as f:显式指定编码,Windows 上默认cp1252很容易炸 - 路径别硬写相对路径,优先用
pathlib.Path(__file__).parent / "config.yaml",避免脚本挪位置就打不开 - 如果 YAML 内容来自网络或用户上传,先检查
bytes是否以 UTF-8 BOM 开头,必要时用content.decode("utf-8-sig")
safe_load() 返回的是 dict,但嵌套太深怎么安全取值
YAML 解析后就是普通 Python dict 和 list,没有“YAML 对象”的概念。但深层嵌套时,直接链式访问如 data["db"]["host"]["port"] 容易触发 KeyError 或 TypeError。
这不是 PyYAML 的问题,是你没处理字典层级缺失。别靠 try-except 包裹每一层。
- 用
dict.get()链式兜底:data.get("db", {}).get("host", {}).get("port", 5432) - 更清晰的做法:提前校验结构,比如用
pydantic.BaseModel或voluptuous做 schema 断言,一加载就报错在哪少字段 - 注意
None和空字符串的区别——YAML 里port:和port: null都解析为None,但业务上可能要区分“未配置”和“显式置空”
dump() 写出的 YAML 为啥缩进混乱、没换行、还带单引号
dump() 默认输出紧凑、无排序、自动加引号,看着不像人写的 YAML。这不是 bug,是默认行为追求可解析性而非可读性。
尤其当字典 key 含特殊字符、或 value 是布尔/数字时,PyYAML 会主动加引号防歧义,但你可能只想导出配置给运维看。
- 加
default_flow_style=False强制块格式,避免{a: 1, b: 2}这种一行写法 - 加
indent=2控制缩进(默认是 2,但显式写上更稳) - 加
allow_unicode=True保证中文不转义成\u4f60 - 如果想让 key 保持插入顺序(Python 3.7+ dict 本身有序),不用额外操作;但旧版本需用
collections.OrderedDict
示例:yaml.dump(data, stream, default_flow_style=False, indent=2, allow_unicode=True)
复杂点在于:YAML 规范本身允许多种等价表示(如 true、True、on 都算布尔真),PyYAML 输出时选哪个,取决于输入值的 Python 类型,而不是你想让它显示成啥样——这点容易被忽略,调试时看到输出和预期不符,先查原始 dict 里存的是 True 还是字符串 "true"。










