【问题标题】:using construct_undefined in ruamel from_yaml在 ruamel from_yaml 中使用construct_undefined
【发布时间】:2020-10-15 20:20:27
【问题描述】:

我正在创建一个自定义 yaml 标签 MyTag。它可以包含任何给定的有效 yaml - 映射、标量、锚点、序列等。

如何实现 MyTag 类来对此标签进行建模,以便 ruamel 以与解析任何给定 yaml 完全相同的方式解析 !mytag 的内容? MyTag 实例只存储 yaml 内容的解析结果。

以下代码有效,并且断言应该准确地演示它应该做什么并且它们都通过了。

但我不确定它是否出于正确的原因工作。 . .特别是在from_yaml 类方法中,使用commented_obj = constructor.construct_undefined(node) 是一种推荐的实现方式,并且从产生的生成器中消耗1 且仅1 正确吗?这不是偶然的吗?

我应该改用construct_objectconstruct_map 之类的东西吗? . .?我能够找到的示例倾向于知道它正在构造什么类型,因此可以使用construct_mapconstruct_sequence 来选择要构造的对象类型。在这种情况下,我实际上想为可能存在的任何未知类型搭载通常/标准的 ruamel 解析,并将其存储在自己的类型中。

import ruamel.yaml
from ruamel.yaml.comments import CommentedMap, CommentedSeq, TaggedScalar


class MyTag():
    yaml_tag = '!mytag'

    def __init__(self, value):
        self.value = value

    @classmethod
    def from_yaml(cls, constructor, node):
        commented_obj = constructor.construct_undefined(node)
        flag = False
        for data in commented_obj:
            if flag:
                raise AssertionError('should only be 1 thing in generator??')
            flag = True

        return cls(data)


with open('mytag-sample.yaml') as yaml_file:
    yaml_parser = ruamel.yaml.YAML()
    yaml_parser.register_class(MyTag)
    yaml = yaml_parser.load(yaml_file)

custom_tag_with_list = yaml['root'][0]['arb']['k2']
assert type(custom_tag_with_list) is MyTag
assert type(custom_tag_with_list.value) is CommentedSeq
print(custom_tag_with_list.value)

standard_list = yaml['root'][0]['arb']['k3']
assert type(standard_list) is CommentedSeq
assert standard_list == custom_tag_with_list.value

custom_tag_with_map = yaml['root'][1]['arb']
assert type(custom_tag_with_map) is MyTag
assert type(custom_tag_with_map.value) is CommentedMap
print(custom_tag_with_map.value)

standard_map = yaml['root'][1]['arb_no_tag']
assert type(standard_map) is CommentedMap
assert standard_map == custom_tag_with_map.value

custom_tag_scalar = yaml['root'][2]
assert type(custom_tag_scalar) is MyTag
assert type(custom_tag_scalar.value) is TaggedScalar

standard_tag_scalar = yaml['root'][3]
assert type(standard_tag_scalar) is str
assert standard_tag_scalar == str(custom_tag_scalar.value)

还有一些示例 yaml:

root:
  - item: blah
    arb:
      k1: v1
      k2: !mytag
        - one
        - two
        - three-k1: three-v1
          three-k2: three-v2
          three-k3: 123 # arb comment
          three-k4: 
            - a
            - b
            - True
      k3:
        - one
        - two
        - three-k1: three-v1
          three-k2: three-v2
          three-k3: 123 # arb comment
          three-k4: 
            - a
            - b
            - True
  - item: argh
    arb: !mytag
            k1: v1
            k2: 123
            # blah line 1
            # blah line 2
            k3:
              k31: v31
              k32: 
                - False
                - string here
                - 321
    arb_no_tag:
      k1: v1
      k2: 123
      # blah line 1
      # blah line 2
      k3:
        k31: v31
        k32: 
          - False
          - string here
          - 321
  - !mytag plain scalar
  - plain scalar
  - item: no comment
    arb:
      - one1
      - two2

【问题讨论】:

    标签: yaml ruamel.yaml


    【解决方案1】:

    在 YAML 中,您可以拥有锚点和别名,并且让对象成为其自身的子对象(使用别名)非常好。如果要转储Python数据结构data

    data = [1, 2, 4, dict(a=42)]
    data[3]['b'] = data
    

    它转储到:

    &id001
    - 1
    - 2
    - 4
    - a: 42
      b: *id001
    

    因此锚点和别名是必要的。

    当加载这样的构造时,ruamel.yaml 会递归到嵌套的数据结构中,但是如果顶层节点没有导致构造一个可以引用锚点的真实对象,则递归叶无法解析别名.

    为了解决这个问题,使用了生成器,但标量值除外。它首先创建一个空对象,然后递归并更新它的值。在调用构造函数的代码中,检查是否返回了生成器,在这种情况下,next() 对数据进行了处理,并且潜在的自递归“已解决”。

    因为你调用construct_undefined(),你总是得到一个生成器。实际上,如果该方法检测到标量节点(当然不能递归),则该方法可以返回一个值,但事实并非如此。如果可以,您的代码将无法加载以下 YAML 文档:

    !mytag 1
    

    无需修改来测试您是否获得了生成器,就像在 ruamel.yaml 中调用各种构造函数的代码中所做的那样,它可以同时处理 construct_undefined 和 e.g. construct_yaml_int(不是生成器)。

    【讨论】:

    • 感谢您提供如此快速的回答并如此详细地回答,Anthon!非常非常感谢,并感谢您在 ruamel 上所做的所有工作。我很清楚:如果我理解您解释的所有内容,这意味着我上面使用的construct_undefined 应该可以按原样工作吗?我也尝试使用递归/嵌套标签引用来打破它,它似乎很好。正如您所提到的,在 ruamel 的其他地方您使用类似这样的东西:python if isinstance(data, types.GeneratorType): generator = data data = next(generator) 但在这种情况下它恰好是不必要的。
    • 是的,它有效。关键是contruct_undefined 总是返回一个生成器,即使对于标量也是如此,因此对于这种情况,不需要对类型进行测试
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    • 1970-01-01
    • 1970-01-01
    • 2021-02-19
    • 1970-01-01
    相关资源
    最近更新 更多