5

PyYAML は、通常の Python オブジェクトで巡回グラフを処理できます。例えば:

スニペット #1。

class Node: pass
a = Node()
b = Node()
a.child = b
b.child = a
# We now have the cycle a->b->a
serialized_object  = yaml.dump(a)
object = yaml.load(serialized_object)

このコードは成功するので、シリアライズされたオブジェクトをロードする際の無限再帰を防ぐ何らかのメカニズムがあることは明らかです。独自の YAML コンストラクター関数を作成するときにそれを利用するにはどうすればよいですか?

たとえば、は一時的なフィールドと、および非Node一時的なフィールド を持つクラスであるとします。yamlドキュメントにする必要があります。私はこれをしたいと思います:foobarchildchild

スニペット #2。

def representer(dumper, node):
  return dumper.represent_mapping("!node", {"child": node.child})

def constructor(loader, data):
  result = Node()
  mapping = loader.construct_mapping(data)
  result.child = mapping["child"]
  return result

yaml.add_representer(Node, representer)
yaml.add_constructor("!node", constructor)

# Retry object cycle a->b->a from earlier code snippet
serialized_object  = yaml.dump(a)
print serialized_object
object = yaml.load(serialized_object)

しかし、それは失敗します:

&id001 !node
child: !node
  child: *id001

yaml.constructor.ConstructorError: found unconstructable recursive node:
  in "<string>", line 1, column 1:
    &id001 !node

理由がわかります。私のコンストラクター関数は再帰用に構築されていません。親オブジェクトの構築を完了する前に子オブジェクトを返す必要があり、子と親が同じオブジェクトである場合は失敗します。

しかし、スニペット #1 が機能するため、明らかに PyYAML にはこの問題を解決するグラフ トラバーサルがあります。おそらく、すべてのオブジェクトを構築するための 1 つのパスと、それらのフィールドに値を設定するための 2 番目のパスがあります。私の質問は、カスタム コンストラクターをこれらのメカニズムにどのように結び付けることができるかということです。

その質問に対する答えは理想的です。しかし、答えがカスタムコンストラクターではこれを行うことができないというものであり、あまり望ましくない代替手段がある場合 (たとえば、YAMLObjectクラスを自分のNodeクラスに混在させるなど)、その答えも高く評価されます。

4

2 に答える 2

11

再帰を伴う可能性のある複雑な型 (マッピング/dict、シーケンス/リスト、オブジェクト) の場合、コンストラクターは一度にオブジェクトを作成できません。したがってyield、関数で構築されたオブジェクトを作成し、constructor()その後の値を更新する必要があります¹:

def constructor(loader, data):
    result = Node()
    yield result
    mapping = loader.construct_mapping(data)
    result.child = mapping["child"]

それはエラーを取り除きます。

¹ PyYAML をruamel.yamlにアップグレードしている間、集中的に見なければ、これはどこにも文書化されていないと思います。これを行う方法を知らなかったでしょう。典型的なケース:ソースを読む Lukepy/constructor.py

于 2015-04-13T13:56:24.490 に答える