Почему PyYAML использует генераторы для создания объектов?
Я читал исходный код PyYAML, чтобы попытаться понять, как определить подходящую конструкторскую функцию, которую я могу добавить с помощью add_constructor
. У меня довольно хорошее представление о том, как работает этот код, но я до сих пор не понимаю, почему конструкторы YAML по умолчанию в SafeConstructor
являются генераторами. Например, метод construct_yaml_map
of SafeConstructor
:
def construct_yaml_map(self, node):
data = {}
yield data
value = self.construct_mapping(node)
data.update(value)
Я понимаю, как генератор используется в BaseConstructor.construct_object
следующим образом, чтобы заглушить объект и только заполнить его данными из node, если 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, или это обеспечивает производительность?
Этот ответ по другому вопросу был несколько полезен, но я не понимаю, почему этот ответ делает это:
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 одинаков.
Ответы
Ответ 1
В YAML вы можете иметь якоря и псевдонимы. С этим вы можете прямо или косвенно создавать самореферентные структуры.
Если у 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 пытается разрешить псевдоним в исходном коде.