Python
YAML
PyYAML

Using yaml.dumper without Alias by PyYAML - PyYAML の yaml.dumpでaliasを無効にする

Overview

PyYAMLを使ってyaml dumpをしていると
&id001*id001 と言った出力を見かけたことがないだろうか。

非常に簡単な例を挙げると以下のようなものだ。

サンプルコード

import yaml

sample_dict = {
    "username": "User",
    "password": "Password",
}
yaml_data = [sample_dict, sample_dict]

print(yaml.dump(yaml_data))

出力結果

$ python sample.py 
- &id001 {password: Password, username: User}
- *id001

期待した動作

$ python sample.py 
- {password: Password, username: User}
- {password: Password, username: User}

サンプルコード2

YAMLファイルを生成するならば
より読みやすい出力にすることが多いと思われるので
default_flow_style = False としたサンプルも貼っておく

import yaml

sample_dict = {
        "username": "User",
        "password": "Password",
    }
yaml_data = [sample_dict, sample_dict]

print(yaml.dump(yaml_data, default_flow_style=False,))

出力結果2

$ python sample.py 
- &id001
  password: Password
  username: User
- *id001

原因

平たく言うとバグ。若しくは仕様のようだ。
トップページに事例付きで解説が書いてある。

https://pypi.org/project/pyaml/

同様の問題を抱えている人は少なくなく
Qiitaでもこちらで対処法が記載されていたり、
Issue103、及びIssue104でチケットが上がっている。

残念ながら2018年11月現在においてもこの問題は解消されておらず
WorkArround対応となっている。

Issue104に記載があるが
yaml.dump(data, ignore_aliases=True) を実装すると、逆にAliasの有るYAMLを読み込んだ際に
無限ループに陥ってしまうらしく、双方をええ具合によろしくやってくれる方法はまだ無いようだ。

従ってWork Arroundで対処する必要がある。

対処方法

- &id001
 password: Password
 username: User
- *id001
import yaml
from pathlib import Path

"""Load yaml file"""
yaml_data = Path().parent.joinpath('sample.yml').open('r')
data = yaml.load(yaml_data)

"""Solution1"""
class NoAliasDumper(yaml.Dumper):
    def ignore_aliases(self, data):
        return True

out1 = yaml.dump(data, Dumper=NoAliasDumper)
print(out1)

"""Solution2"""
noalias_dumper = yaml.dumper.Dumper
noalias_dumper.ignore_aliases = lambda self, data: True
out2 = yaml.dump(data, Dumper=noalias_dumper)
print(out2)
python sample_read.py 
- {password: Password, username: User}
- {password: Password, username: User}

- {password: Password, username: User}
- {password: Password, username: User}

どちらで対処するかはお好みで。
なお、Dumper を SafeDumperにすることはできるが
yaml.dump を yaml.safe_dumpにはできない。
yaml.safe_dumpにすると以下のようなエラーが出てしまう

TypeError: dump_all() got multiple values for keyword argument 'Dumper'

参考リンク