using construct_undefined in ruamel from_yaml
Ich erstelle ein benutzerdefiniertes Yaml-Tag MyTag. Es kann eine beliebige gültige yaml - Karte, Skalar, Anker, Sequenz usw. enthalten.
Wie implementiere ich die Klasse MyTag, um dieses Tag so zu modellieren, dass ruamel den Inhalt von a genauso analysiert !mytag, wie es ein bestimmtes yaml analysieren würde? Die MyTagInstanz speichert nur das analysierte Ergebnis des Yaml-Inhalts.
Der folgende Code funktioniert, und die Asserts sollten genau zeigen, was er tun soll , und alle werden bestanden.
Aber ich bin mir nicht sicher, ob es aus den richtigen Gründen funktioniert. . . from_yamlVerwendet speziell in der Klassenmethode commented_obj = constructor.construct_undefined(node)eine empfohlene Methode, um dies zu erreichen, und ist es richtig, 1 und nur 1 vom ausgegebenen Generator zu verbrauchen? Es funktioniert nicht nur aus Versehen?
Sollte ich stattdessen so etwas wie construct_objectoder construct_mapoder verwenden? . .? Die Beispiele, die ich finden konnte, neigen dazu zu wissen, welchen Typ es konstruiert, also würden sie entweder den zu konstruierenden Objekttyp verwenden construct_mapoder construct_sequenceauswählen. In diesem Fall möchte ich die übliche / Standard-Ruamel-Analyse für jeden unbekannten Typ, der sich dort befindet, effektiv huckepack nehmen und sie einfach in ihrem eigenen Typ speichern.
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)
Und einige Beispiele 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
Antworten
In YAML können Sie Anker und Aliase haben, und es ist vollkommen in Ordnung, wenn ein Objekt ein Kind von sich selbst ist (unter Verwendung eines Alias). Wenn Sie die Python-Datenstruktur sichern möchten data:
data = [1, 2, 4, dict(a=42)]
data[3]['b'] = data
es wird ausgegeben an:
&id001
- 1
- 2
- 4
- a: 42
b: *id001
und dafür sind Anker und Aliase notwendig.
Beim Laden eines solchen Konstrukts wird ruamel.yaml in die verschachtelten Datenstrukturen zurückgeführt. Wenn der Knoten auf oberster Ebene jedoch nicht dazu geführt hat, dass ein reales Objekt erstellt wurde, auf das der Anker als Referenz verwendet werden kann, kann das rekursive Blatt den Alias nicht auflösen.
Um dies zu lösen, wird ein Generator verwendet, mit Ausnahme von Skalarwerten. Es erstellt zuerst ein leeres Objekt, rekursiert es dann und aktualisiert seine Werte. Beim Aufrufen des Konstruktors durch Code wird geprüft, ob ein Generator zurückgegeben wird. In diesem Fall werden next()die Daten überprüft und die mögliche Selbstrekursion "aufgelöst".
Weil Sie anrufen construct_undefined(), bekommen Sie immer einen Generator. Praktisch könnte diese Methode einen Wert zurückgeben, wenn sie einen Skalarknoten erkennt (der natürlich nicht wiederkehren kann), aber nicht. In diesem Fall könnte Ihr Code das folgende YAML-Dokument nicht laden:
!mytag 1
ohne Modifikationen, die testen, ob Sie einen Generator erhalten oder nicht, wie im Code in ruamel.yaml beschrieben, der die verschiedenen Konstruktoren aufruft, damit er beide construct_undefinedund z. B. construct_yaml_int(was kein Generator ist) verarbeiten kann.