この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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