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



我有一个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 "...yamlconstructor.py", line 43, in get_single_data
return self.construct_document(node)
File "...yamlconstructor.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

更新 2根据与@flyx的讨论,我尝试对齐名称,如下所示

import yaml
import sys
class Volume(yaml.YAMLObject):
yaml_loader = yaml.SafeLoader
def __init__(self, property1, property2):
self.property1 = property1
self.property2 = property2
@property
def property1(self):
return self.property1
@property1.setter
def property1(self, property1):
self.__property1 = 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, allow_unicode=True)

但是它不会更改输出格式

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

鉴于使用属性装饰器是 Python 的核心功能,我很惊讶 pyyaml 需要自定义表示器和构造函数来处理它。

您显示的类Volume有一个需要两个参数的__init__input_property1input_property2。但是,默认表示符表示名为_Volume__property1的第一个属性。在你对__init__的调用中,这不能被映射,因为它与__init__参数的名称不对应。

因此,您的选择是:

  • 编辑__init__的参数名称,使其与参数序列化时使用的名称匹配
  • 添加自定义表示符,这些表示符表示具有__init__所需的字段名称的对象
  • 使用YAMLObject的默认加载器,并将自定义代码放在之后调用的常规方法中。 例如:
def volume_constructor(loader, node):
instance = loader.construct_yaml_object(loader, Volume)
instance.myinit()
return instance

这样,加载Volume与默认转储程序兼容,因为您使用默认加载程序。然后,将任何其他初始化代码放入方法myinit中。

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

您的volume_constructor假定标记的节点是一个映射(因为您在该函数中调用construct_mapping)。尽管您的 YAML 无效,但有一点很清楚,即前面提到的映射具有关键_Volume__consuminghostlist.因此,python字典state将具有该键,并且由于您将其传递给__init__with**,这将像您调用一样工作:

__init__(_Volume__consuminghostlist: <value for _Volume__consuminghostlist>)

你必须照顾好这个关键词。

如果你以这种方式做事,这在ruamel.yaml和PyYAML中都是必需的。

最新更新