【发布时间】:2020-04-22 22:13:44
【问题描述】:
我有一个 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
更新 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 需要自定义表示器和构造器来处理它。
【问题讨论】:
-
请创建一个minimal, reproducible example。您显示的摘录不是有效的 YAML,因此不清楚发生了什么,并且您的声明 „省略了值“ 没有帮助;
Volume的缺失定义也没有。 -
@flyx 我在原始帖子中进行了更新,探讨了为什么 yaml 格式很奇怪。简而言之,我使用转储程序而不是手动创建它,并且属性装饰器的使用似乎导致了奇怪的格式。
标签: python yaml pyyaml ruamel.yaml