9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PyYAMLで改行のある文字列を出力する

Posted at

概要

  • Pythonを使ってYAMLファイルを出力する際に、改行を含む文字列の出力にちょっとしたコツが必要だったので、メモとして残しておきます。(YAMLの知識が足りていなかったという噂もありますが。。)

PyYAML

  • PythonでYAMLを扱うということで、PyYAMLを利用します。

やりたいこと

  • このような改行を含む文字列のデータがあった際に
 {
      'aa': 'bbbb\ncccc\ndddd',
      'bb': 'eeee'
 }
  • こんな感じで改行コードでちゃんと改行してブロックスタイルで出力したい
aa: |
  bbbb
  cccc
  dddd
bb: eeee

そのままやってみる

  • とりあえず、そのままyaml.dumpしてみます
    • ※実際に必要だったのはファイル出力する処理でしたが、わかりやすくprintする処理にしています
import yaml

def main():

    test_dict = {
        'aa': 'bbbb\ncccc\ndddd',
        'bb': 'eeee'
    }
    print(
        yaml.dump(test_dict,
                  allow_unicode=True,
                  encoding='utf-8',
                  default_flow_style=False).decode()
    )


if __name__ == '__main__':
    main()
  • なにか違う。。
aa: 'bbbb

  cccc

  dddd'
bb: eeee

representerの登録

  • 調べてみたところこのあたりに同じような内容があったので、修正して対応してみました。
    • 修正したコードがこちらになります。
import yaml

def represent_str(dumper, instance):
    if "\n" in instance:
        return dumper.represent_scalar('tag:yaml.org,2002:str',
                                       instance,
                                       style='|')
    else:
        return dumper.represent_scalar('tag:yaml.org,2002:str',
                                       instance)

def main():

    test_dict = {
        'aa': 'bbbb\ncccc\ndddd',
        'bb': 'eeee'
    }
    yaml.add_representer(str, represent_str)
    print(
        yaml.dump(test_dict,
                  allow_unicode=True,
                  encoding='utf-8',
                  default_flow_style=False).decode()
    )


if __name__ == '__main__':
    main()
  • add_representerメソッドで出力する際のスタイルを定義してあげています
    • 文字列に改行コードを含む場合のみ style='|' を指定するようにします。
  • 実行結果がこちらです

aa: |-
  bbbb
  cccc
  dddd
bb: eeee
  • いい感じになりました!

うまくいかなケースがある。。

  • 上記の実装で出力していたところ、いくつか想定どおりに出力されないケースがありました。
    • 調べていったところ、、 文字列の改行の前にスペースがある場合 にうまく出力されない事がわかりました。
    • このようなデータの場合
    test_dict = {
        'aa': 'bbbb\ncccc \ndddd',
        'bb': 'eeee'
    }
  • 改行してくれません。。
aa: "bbbb\ncccc \ndddd"
bb: eeee

調べてみました

  • 気になったのでPyYAMLのコードを確認してみました。
  • analyze_scalar メソッド内でデータのタイプを判定しているのですが、 スペースの次に改行コードがある場合 、space_breakという変数がTrueになります。
    • するとこのあたりのフラグがすべてFalseにセットされます。
        if space_break or special_characters:
            allow_flow_plain = allow_block_plain =  \
            allow_single_quoted = allow_block = False
  • 実際にyamlを出力する際にはこちらのchoose_scalar_styleメソッド内でスタイルを判定して出力するようになっています。
  • 本来は、先程実装したとおり、 add_representer メソッドで指定したスタイルで出力されるはずなのですが、先程のフラグのうち、 allow_blockがFalseになっていると指定したスタイル(ここでは|)で出力されないようになっています。
        if self.event.style and self.event.style in '|>':
            if (not self.flow_level and not self.simple_key_context
                    and self.analysis.allow_block):
                return self.event.style
  • 先程のフラグをセットしているanalyze_scalarのところのコメントにはこのような記載があります。
    • スペースに続く改行がある場合にはブロックスタイルでの出力がされないということのようです。
Spaces followed by breaks, as well as special character
 are only allowed for double quoted scalars.
  • さらにanalyze_scalarを見てみると同様にallow_blockがFalseセットされている箇所があります。

    • 文字列の末尾にスペースがある場合 にallow_blockがFalseセットされるようです。
    • コメントにはこのような記載があり、同様にプロックスタイルでの出力がされないということのようです。
      • We do not permit trailing spaces for block scalars.
  • まとめると、文字列の中に、 スペースに続く改行がある場合文字列の末尾にスペースがある場合 には改行コードで文字列が改行されたブロックスタイルでの出力がされないようです。

対応

  • ということで、最終的にこちらのような実装で対応をしました。
    • 単純にrepresentメソッド内で先程の該当する文字列を置換しています。
import yaml
import re


def represent_str(dumper, instance):
    if "\n" in instance:
        instance = re.sub(' +\n| +$', '\n', instance)
        return dumper.represent_scalar('tag:yaml.org,2002:str',
                                       instance,
                                       style='|')
    else:
        return dumper.represent_scalar('tag:yaml.org,2002:str',
                                       instance)


def main():

    test_dict = {
        'aa': 'bbbb\ncccc \ndddd',
        'bb': 'eeee'
    }
    yaml.add_representer(str, represent_str)
    print(
        yaml.dump(test_dict,
                  allow_unicode=True,
                  encoding='utf-8',
                  default_flow_style=False).decode()
    )


if __name__ == '__main__':
    main()
  • スペースのあとに改行があるケースでも想定どおりの出力となりました。
aa: |-
  bbbb
  cccc
  dddd
bb: eeee

YAMLは奥が深い。。

  • きちんと調べていませんが、YAMLの仕様にのっとった処理になっているということのようです。
    • 普段あまり気にしていなかったのですが、YAMLの仕様は奥が深いようです。。

参考サイト

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?