2017-01-27 3 views
11

Я читал исходный код PyYAML, чтобы попытаться понять, как определить правильную конструкторскую функцию, которую я могу добавить с помощью add_constructor. У меня довольно хорошее представление о том, как работает этот код, но я до сих пор не понимаю, почему конструкторы YAML по умолчанию в SafeConstructor являются генераторами. Так, например, метод construct_yaml_map из SafeConstructor:Почему PyYAML использует генераторы для создания объектов?

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

Я понимаю, как генератор используется в BaseConstructor.construct_object следующим образом, чтобы гасит объект, и только заполнить его с данными из узла, если deep=False передается construct_mapping:

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) 

Я проверил, что последняя форма работы для примеры, размещенные на этом другом ответе, но, возможно, мне не хватает какого-то края.

Я использую версию 3.10 PyYAML, но похоже, что код, о котором идет речь, в последней версии (3.12) PyYAML.

+0

Добро пожаловать в Stackoverflow. – Randy

ответ

9

В YAML у вас может быть anchors and aliases. С этим вы можете прямо или косвенно создавать самореферентные структуры.

Если у YAML не было такой возможности самореференции, вы могли бы сначала построить все дочерние элементы, а затем создать родительскую структуру за один раз. Но из-за самореференций у вас может не быть ребенка, чтобы «заполнить» структуру, которую вы создаете. Используя двухэтапный процесс генератора (я называю этот два шага, потому что он имеет только один доход до того, как вы дойдете до конца метода), вы можете частично создать объект и заполнить его саморекламой , поскольку объект существует (т. е. определено его место в памяти).

Выгода не в скорости, а исключительно из-за возможности самореференции.

Если упростить пример из ответа вы ссылаетесь немного, следующие нагрузки:

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

Спасибо, я думал, что это может быть связано с псевдонимами и якорями. Почему, когда я изменяю 'foo_constructor' из [вашего ответа] (http://stackoverflow.com/a/35476888/7476443), как описано в моем вопросе, я, кажется, вижу правильный вывод? В его ответах есть собственные ссылки. Можете ли вы включить в свой ответ пример документа YAML, который будет иметь проблемы, если я отредактировал 'foo_constructor', чтобы * not * был генератором, как показано в моем вопросе? – Ryan

+0

@Ryan Я обновил свой ответ, с кодом для ruamel.yaml. PyYAML должен вести себя одинаково в этом отношении. Из-за отсутствия отслеживания комментариев его код для 'BaseConstructor.construct_mapping()' на самом деле может быть проще, чем для ruamel.yaml. – Anthon

+1

BTW, добро пожаловать в [так], и, пожалуйста, напишите больше таких замечательных вопросов. – Anthon

Смежные вопросы