问题
I'm trying to use the ruamel.yaml
Python library to remove some keys/value pairs from nested dictionaries within a large YAML file, while retaining surrounding comments. Here's a simplified version of the code I'm using:
import sys
import ruamel.yaml
with open(sys.argv[1], 'r') as doc:
parsed = ruamel.yaml.round_trip_load(doc, preserve_quotes=True)
for item in parsed['items']:
if item['color'] == 'blue':
del item['color']
yaml = ruamel.yaml.YAML(typ='rt')
yaml.indent(sequence=4, offset=2)
yaml.dump(parsed, sys.stdout)
... and an accompanying file that I'm trying to edit (the intent is to remove the 'color: blue' line:
▶ cat items.yml
items:
- name: a
color: blue
texture: smooth
# This is a comment above 'c'
# More comment
- name: b
texture: wrinkled
color: yellow
With that specific file, the code does what I want:
▶ ./munge.py items.yml
items:
- name: a
texture: smooth
# This is a comment above 'c'
# More comment
- name: b
texture: wrinkled
color: yellow
However, if color: blue
is the last key/value pair in the first dict, the comment preceding the second item gets eaten:
▶ cat items.yml
items:
- name: a
texture: smooth
color: blue
# This is a comment above 'c'
# More comment
- name: b
texture: wrinkled
color: yellow
▶ ./munge.py items.yml
items:
- name: a
texture: smooth
- name: b
texture: wrinkled
color: yellow
It looks like the comment is being attached to the last key/value pair of the dictionary, rather than being represented as a 'leading' comment preceding the second item.
Is there any way to preserve the comment that precedes the next item even when removing the last key in the dict?
回答1:
Comments are associated with the last collection node parsed, based on the key of a mapping or the the index of a sequence. Your comments "before", are actually end-of-line comments following the last key-value pair of the first sequence item, extending over multiple lines.
If you print the comment attribute of the dict like object (a CommentedMap
), using print(parsed['items'][0].ca)
, you'll get:
items={'color': [None, None, CommentToken("\n\n # This is a comment above 'c'\n # More comment\n", line: 5, col: 2), None]})
So if you delete the key color
from the CommentedMap
, dumping parsed
will no longer output
the comment, as there is no automatic mechanism to associate the comment with another key or some higher up node.
You can "move" that comment by keeping track of the previous key in your loop:
parsed['items'][0].ca.items['texture'] = parsed['items'][0].ca.items.pop('color')
and that requires you to keep track of the previous key:
import sys
import ruamel.yaml
yaml_str = """\
items:
- name: a
texture: smooth
color: blue
# This is a comment associated with the last key of the preceding mapping
# More of the same comment
- name: b
texture: wrinkled
color: yellow
"""
yaml = ruamel.yaml.YAML()
yaml.indent(sequence=4, offset=2)
yaml.preserve_quotes = True
parsed = yaml.load(yaml_str)
prev = None
for item in parsed['items']:
for key in item:
if key == 'color' and item[key] == 'blue':
if prev is not None:
item.ca.items[prev] = item.ca.items.pop(key)
del item['color']
break
prev = key
yaml.dump(parsed, sys.stdout)
which gives:
items:
- name: a
texture: smooth
# This is a comment associated with the last key of the preceding mapping
# More of the same comment
- name: b
texture: wrinkled
color: yellow
A few notes:
moving the comment to the parent (sequence/list/CommentedSeq) is possible, but not as trivial
there is no need to mix the old API (
ruamel.yaml.round_trip_load()
) with the new (yaml=YAML()
)you should get the for loop out of the
with
statement, the file can be closed afterround_trip_load(doc)
(oryaml = ruamel.yaml.YAML(); yaml.load(doc)
)the officially recommended extension for YAML files has been
.yaml
for close to 13 years
And make sure you have a test case and/or pin the ruamel.yaml version you are using (ruamel.yaml<0.17
) as such comment trickery is not guaranteed to keep working the same way in future versions.
来源:https://stackoverflow.com/questions/57582926/preserving-following-comments-when-removing-last-dict-key-with-ruamel-yaml