Python で YAML ファイルを読み書きする(PyYAML)
YAML は設定ファイルや構成管理で広く使われるデータフォーマットだ。JSON と比べてインデントベースで人間が読みやすく、コメントも書ける。Python では PyYAML ライブラリを使って YAML ファイルの読み書きを行う。
インストール
PyYAML は標準ライブラリに含まれていないため、pip でインストールする必要がある。
pip install pyyamlインストール後は import yaml で使えるようになる。モジュール名がパッケージ名と異なる点に注意しておこう。
YAML ファイルを読み込む
yaml.safe_load() を使うと、YAML 文字列を Python のオブジェクト(辞書やリスト)に変換できる。
import yaml
with open("config.yaml", "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
print(data)
たとえば次のような YAML ファイルがあるとする。
database:
host: localhost
port: 5432
name: myapp
logging:
level: DEBUG
file: app.logこれを safe_load() で読み込むと、ネストされた辞書として取得できる。
# 結果
{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"logging": {
"level": "DEBUG",
"file": "app.log"
}
}
YAML の型変換は自動で行われる。文字列はそのまま str に、数値は int や float に、true/false は bool にそれぞれマッピングされる。
safe_load と load の違い
yaml.load() は任意の Python オブジェクトをデシリアライズできるため、悪意ある YAML を読み込むとコード実行の脆弱性が生じる。安全な yaml.safe_load() を使うのが原則だ。
基本的な型(辞書、リスト、文字列、数値、真偽値、None)のみを生成する。外部から受け取った YAML を安全に処理できる
任意の Python オブジェクトを構築でき、悪意のある入力でコードが実行される危険がある。明確な理由がない限り使うべきではない
どうしても load() を使う場合は、Loader 引数を明示的に指定する。
# 非推奨だが必要な場合
data = yaml.load(content, Loader=yaml.SafeLoader)
# これは safe_load() と等価
YAML ファイルに書き込む
yaml.dump() で Python オブジェクトを YAML 形式に変換して書き出せる。
import yaml
config = {
"server": {
"host": "0.0.0.0",
"port": 8080,
},
"features": ["auth", "logging", "cache"],
}
with open("output.yaml", "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
allow_unicode=True を指定しないと、日本語がエスケープされて読みにくくなる。default_flow_style=False はブロックスタイル(インデント形式)での出力を保証するオプションだ。
出力結果は次のようになる。
features:
- auth
- logging
- cache
server:
host: 0.0.0.0
port: 8080複数ドキュメントを扱う
YAML では --- で区切ることで 1 つのファイルに複数のドキュメントを格納できる。読み込みには safe_load_all() を使う。
import yaml
yaml_text = """
name: Alice
role: admin
name: Bob
role: user
“”"
for doc in yaml.safe_load_all(yaml_text):
print(doc)
{‘name’: ‘Alice’, ‘role’: ‘admin’}
{‘name’: ‘Bob’, ‘role’: ‘user’}
書き出す場合は yaml.dump_all() を使えばよい。
docs = [
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "user"},
]
output = yaml.dump_all(docs, allow_unicode=True)
print(output)
YAML 特有の型変換の罠
YAML の自動型変換は便利だが、意図しない変換が起きることがある。たとえばノルウェーの国コード NO が真偽値 False に変換されるのは有名な問題だ。
import yaml
# "NO" が False に変換される
data = yaml.safe_load("country: NO")
print(data) # {'country': False}
# クォートで囲めば文字列として扱われる
data = yaml.safe_load('country: "NO"')
print(data) # {'country': 'NO'}
同様に on / off や yes / no も真偽値として解釈される。設定ファイルを書くときは、文字列として扱いたい値をクォートで囲む習慣をつけておくと安全だ。
YAML の自動型変換
“NO”, “on”, “off” などが意図せず bool に変換される
値をクォートで囲んで文字列と明示する
実践的な使い方:設定ファイルの管理
実際のプロジェクトでは、環境ごとに異なる設定を YAML で管理するケースが多い。デフォルト値と環境固有の値をマージするパターンを示す。
import yaml
def load_config(env="development"):
with open("config/default.yaml", encoding="utf-8") as f:
default = yaml.safe_load(f)
try:
with open(f"config/{env}.yaml", encoding="utf-8") as f:
override = yaml.safe_load(f)
except FileNotFoundError:
override = {}
# 浅いマージ(ネストが深い場合は再帰的なマージが必要)
default.update(override)
return default
config = load_config("production")
ネストされた辞書を再帰的にマージする場合は、手動で実装するか、deepmerge などの外部ライブラリを活用するとよい。PyYAML 自体にはマージ機能が組み込まれていないため、設計段階で設定の階層構造をシンプルに保つことも重要になってくる。