2017-01-27 25 views
11

add_constructor ile ekleyebileceğim uygun bir yapıcı işlevinin nasıl tanımlanacağını anlamaya çalışmak için PyYAML kaynak kodunu okuyorum. Bu kodun şimdi nasıl çalıştığına dair oldukça iyi bir anlayışa sahibim, ancak hala SafeConstructor'daki varsayılan YAML kurucularının jeneratörler olduğunu anlamıyorum. Örneğin, bu yöntem SafeConstructor arasında construct_yaml_map:PyYAML neden jeneratörler nesnelerini oluşturmak için kullanıyor?

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

bir nesne saplama ve sadece deep=Falseconstruct_mapping geçirilir, düğümden veri ile doldurmak için aşağıdaki şekilde jeneratör BaseConstructor.construct_object kullanılır anlamak:

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) 

Ve construct_mapping için deep=False'un bulunduğu durumda verilerin BaseConstructor.construct_document içinde nasıl üretildiğini anlıyorum. Anlamıyorum ne

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 

veri nesneleri dışarı stubbing ve construct_document yılında jeneratörler üzerinde yineleme tarafından nesneler aracılığıyla aşağı çalışma yararı olduğunu. Bunun YAML spesifikasyonundaki bir şeyi desteklemek için yapılması gerekiyor mu, yoksa bir performans avantajı sağlıyor mu?

This answer on another question

biraz yardımcı oldu, ama o cevap neden yaptığını anlamıyorum bu: Ben ikinci formu için çalıştığını test ettik

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

: bunun yerine

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

Diğer cevapta yayınlanan örnekler, ama belki de bazı kenar vakalarını kaçırıyorum.

PyYAML sürüm 3.10 kullanıyorum, ancak söz konusu kod PyYAML'nin en yeni sürümünde (3.12) aynı görünüyor.

+0

Stackoverflow'a Hoş Geldiniz. – Randy

cevap

9

YAML'de anchors and aliases'a sahip olabilirsiniz. Bununla doğrudan veya dolaylı olarak kendini referans yapıları yapabilirsiniz.

Eğer YAML bu kendi kendine referans olasılığına sahip olmazsa, önce tüm çocukları kurabilir ve ardından bir kerede ana yapıyı yaratabilirsiniz. Fakat öz referanslar yüzünden, henüz yaratmakta olduğunuz yapıyı “doldurmak” zorunda kalmayabilirsiniz. Jeneratörün iki aşamalı işlemini kullanarak (bu iki adımı derlerim, çünkü yöntemin sonuna gelmeden önce tek bir verim vardır), bir nesneyi kısmen oluşturabilir ve bir öz referansla doldurabilirsiniz. Çünkü nesne mevcut (yani bellekteki yeri tanımlanmıştır).

Yarar, hızda değil, ancak öz referansı mümkün kıldığından dolayıdır.

Eğer biraz aşağıdaki yükler bakın cevabını örnek basitleştirmek ise:

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) 

ama buna foo_constructor() değiştirirseniz:

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

(verim kaldırıldı, nihai katma , bir ConstructorError almak) return: mesaj olarak

found unconstructable recursive node 
    in "<unicode string>", line 2, column 1: 
    &fooref 
ilePyYAML, benzer bir mesaj vermelidir. Bu hatanın izini inceleyin ve ruamel.yaml/PyYAML'in kaynak koddaki diğer adı çözmeyi denediğini görebilirsiniz.

+0

Teşekkür ederim, takma adlar ve çapalarla ilgili bir şey olabileceğini düşündüm. Neden "foo_constructor" ı [cevabım] 'dan (http://stackoverflow.com/a/35476888/7476443) sorduğumda açıkladığımda doğru çıktıyı görüyor görünmüyor? Bu cevabın kendi örneklerinde kendine referans var. Cevabınıza, sorgumda görüldüğü gibi bir jeneratör olmamaya 'foo_constructor' düzenlediğimde sorun yaşanabilecek bir örnek YAML belgesini ekleyebilir misiniz? – Ryan

+0

@Ryan Cevabımı ruamel.yaml koduyla güncelledim. PyYAML bu bakımdan aynı şekilde davranmalıdır. Yorum takibinden yoksun olduğundan, BaseConstructor.construct_mapping() 'ın kodu, ruamel.yaml'den daha kolay takip edilebilir. – Anthon

+1

BTW, hoş geldiniz, ve lütfen böyle mükemmel sorular gönderin. – Anthon

İlgili konular