2013-10-18 4 views
12

私はカスタムPythonオブジェクトを作成するpythonでyamlシーケンスを作成しようとしています。オブジェクトは、__init__の後に解体された辞書とリストで構築する必要があります。しかし、construct_mapping関数は組み込みシーケンス(リスト)とdictsのツリー全体を構築しないようです。
は、次のことを考えてみましょう:すべてのノードのロードが完了した後、PyYAML construct_mappingを使用してオブジェクトを構築する方法はありますか?

import yaml 

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

def foo_constructor(loader, node): 
    values = loader.construct_mapping(node) 
    s = values["s"] 
    d = values["d"] 
    l = values["l"] 
    return Foo(s, d, l) 
yaml.add_constructor(u'!Foo', foo_constructor) 

f = yaml.load(''' 
--- !Foo 
s: 1 
l: [1, 2] 
d: {try: this}''') 

print(f) 
# prints: 'Foo(1, {'try': 'this'}, [1, 2])' 

fFooオブジェクトが作成された後、実際にデータで満たされているldオブジェクトへの参照を保持しているので、これは正常に動作します。

それでは、ちょっぴりより複雑な何かをしてみましょう:

class Foo(object): 
    def __init__(self, s, l=None, d=None): 
     self.s = s 
     # assume two-value list for l 
     self.l1, self.l2 = l 
     self.d = d 

今、私たちはYAMLのコンストラクタは、前のネストの外側の層から開始されるので、これは、次のエラー

Traceback (most recent call last): 
    File "test.py", line 27, in <module> 
    d: {try: this}''') 
    File "/opt/homebrew/lib/python2.7/site-packages/yaml/__init__.py", line 71, in load 
    return loader.get_single_data() 
    File "/opt/homebrew/lib/python2.7/site-packages/yaml/constructor.py", line 39, in get_single_data 
    return self.construct_document(node) 
    File "/opt/homebrew/lib/python2.7/site-packages/yaml/constructor.py", line 43, in construct_document 
    data = self.construct_object(node) 
    File "/opt/homebrew/lib/python2.7/site-packages/yaml/constructor.py", line 88, in construct_object 
    data = constructor(self, node) 
    File "test.py", line 19, in foo_constructor 
    return Foo(s, d, l) 
    File "test.py", line 7, in __init__ 
    self.l1, self.l2 = l 
ValueError: need more than 0 values to unpack 

を取得し、すべてのノードが終了する前にオブジェクトを構築する。順序を逆にし、深く埋め込まれたオブジェクト(たとえば、ネストされたオブジェクト)を先に開始する方法はありますか?あるいは、ノードのオブジェクトがロードされた後に、少なくともの構築が行われる方法がありますか?

答えて

19

あなたは何を知っていますか?私が見つけた解決策はとてもシンプルでしたが、それほど十分に文書化されていませんでした。

Loader class documentationは、construct_mappingメソッドが1つのパラメータ(node)を取ることを明確に示しています。しかし、私自身のコンストラクタの作成を検討した後、ソースをチェックアウトし、その答えはright thereでした!また、このメソッドはパラメータdeep(デフォルトはFalse)を取ります。

def construct_mapping(self, node, deep=False): 
    #... 

ので、使用する正しいコンストラクタメソッドが

def foo_constructor(loader, node): 
    values = loader.construct_mapping(node, deep=True) 
    #... 

である私はPyYAMLとは、いくつかの追加のドキュメントを使用することができると思いますが、私はそれがすでに存在していることを感謝しています。

+2

**ありがとうございました!**これにより、抜け毛がなくなりました。 –

+0

私はこの男にもっと多くのポイントを与えることができたらいいと思う。 – Dacav

+0

OMG!やっと ! – Penbeuz

6

TL; DR:
はこの答えの下にあるコードの1を使用してfoo_constructorを置き換える


あなたのコード(およびソリューション)にはいくつかの問題がありますが、聞かせての段階的に対応してください。

Fooのために定義された__str__()がないので、あなたはそれが一番下の行のコメントで言っている印刷されません提示したコード、('Foo(1, {'try': 'this'}, [1, 2])')は、それが何かのように出力します。これは、簡単に追加することで解決され

__main__.Foo object at 0x7fa9e78ce850 

Fooに以下の方法:

def __str__(self): 
     # print scalar, dict and list 
     return('Foo({s}, {d}, {l})'.format(**self.__dict__)) 

、あなたはその後、出力を見れば:

Foo(1, [1, 2], {'try': 'this'}) 

これは近いですが、あなたがコメントで約束したものでもありません。 listdictがスワップされています。foo_constructor()には、間違ったパラメータの順序でFoo()を作成するためです。
これは、foo_constructor()が作成しているオブジェクトについて多く知っておく必要がある、より根本的な問題を指摘しています。なぜこれはそうですか?それはちょうどパラメータの順序はありませんが、試してみてください。

f = yaml.load(''' 
--- !Foo 
s: 1 
l: [1, 2] 
''') 

print(f) 

一つは、これは(非指定dキーワード引数のデフォルト値)Foo(1, None, [1, 2])を印刷することを期待します。
d = value['d']でKeyError例外が発生します。あなたがなど使用get('d')、缶

foo_constructor()でこれを解決するために、しかし、あなたは正しい動作のためにあなたが必見あなたのケースでは、単にすべてのNoneことが起こるた(あなたのFoo.__init__()からデフォルト値を指定することを実現するために持っています)、デフォルト値の各パラメータごとに:

def foo_constructor(loader, node): 
    values = loader.construct_mapping(node, deep=True) 
    s = values["s"] 
    d = values.get("d", None) 
    l = values.get("l", None) 
    return Foo(s, l, d) 

これを更新することは、もちろんメンテナンスの悪夢です。

だから、全体foo_constructorをスクラップし、より多くのPyYAMLとは、内部的にこれを行う方法のようなものに置き換えます

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

これは(デフォルト)のパラメータが不足して処理し、デフォルト設定のための場合は、更新する必要はありません。キーワードの引数が変更されます。オブジェクト(常にトリッキー)の自己参照の使用を含めた完全な例で

このすべて

、:

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

    def __str__(self): 
     # print scalar, dict and list 
     return('Foo({s}, {d}, [{l1}, {l2}])'.format(**self.__dict__)) 

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) 

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

ができます:

Foo(1, {'try': 'this'}, [1, 2]) 
Foo(1, None, [1, 2]) 
Foo({'a': <__main__.Foo object at 0xba>}, {'try': 'this'}, [1, 2]) 

これはruamel.yamlを用いて試験しました(そのうちの私は著者です)、これはPyYAMLの拡張バージョンです。解決策は、PyYAML自体に対しても同じように動作するはずです。

+0

(部分的に)作成されたインスタンスを生成する必要がある理由についての詳細については、 'constructor.py:BaseConstructor.construct_object()'を参照してテストを行います登録されたコンストラクタ(すなわち 'foo_constructor()')が 'GeneratorType'を返し、適切なアクションが実行されたかどうかを確認します。 – Anthon

関連する問題