LoginSignup
2
1

More than 3 years have passed since last update.

PyYAMLでカスタムタグを使う

Posted at

調べてみたら全くといっていいほどサンプルが(英語圏にも!)なかったので簡単なのを

きっかけ

YAMLでプログラムの挙動を定義するコードをPythonで書いていて、ちょっとYAML内にオブジェクトを生成する処理を書きたくなりました。

PyYAMLDocumentationを見る限りタグというものがあって、どうもそれが目的のことをやってくれそうなのですが、どこにも利用例がなくどう使えばいいのか、どう定義すればいいのかが分かりません。さて困った。

タグを追加する方法

で、いきなり本題です。タグを追加するには、PyYAMLモジュールのグローバル関数、add_multi_constructor()というものに値を渡せば良い。

  yaml.add_multi_constructor("tag:yaml.org,2002:var", var_handler)

  # ...

  def var_handler(loader, suffix, node=None):
    # 処理

こういうコードを書くと、YAMLファイルに以下のようなタグがあったときに反応し、var_handlerが呼び出される。

  textvariable: !!var:StringVar
    name: label
    default: ""

引数に指定した関数には三つのパラメータが引き渡され

  • loader:読み込み中のYAMLファイルを処理しているローダーオブジェクトそのもの
  • suffix:タグプレフィックスの後ろに続く文字列。上記YAMLコードの場合、:StringVar
  • node:タグを発見した箇所を示すノードオブジェクト。valueにはここより下の下位層にあるノードが格納される。 上記YAMLコードの場合、MappingNode(value=[(ScalarNode(), ScalarNode()), (ScalarNode(), ScalarNode())])

で、関数の戻り値としてなんらかの値をリターンすると、タグの部分が戻り値に指定した値になる。
たとえば上記例の場合、「Yes」という文字を返すと、textvariable: Yesと書いたのと同じようになる。

add_multi_constructor()関数の挙動を追ってみると、キーワード引数として定義されているLoaderを省略した場合、ここで指定した関数はLoader, FullLoader, UnsafeLoaderの三つのクラスに追加されるようです。なので、SafeLoaderを使った場合は、ここで何をしたとしても関数は追加されません。

YAMLで独自に定義したタグだけを使いたい場合

YAMLを読み込むとき、独自で定義したタグのみを読み込みたい場合は、SafeLoaderを引数で指定して関数を追加すると良いです。

わたしは別の処理もあったのと、SafeLoaderを直接いじってしまうことに一抹の不安を感じたため、SafeLoaderを継承した新しいクラスを定義してしまいました。

class GeneratorLoader(yaml.SafeLoader):
  def __init__(self, stream):
    super().__init__(stream)
    yaml.add_multi_constructor("tag:yaml.org,2002:var", GeneratorLoader.var_handler,
      Loader=GeneratorLoader)

  @staticmethod
  def var_handler(loader, suffix, node=None):
    pass

ここで作ったクラスをyaml.load()メソッドでLoaderとして使用すれば、タグが処理されるようになります。

PyYAMLのグローバル関数には、Loaderのインスタンスを受け取る関数がない

なお、yaml.load()などPyYAMLの__init__.pyに定義されている関数には、Loaderのインスタンスを受け取る関数がありません(すべてクラスを受け取っています)。
このため、上記で作成したLoaderオブジェクトを、YAML読み込み後に使用したい場合は、Loader#get_single_data()を直接呼び出す必要があります。

loader = GeneratorLoader(self.string)
struct = loader.get_single_data()

その他の注意点

  • タグは必ず「tag:yaml.org,2002」ではじまる必要があります。よってadd_multi_constructor()関数の第一引数の値は必ず「tag:yaml.org,2002」からはじまります。ただし、この部分は実際にYAMLには書きません(「tag:yaml.org,2002:var」というタグを定義した場合、YAMLに書くのは「var」のみとなります)

説明がよくわからないからとりあえずコードを見せろ

現在作りかけですが次のモジュールで使用しています。

2020/09/21現在はtksugar/generator.pyに当該処理があります(あとでコードを移動させるかも)。

参考文献

2
1
1

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
2
1