sử dụng construct_undefined trong ruamel from_yaml

Oct 16 2020

Tôi đang tạo thẻ MyTag yaml tùy chỉnh. Nó có thể chứa bất kỳ yaml hợp lệ nào - bản đồ, vô hướng, neo, chuỗi, v.v.

Làm cách nào để triển khai MyTag lớp để tạo mô hình thẻ này để ruamel phân tích cú pháp nội dung của a !mytagtheo cách giống hệt như cách nó sẽ phân tích cú pháp bất kỳ yaml nào đã cho? Các MyTagví dụ chỉ các cửa hàng bất cứ kết quả phân tích cú pháp của các nội dung yaml là.

Mã sau hoạt động và các xác nhận phải chứng minh chính xác những gì nó phải làm và tất cả chúng đều vượt qua.

Nhưng tôi không chắc liệu nó có hoạt động đúng lý do hay không. . . Cụ thể trong from_yamlphương pháp lớp, có sử dụng cách được commented_obj = constructor.construct_undefined(node)khuyến nghị để đạt được điều này không và việc sử dụng 1 và chỉ 1 từ trình tạo năng suất có đúng không? Nó không chỉ làm việc một cách tình cờ?

Thay vào đó, tôi có nên sử dụng một cái gì đó như construct_objecthoặc construct_maphoặc. . .? Các ví dụ mà tôi có thể tìm thấy có xu hướng biết nó đang xây dựng kiểu gì, vì vậy sẽ sử dụng construct_maphoặc construct_sequencechọn loại đối tượng nào để xây dựng. Trong trường hợp này, tôi thực sự muốn phân tích cú pháp ruamel thông thường / tiêu chuẩn cho bất kỳ loại nào không xác định có thể có trong đó và chỉ cần lưu trữ nó trong loại riêng của nó.

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)

Và một số mẫu 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

Trả lời

Anthon Oct 16 2020 at 04:23

Trong YAML, bạn có thể có neo và bí danh, và hoàn toàn ổn nếu có một đối tượng là con của chính nó (sử dụng bí danh). Nếu bạn muốn kết xuất cấu trúc dữ liệu Python data:

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

nó đổ vào:

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

và cho rằng neo và bí danh là cần thiết.

Khi tải một cấu trúc như vậy, ruamel.yaml đệ quy vào các cấu trúc dữ liệu lồng nhau, nhưng nếu nút cấp trên cùng chưa tạo ra một đối tượng thực được xây dựng mà mỏ neo có thể được tạo tham chiếu, thì lá đệ quy không thể giải quyết bí danh.

Để giải quyết điều đó, một trình tạo được sử dụng, ngoại trừ các giá trị vô hướng. Đầu tiên, nó tạo một đối tượng trống, sau đó đệ quy và cập nhật các giá trị của nó. Trong mã gọi phương thức khởi tạo, việc kiểm tra được thực hiện để xem liệu trình tạo có được trả về hay không và trong trường hợp đó next()được thực hiện trên dữ liệu và tự đệ quy tiềm năng "đã được giải quyết".

Bởi vì bạn gọi construct_undefined(), bạn luôn nhận được một máy phát điện. Trên thực tế, phương thức đó có thể trả về một giá trị nếu nó phát hiện ra một nút vô hướng (tất nhiên là không thể đệ quy), nhưng nó không. Nếu đúng như vậy, mã của bạn sau đó không thể tải tài liệu YAML sau:

!mytag 1

mà không có sửa đổi kiểm tra xem bạn có nhận được trình tạo hay không, như được thực hiện trong mã trong ruamel.yaml gọi các trình tạo khác nhau để nó có thể xử lý cả hai construct_undefinedvà ví dụ construct_yaml_int(không phải là trình tạo).