如何在yaml.loader add_contructor中获得父节点?

时间:2019-03-31 13:19:46

标签: python yaml

我正在设计一个YAML模板,该模板将允许使用特定的!Tags。在python中加载YAML文件时处理标签的方法是添加一个构造函数:yaml.add_constructor。我想添加一个ScalarNode构造函数,但返回值应基于作为被评估节点的同级出现的数据。

def sibling_constructor(loader, node):
    value = loader.construct_scalar(node)
    # How to get parent node's child nodes?
    return value + ' are dangerous.'

yaml.add_constructor('!Sibling', sibling_constructor)    
return yaml.load(myFile)

Yaml:

Thing:
  ParentThing:
    Child1: Fast cars
    Child2: Slow cars
    Child3: !Sibling Child1

处理完YAML文件后,我希望内存字典中会出现如下所示:

{
  'Thing': {
    'ParentThing': {
        'Child1': 'Fast cars',
        'Child2': 'Slow cars',
        'Child3': 'Fast cars are dangerous.'
    }
  }
}

YAML模块内是否有获取父节点的功能?如果没有,我将如何遍历字典以获取父节点和兄弟节点?

1 个答案:

答案 0 :(得分:0)

YAML模块中没有功能可访问同级或 sibling_constructor中的父项,因为必须通过 参数loadernode

node参数是一个Node实例, attributestagvaluestart_markend_markcommentanchor不包含上下文信息(mark包含指向源的指针 流中,comment可能在该行上添加了行尾注释。

loader参数没有此信息要少得多 很清楚,因为它的数据结构要复杂得多。但是你可能会 没意识到是施工过程是深度的 首先,所以您的父母尚未建造,因此 在处理完最后一个节点之前,没有从YAML加载的数据结构的入口点, 之后可以完全创建其父级,然后是其父级 等等,直到递归结束并且构造的数据从 load()函数。


但并非所有希望都消失了,如果您使用默认的往返加载程序,则可以访问 标记,而无需添加构造函数,因此您可以轻松地进行后处理过程。
假设您的输入在文件input.yaml中:

import sys
from pprint import pprint
from pathlib import Path
import ruamel.yaml

input_path = Path('input.yaml')

def sibling(d):
    # recurse into a data structure loaded from YAML
    if isinstance(d, dict):
        for k, v in d.items():
            try:
                if v._yaml_tag.value == '!Sibling':
                    d[k] = d[v.value] + ' are dangerous.'
            except (AttributeError, ):
                pass
            sibling(v)
    elif isinstance(d, list):
        for item in d:
            sibling(item)



yaml = ruamel.yaml.YAML()
data = yaml.load(input_path)
sibling(data)

pprint(data)
print('-------')
yaml.dump(data, sys.stdout)

给予:

{'Thing': {'ParentThing': {'Child1': 'Fast cars',
                           'Child2': 'Slow cars',
                           'Child3': 'Fast cars are dangerous.'}}}
-------
Thing:
  ParentThing:
    Child1: Fast cars
    Child2: Slow cars
    Child3: Fast cars are dangerous.

虽然可以注册构造一个新方法的新方法 映射(construct_yaml_map),并附加dict 构造为Constructor实例,但是这个dict 一次更新所有映射的键/值对 解决:

def construct_yaml_map(self, node):
    data = self.yaml_base_dict_type()  # type: Dict[Any, Any]
    self._tw_current_dict = data
    yield data
    value = self.construct_mapping(node)
    data.update(value)

ruamel.yaml.SafeConstructor.add_constructor(u'tag:yaml.org,2002:map', construct_yaml_map)

yaml.add_constructor('!Sibling', sibling_constructor, constructor=ruamel.yaml.SafeConstructor)
return yaml.safe_load(myFile)

因此,当您将以上内容添加到代码中时,您的sibling_constructor将被称为loader._tw_current_dict,但为空。

(您应该使用SafeLoader,没有必要使用从PyYAML继承的旧load方法来使所有事情变得不安全)

您可能可以加入construct_mapping,我曾尝试过 并陷入困境(我确实声称对YAML模块的源代码相当熟悉)。


您可能要考虑的一件事是将输入的YAML更改为:

Thing:
  ParentThing:
    Child1: &child Fast cars
    Child2: Slow cars
    Child3: !Sibling *child

即使用锚和别名的YAML中的常规引用机制。那个别名 *child当然可以在创建Child3的值时被解析。