Python 读写 YAML:pyyaml safe_load / dump 常用选项

Python 读写 YAML 文件用 PyYAML,核心只有两个函数:yaml.safe_loadyaml.dump

安装

pip install pyyaml

读取 YAML

# config.yaml
server:
  host: 127.0.0.1
  port: 8000

database:
  user: root
  password: secret123

names:
  - alice
  - bob
import yaml

with open("config.yaml", "r", encoding="utf-8") as f:
    data = yaml.safe_load(f)

print(data["server"]["host"])  # 127.0.0.1
print(data["names"])           # ['alice', 'bob']

safe_load 而不是 load——load 会执行 YAML 里嵌入的 Python 代码,存在安全风险。

写出 YAML

import yaml

data = {
    "server": {"host": "127.0.0.1", "port": 8000},
    "database": {"user": "root", "password": "secret123"},
    "names": ["alice", "bob"]
}

with open("output.yaml", "w", encoding="utf-8") as f:
    yaml.dump(
        data,
        f,
        allow_unicode=True,  # 中文不转成 \uXXXX
        sort_keys=False      # 保持原字典顺序
    )

默认 sort_keys=True,会按字母排序;allow_unicode=False 会把中文转成 \uXXXX——一般都要加这两个参数。

输出为字符串

yaml_str = yaml.dump(data, allow_unicode=True, sort_keys=False)
print(yaml_str)

修改 YAML 文件(读取后更新)

import yaml

with open("config.yaml", encoding="utf-8") as f:
    cfg = yaml.safe_load(f)

cfg["server"]["port"] = 9000
cfg["names"].append("carol")

with open("config.yaml", "w", encoding="utf-8") as f:
    yaml.dump(cfg, f, allow_unicode=True, sort_keys=False)

转成对象访问(点号语法)

如果不喜欢 data["server"]["host"],可以转成 SimpleNamespace

from types import SimpleNamespace
import yaml

def to_obj(d):
    if isinstance(d, dict):
        return SimpleNamespace(**{k: to_obj(v) for k, v in d.items()})
    if isinstance(d, list):
        return [to_obj(i) for i in d]
    return d

with open("config.yaml", encoding="utf-8") as f:
    config = to_obj(yaml.safe_load(f))

print(config.server.host)   # 127.0.0.1
print(config.database.user) # root

常见坑

名称含冒号的字符串要加引号,否则 YAML 解析出错:

# 错误
name: O:

# 正确
name: "O:"

字段名问题:Clash Meta 等工具要求字段名是 port,不能是 server_port。读取 sing-box 格式(用 server_port)再写入 Clash 配置时记得手动映射:

clash_node = {
    "name": node["tag"],
    "server": node["server"],
    "port": node["server_port"],  # 注意字段名
    "type": node["type"],
}

直接 yaml.dump(singbox_node) 生成的字段名 Clash 不认,会报 has unset fields: port