ruamel from_yaml에서 construct_undefined 사용

Oct 16 2020

사용자 지정 yaml 태그 MyTag를 만들고 있습니다. 지정된 유효한 yaml (맵, 스칼라, 앵커, 시퀀스 등)을 포함 할 수 있습니다.

ruamel이 !mytag주어진 yaml을 구문 분석하는 것과 똑같은 방식으로 a의 내용을 구문 분석하도록이 태그를 모델링하기 위해 MyTag 클래스를 어떻게 구현 합니까? MyTagYAML 내용의 구문 분석 된 결과는 어떤 인스턴스 만 저장합니다.

다음 코드는 작동하며 어설 션 수행 해야하는 작업을 정확히 보여 주어야 하며 모두 통과해야합니다.

그러나 그것이 올바른 이유로 작동하는지 확실하지 않습니다. . . 특히 from_yaml클래스 메서드에서 commented_obj = constructor.construct_undefined(node)이를 달성하기 위해 권장되는 방법을 사용 하고 있으며 산출 된 생성기에서 1 개만 소비하고 있습니까? 우연히 작동하지 않습니까?

대신 construct_object, 또는 construct_map또는 같은 것을 사용해야 합니다. . .? 나는 그것을 건설하고 입력 한 내용을 알고하는 경향이 찾을 수 있었던 예는, 그래서 중 하나를 사용하는 것 construct_map또는 construct_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

답변

Anthon Oct 16 2020 at 04:23

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예와 construct_yaml_int(발전기하지 않은).