【PyYAML VS ruamel.yaml】PythonからYAMLファイルを触ってみた
Python で YAML ファイル操作する必要があり、2種類のライブラリ
を触る機会があったので、簡単な使い方を記録しておきます。
実行環境
- Python3.6
- OS : Amazon Linux
- LibYAML : 0.1.6
- PyYAML : 3.12
- ruamel.yaml : 0.15.0
tl;dr
- ともに YAML 1.2 に対応。
- PyYAML は古くから存在し、シンプルな API で基本機能に特化。ドキュメントの多くが Python2 時代のまま。
- ruamel.yaml は PyYAML のフォークでこまめにアップデートされている。入出力結果ができるだけ同じになるように設計されている。
LibYAML のインストール
PyYAML/ruamel.yaml ともに LibYAML と連携可能です。ライブラリインストール時に LibYAML がインストール済みであれば、利用可能になります。特別な理由がない限りはインストールしておきましょう。
$ sudo yum install -y libyaml-devel
PyYAML の使い方
インストール
$ python -m venv test $ source test/bin/activate $ pip install -U pyyaml
ファイル入出力
YAML ファイルの入出力をやってみます
import yaml with open('in.yml') as stream: data = yaml.load(stream) with open('out.yml', 'w') as stream: yaml.dump(data, stream=stream))
フロー vs ブロック
出力内容からわかるように、PyYAML はデフォルトではフロースタイルを採用しています。
ブロックスタイルで出力するには dump 時に default_flow_style=False
オプションを渡します。
>>> print(yaml.dump(yaml.load('a: b'))) {a: b} >>> print(yaml.dump(yaml.load('a: b'), default_flow_style=False)) a: b
ユニコードの扱い
日本語などの非アスキー文字を dump 時にエンコードされないようにするには、allow_unicode=True
オプションを渡します。
>>> print(yaml.dump(yaml.load('a: あ'))) {a: "\u3042"} >>> print(yaml.dump(yaml.load('a: あ'), allow_unicode=True)) {a: あ}
数字のみで構成される文字列
数字のみで構成される文字列の場合、
- 0 で始まらなければ、整数
- 0 で始まる場合、octet 評価できれば octet、出来なければ文字列 として扱われます
>>> yaml.load('a: 123') {'a': 123} >>> yaml.load('a: 012') # octet {'a': 10} >>> yaml.load('a: 09') # {'a': '09'}
null の扱い
null は常に null で出力されます。
>>> yaml.dump(yaml.load('a: [null, ~]')) 'a: [null, null]\n'
コメントの扱い
ロード時にコメントは破棄されます。
>>> data = yaml.load(''' ... # comment ... a: b ... c: d #comment''') >>> yaml.dump(data) '{a: b, c: d}\n'
ディレクティブのセパレーター
ディレクティブごとにセパレーター(---
)を挿入する場合、explicit_start=True
オプションを渡します。
>>> yaml.dump('a: b', explicit_start=True) "--- 'a: b'\n"
順序の維持について
PyYAML は入出力の前後で順序が維持されることを保障しません。 順序を維持する PR が存在します。
Allow to turn off sorting keys in Dumper #143 · yaml/pyyaml · GitHub
ruamel.yaml の使い方
PyYAML だけでは要件を実現出来ない箇所があったため、かわりに見つけたのが ruamel.yaml です。
インストール
$ python -m venv test $ source test/bin/activate $ pip install -U ruamel.yaml
ファイル入出力
YAML ファイルの入出力をやってみます
import ruamel import ruamel.yaml yaml = ruamel.yaml.YAML() with open('in.yml') as stream: data = yaml.load(stream) with open('out.yml', 'w') as stream: yaml.dump(data, stream=stream)
PyYAML とほぼ同じです。
以下の様にファイル出力することも出来ます。
from pathlib import Path stream = Path('out.yml') yaml.dump(data, stream=stream)
フロー vs ブロック
ruamel.yaml はデフォルトでブロックスタイルを採用しています。
フロースタイルにするには yaml.default_flow_style
を True にします。
>>> yaml.default_flow_style False >>> yaml.dump(yaml.load('a: b'), sys.stdout) a: b
ユニコードの扱い
特別な対応は不要です。
>>> yaml.dump(yaml.load('a: あ'), sys.stdout) a: あ
数字のみで構成される文字列
数字のみで構成される文字列の場合、不思議な仕様です。
- 0 で始まらなければ、整数
- 0 で始まる場合、octet 評価できれば先頭の0を除いた整数として評価、出来なければ文字列として扱われます
>>> yaml.load('a: 123') CommentedMap([('a', 123)]) >>> yaml.load('a: 012') CommentedMap([('a', 12)]) # ← 10 進の 10 ではない >>> yaml.load('a: 09') CommentedMap([('a', '09')])
少しモヤモヤしますね。
null の扱い
null は ~/null
では無く空文字で出力されます。
>>> yaml.dump({'a': None}, sys.stdout) a:
ここもモヤモヤしますね。 YAML 1.2 の null の定義を引用します
Represents the lack of a value. This is typically bound to a native null-like value (e.g., undef in Perl, None in Python). Note that a null is different from an empty string.
なお null の出力はカスタマイズ出来ます。
例えば null を ~
で出力したい場合、次のようにします。
>>> def custom_none(self, data): .... return self.represent_scalar(u'tag:yaml.org,2002:null', u'~') >>> ruamel.yaml.RoundTripRepresenter.add_representer(type(None), custom_none) >>> yaml.dump({'a': None}, sys.stdout) a: ~
関連 https://bitbucket.org/ruamel/yaml/issues/169/roundtripdumper-dumps-null-values
コメントの扱い
コメントは保持されます。
>>> data = yaml.load(''' ... # comment ... a: b ... c: d #comment''') >>> yaml.dump(data, sys.stdout) # comment a: b c: d #comment
ディレクティブのセパレーター
ディレクティブごとにセパレーター(---
)を挿入する場合、explicit_start=True
にします。
>>> yaml.explicit_start >>> yaml.explicit_start = True >>> yaml.dump({'a': 1}, sys.stdout) --- a: 1
順序の維持について
入出力により、順序が維持されることを保障します。 新規アイテムは末尾に追加されます。
>>> data = yaml.load(''' ... a: b ... c: d''') >>> data CommentedMap([('a', 'b'), ('c', 'd')]) >>> data['e'] = 'f' # アイテムを追加 >>> yaml.dump(data, sys.stdout) a: b c: d e: f
まとめ
PyYAML とそのフォークである ruamel.yaml の使い方を紹介しました。
PyYAML が YAML の仕様を重視したライブラリであるのに対して、ruamel.yaml は YAML 処理を重視したライブラリと言う印象を持ちました。
用途に合わせて使い分けて下さい。
参考
- PyYAML https://bitbucket.org/xi/pyyaml
- ruamel.yaml http://yaml.readthedocs.io/en/latest/
- LibYAML http://pyyaml.org/wiki/LibYAML