繁体   English   中英

无法从 YAML 创建 Python 对象数组

[英]Unable to create an array of Python objects from YAML

我试图从 YAML 中实例化一个 python 对象数组,在一个结构中。 在结构之外,我可以轻松地做到这一点,但似乎 YAML BaseLoader 无法递归搜索我的对象的子节点。

import yaml
import ruamel.yaml

class Person:
    def __init__(self, name: str = 'JohnDoe'):
        self.name = name

    @classmethod
    def from_yaml(cls, constructor, node):
        for m in constructor.construct_yaml_map(node):
            pass
        if 'Name' in m:
            name = m['Name']

        return cls(name=name)

    def __repr__(self):
        return f'Person(name={self.name})'


class Car:
    def __init__(self):
        self.passengers = []

    def add_person(self, person: Person = None):
        self.passengers.append(person)

    @classmethod
    def from_yaml(cls, constructor, node):
        for m in constructor.construct_yaml_map(node):
            pass

        inst = cls()

        if 'Driver' in m:
            inst.passengers = [m['Driver']]+inst.passengers

        if 'Passengers' in m:
            foo = m['Passengers']
            print(f'm[\'Passengers\'] = {foo}')
            for person in m['Passengers']:
                inst.add_person(person)

        return inst

    def __repr__(self):
        return f'Car(passengers={self.passengers})'


if __name__ == "__main__":

    yaml = ruamel.yaml.YAML(typ='safe')
    yaml.register_class(Person)
    yaml.register_class(Car)

    data = yaml.load("""
        - !Person &0
            Name: 'Paul'

        - !Person &1
            Name: 'George'
            
        - !Person &3
            Name: 'John'

        - !Car
            Driver: *0
            Passengers: [*1]

        - !Car
            Driver: *3
            Passengers: 
                - !Person &4
                    Name: 'Ringo'

        """)

    print(f'data = {data}')

上面的代码在执行时将以下内容打印到控制台:

m['Passengers'] = []
m['Passengers'] = []
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul)]), Car(passengers=[Person(name=John)])]

我希望输出在哪里

m['Passengers'] = [Person(name=George)]
m['Passengers'] = [Person(name=Ringo)]
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul), Person(name=George)]), Car(passengers=[Person(name=John), Person(name=Ringo)])]

无论如何,即使是字符串数组,键“Passengers”的关联值始终是字典 m 中的 []。

我是否必须手动告诉构造函数首先在 from_yaml 函数中遍历节点的其余部分,或者 YAML 加载器是否从下往上递归地工作?

我能够在这篇文章中找到部分答案。

对于raumel.yaml ,看起来我们的构造函数是from_yaml函数,它是在注册类时添加的。 我们所要做的就是在from_yaml中初始化我们的类之后,在我们检索递归项Passengers之前添加一个yield。

class Car:
    def __init__(self):
        self.passengers = []

    def add_person(self, person: Person = None):
        self.passengers.append(person)

    @classmethod
    def from_yaml(cls, constructor, node):

        for m in constructor.construct_yaml_map(node):
            print(f'm{type(m)} = {m}')
            pass

        inst = cls()
        yield inst # <-- This yield statement fixes our issue

        if 'Driver' in m:
            inst.passengers = [m['Driver']]+inst.passengers

        if 'Passengers' in m:
            foo = m['Passengers']
            print(f'm[\'Passengers\'] = {foo}')
            for person in m['Passengers']:
                inst.add_person(person)
                
        return inst

    def __repr__(self):
        return f'Car(passengers={self.passengers})'

在您的示例中无需import yaml

在汽车的建造过程中,它的乘客还不知道。 因此,您需要做的是在两步过程中构造潜在的递归数据,例如PersonCar ,首先构造并生成“空” Car ,然后在已生成的实例上填充Driver和任何Passangers 加载器知道如何处理这个问题,因此您不必递归到from_yaml中的任何内容。

此外,您需要在from_yaml中调用constructor.construct_mapping(node, deep=True) ,而不是在constructor.construct_yaml_map(node)上进行迭代:

import ruamel.yaml

class Person:
    def __init__(self, name: str = 'JohnDoe'):
        self.name = name

    @classmethod
    def from_yaml(cls, constructor, node):
        inst = cls()
        yield inst
        m = constructor.construct_mapping(node, deep=True)
        if 'Name' in m:
            inst.name = m['Name']
     
    def __repr__(self):
        return f'Person(name={self.name})'


class Car:
    def __init__(self):
        self.passengers = []

    def add_person(self, person: Person = None):
        self.passengers.append(person)

    @classmethod
    def from_yaml(cls, constructor, node):
        inst = cls()
        yield inst
        m = constructor.construct_mapping(node, deep=True)

        if 'Driver' in m:
            inst.passengers = [m['Driver']] + inst.passengers

        if 'Passengers' in m:
            foo = m['Passengers']
            print(f'm[\'Passengers\'] = {foo}')
            for person in m['Passengers']:
                inst.add_person(person)

    def __repr__(self):
        return f'Car(passengers={self.passengers})'


if __name__ == "__main__":

    yaml = ruamel.yaml.YAML(typ='safe')
    yaml.register_class(Person)
    yaml.register_class(Car)

    data = yaml.load("""
        - !Person &0
            Name: 'Paul'

        - !Person &1
            Name: 'George'
            
        - !Person &3
            Name: 'John'

        - !Car
            Driver: *0
            Passengers: [*1]

        - !Car
            Driver: *3
            Passengers: 
                - !Person &4
                    Name: 'Ringo'

        """)

    print(f'data = {data}')

这使:

m['Passengers'] = [Person(name=George)]
m['Passengers'] = [Person(name=Ringo)]
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul), Person(name=George)]), Car(passengers=[Person(name=John), Person(name=Ringo)])]

虽然允许在锚之后写标记,但 IMO 更适合在标记之后写锚,因为您将获得标记对象的锚定实例。

所以这让我想知道&2 !Person的名字是什么(可能与!Person &2相同),是皮特吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM