ใช้ construction_undefined ใน ruamel from_yaml

Oct 16 2020

ฉันกำลังสร้างแท็ก yaml ที่กำหนดเอง MyTag มันสามารถมีมันเทศที่ถูกต้องใดก็ได้เช่นแผนที่สเกลาร์จุดยึดลำดับและอื่น ๆ

ฉันจะใช้คลาส MyTag เพื่อสร้างแบบจำลองแท็กนี้ได้อย่างไรเพื่อให้ ruamel แยกวิเคราะห์เนื้อหาของ a !mytagในลักษณะเดียวกับที่จะแยกวิเคราะห์ yaml ที่ระบุ MyTagเช่นร้านค้าเพียงแค่สิ่งที่ผลการแยกวิเคราะห์เนื้อหา yaml คือ

รหัสต่อไปนี้ใช้งานได้และคำยืนยันควรแสดงให้เห็นว่าควรทำอย่างไรและทุกอย่างก็ผ่าน

แต่ฉันไม่แน่ใจว่ามันใช้งานได้ด้วยเหตุผลที่ถูกต้องหรือไม่ . . โดยเฉพาะอย่างยิ่งในfrom_yamlวิธีการคลาสกำลังใช้commented_obj = constructor.construct_undefined(node)วิธีที่แนะนำในการบรรลุสิ่งนี้และใช้ 1 และเพียง 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)

และมันเทศตัวอย่างบางส่วน:

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(ซึ่งไม่ใช่เครื่องกำเนิดไฟฟ้า)