使用自定义构造函数将嵌套对象加载到yaml文件中

问题描述 投票:0回答:1

我有一个yaml文件,描述了我正在导入的一系列嵌套对象。因为我想在每个调用上都有__init__,所以我正在编写自定义构造函数

def volume_constructor(loader, node):
    instance = Volume.__new__(Volume)
    yield instance
    state = loader.construct_mapping(node)
    instance.__init__(**state)

然后,当我读取yaml文件时,我在执行加载之前添加了构造函数:

yaml.add_constructor(volume_yaml_tag, volume_constructor, yaml.SafeLoader)
yaml.add_constructor(host_yaml_tag, volume_constructor, yaml.SafeLoader)
configcontent = configfile.read()
cfg = yaml.safe_load(configcontent)

在这种情况下,卷是父对象,它包含几个属性,如普通字符串等,以及主机对象列表。下面的摘录显示了一般形式,为简洁起见,省略了值

- !!python/object:libraries.volume.Volume
_Volume__consuminghostlist:
- !!python/object:libraries.host.Host
  _Host__property1: fc
  _Host__property2: localhost
_Volume__property1: false
_Volume__property2: sometextvalue
_Volume__property3: somenumericvalue

我可以毫无问题地定义和转储对象,但是当我尝试使用如上定义的自定义构造函数进行加载时,出现以下错误堆栈。请注意,当我更改属性的顺序以使处理的第一个对象不是嵌套对象时,错误仍会标记处理的第一个对象。

        cfg = yaml.safe_load(configcontent)
  File "....yaml\__init__.py", line 162, in safe_load
    return load(stream, SafeLoader)
  File "...yaml\__init__.py", line 114, in load
    return loader.get_single_data()
  File "...yaml\constructor.py", line 43, in get_single_data
    return self.construct_document(node)
  File "...yaml\constructor.py", line 52, in construct_document
    for dummy in generator:
  File "...volume.py", line 15, in volume_constructor
    instance.__init__(**state)
TypeError: __init__() got an unexpected keyword argument '_Volume__consuminghostlist'

但是,如果我放弃添加自定义构造函数并执行库存load,则load成功,尽管在此不再为每个调用__init__。是否应该有另一种方法来调用__init__或设置构造函数的方式有问题?我想必须有某种方法可以避免对构造函数上的每个标签进行手动解析,并将它们作为单独的参数提供给init _。我开玩笑地设置了deepcopy属性,但是并没有什么不同。供参考,配置文件使用以下方法创建:

yaml.dump(cfg, sys.stdout, Dumper=noalias_dumper, default_flow_style=False)

更新

:几个回复提到无效的yaml格式,我同意这种格式看起来不正确,因此我做了一些实验来了解为什么会这样。我通过定义对象然后转储而不是手工编写来创建yaml,因此我希望转储使用有效的yaml格式。我发现如下图所示,当我为属性设置了@property装饰器时,这种情况变得混乱了。
import yaml
import sys
class Volume(yaml.YAMLObject):
    yaml_loader = yaml.SafeLoader

    def __init__(self, input_property1, input_property2):
        self.property1 = input_property1
        self.property2 = input_property2

    @property
    def property1(self):
        return self.__property1

    @property1.setter
    def property1(self, input_property1):
        self.__property1 = input_property1


samplevolume = Volume("ABC", 1.0)
noalias_dumper = yaml.dumper.Dumper
noalias_dumper.ignore_aliases = lambda self, data: True
yaml.dump(samplevolume, sys.stdout, Dumper=noalias_dumper, default_flow_style=False)

当我运行上面的示例代码时,我得到下面的转储输出,其中显示了对没有@property定义的属性的预期处理方式>

!!python/object:__main__.Volume
_Volume__property1: ABC
property2: 1.0

我有一个yaml文件,描述了我正在导入的一系列嵌套对象。因为我想在每次调用时都具有__init__,所以我正在编写自定义构造函数def volume_constructor(...

您不会在_Volume__consuminghostlist类的__init__中处理关键字Volume,当然您需要这样做。

您的volume_constructor假定标记的节点是一个映射(因为您在该函数中调用了construct_mapping)。尽管您的YAML无效,但很明显的一点是,上述映射具有键_Volume__consuminghostlist。因此,python字典state将具有该键,并且由于您将其与__init__一起传递给了**,因此该方法就像您调用了:

__init__(_Volume__consuminghostlist: <value for _Volume__consuminghostlist>)

而且您必须注意该关键字。

如果您以这种方式进行操作,则在ruamel.yaml和PyYAML中都是必需的。

python yaml pyyaml ruamel.yaml
1个回答
0
投票

您不会在_Volume__consuminghostlist类的__init__中处理关键字Volume,当然您需要这样做。

© www.soinside.com 2019 - 2024. All rights reserved.