2017-01-27 5 views
11

私はadd_constructorで追加できる適切なコンストラクタ関数を定義する方法を理解するために、PyYAMLソースコードを読んできました。私はそのコードが今どのように動作しているかをよく理解していますが、なぜSafeConstructorのデフォルトのYAMLコンストラクタがジェネレータであるのか理解できません。例えば、方法SafeConstructorconstruct_yaml_mapなぜPyYAMLはジェネレータを使ってオブジェクトを構築しますか?

def construct_yaml_map(self, node): 
    data = {} 
    yield data 
    value = self.construct_mapping(node) 
    data.update(value) 

私はオブジェクトをスタブのみdeep=Falseconstruct_mappingに渡された場合、ノードからデータを移入するために次のように発電機がBaseConstructor.construct_objectに使用される方法を理解する。

if isinstance(data, types.GeneratorType): 
     generator = data 
     data = generator.next() 
     if self.deep_construct: 
      for dummy in generator: 
       pass 
     else: 
      self.state_generators.append(generator) 

BaseConstructor.construct_documentのデータはdeep=Falseの場合、construct_mappingの場合はどのように生成されるのか分かります。私は理解していない何

def construct_document(self, node): 
    data = self.construct_object(node) 
    while self.state_generators: 
     state_generators = self.state_generators 
     self.state_generators = [] 
     for generator in state_generators: 
      for dummy in generator: 
       pass 

は、データオブジェクトをスタブとconstruct_documentに発電機を反復して、オブジェクトを下に作業の利益です。これはYAML仕様の中で何かをサポートするために行わなければならないのですか、それともパフォーマンス上の利点をもたらしますか?

This answer on another questionはやや役立ったが、私はその答えは、このない理由を理解していない。これに代えて

def foo_constructor(loader, node): 
    instance = Foo.__new__(Foo) 
    yield instance 
    state = loader.construct_mapping(node, deep=True) 
    instance.__init__(**state) 

def foo_constructor(loader, node): 
    state = loader.construct_mapping(node, deep=True) 
    return Foo(**state) 

を、私は後者の形式は、のために働くことをテストしてみました例はその他の答えに掲載されていますが、おそらく私はいくつかの端の場合がありません。

PyYAMLのバージョン3.10を使用していますが、問題のコードがPyYAMLの最新バージョン(3.12)で同じであるようです。

+0

ようこそStackoverflow。 – Randy

答えて

9

YAMLにはanchors and aliasesがあります。これにより、直接的または間接的に自己参照構造を作ることができます。

YAMLにこのような自己参照の可能性がない場合は、最初にすべての子を構築してから、一度に親構造を作成することができます。しかし、自己参照のために、作成している構造をまだ "記入"していない可能性があります。ジェネレータの2ステッププロセスを使用すると(メソッドの最後に来る前に1歩だけしかないので、この2つのステップを呼び出します)、オブジェクトを部分的に作成し、自己参照オブジェクトが存在する(すなわち、メモリ内の場所が定義されている)ためです。

利点はスピードではなく、純粋に自己参照を可能にするためです。

あなたがビットを参照してください答えは、以下の負荷からの例を簡素化する場合:

import sys 
import ruamel.yaml as yaml 


class Foo(object): 
    def __init__(self, s, l=None, d=None): 
     self.s = s 
     self.l1, self.l2 = l 
     self.d = d 


def foo_constructor(loader, node): 
    instance = Foo.__new__(Foo) 
    yield instance 
    state = loader.construct_mapping(node, deep=True) 
    instance.__init__(**state) 

yaml.add_constructor(u'!Foo', foo_constructor) 

x = yaml.load(''' 
&fooref 
!Foo 
s: *fooref 
l: [1, 2] 
d: {try: this} 
''', Loader=yaml.Loader) 

yaml.dump(x, sys.stdout) 

しかし、あなたがfoo_constructor()を変更した場合:

def foo_constructor(loader, node): 
    instance = Foo.__new__(Foo) 
    state = loader.construct_mapping(node, deep=True) 
    instance.__init__(**state) 
    return instance 

(収率は削除、最後のを追加しました返信)、ConstructorError:メッセージとして

found unconstructable recursive node 
    in "<unicode string>", line 2, column 1: 
    &fooref 

PyYAMLにも同様のメッセージが表示されます。そのエラーのトレースバックを調べると、ruamel.yaml/PyYAMLがソースコードのエイリアスを解決しようとしている場所がわかります。

+0

ありがとう、私はそれがエイリアスとアンカーと関係するかもしれないと思った。私の質問で説明したように、[あなたの答え](http://stackoverflow.com/a/35476888/7476443)から 'foo_constructor'を修正すると、なぜ私は正しい出力を見かけますか?その答えはその例では自己参照をしています。私の質問に示されているように、 'foo_constructor'をジェネレータにしないで編集した場合、問題のあるサンプルのYAMLドキュメントをあなたの答えに含めることができますか? – Ryan

+0

@Ryan私はruamel.yamlのコードを使って答えを更新しました。 PyYAMLはこの点で同じように動作するはずです。コメントの追跡が不十分であるため、 'BaseConstructor.construct_mapping()'のコードは実際にはruamel.yamlのコードよりも追跡する方が簡単かもしれません。 – Anthon

+1

ところで、[ようやく]ようこそ、このような優れた質問を投稿してください。 – Anthon

関連する問題