问题
This is my YAML file (input.yaml
):
team_member:
name: Max
hobbies:
- Reading
team_leader:
name: Stuart
hobbies:
- dancing
I want to edit this YAML file to add more values in key 'hobbies', example:
team_member:
name: Max
hobbies:
- Reading
- Painting
team_leader:
name: Stuart
hobbies:
- Dancing
- Fishing
I tried to implement the code Anthon to fit my situation but it didn't helped at all, because the indention level of that YAML file is different from mine.
Example:
import sys
import ruamel.yaml
yaml = ruamel.yaml.YAML()
# yaml.preserve_quotes = True
with open('input.yaml') as fp:
data = yaml.load(fp)
for elem in data:
if elem['name'] == 'Stuart':
elem['hobbies'] = ['Fishing']
break # no need to iterate further
yaml.dump(data, sys.stdout)
I get error "TypeError('string indices must be integers',)", I know this code might be completely wrong, but I am new to ruamel.yaml.
How to code this?
回答1:
The thing missing form the error message displayed is the line number (I assume that it is 9). That points to the line
if elem['name'] == 'Stuart':
And if that doesn't give you a clue, the approach that I recommend in such cases is starting to add some print
functions, so that you know what you are working on. The for
loop looks like:
for elem in data:
print('elem', elem)
if elem['name'] == 'Stuart':
print('elem->hobbies', elem['hobbies'])
elem['hobbies'] = ['Fishing']
this prints
elem team_member
before the exception is thrown, and I hope that will make you realize your are not iterating over the elements (items) of a list, but over the keys of a dict (constructed from the root level mapping in your YAML). And the value associated with the key is the object having a key name
and a key hobbies
.
So change the variable elem
to key
to make clear what you're handling and then proceed to work with value
, the value associated with that key instead of elem
within that loop¹:
for key in data:
value = data[key]
if value['name'] == 'Stuart':
print('value->hobbies', value['hobbies'])
value['hobbies'] = ['Fishing']
This gives:
value->hobbies ['dancing']
team_member:
name: Max
hobbies:
- Reading
team_leader:
name: Stuart
hobbies:
- Fishing
So we got rid of the exception, but the result is not exactly what you want. The element dancing
for the key 'hobbies' is gone, because you assign a new (list) value to that key, whereas what you should do is append a single item to the list. We can also get rid of the print function by now:
for key in data:
value = data[key]
if value['name'] == 'Stuart':
value['hobbies'].append('Fishing')
This will get you two items in the final sequence in the file. There is a few more things to address:
- the capitalization of
dancing
incorrect. To correct that, add a line handling the list if there is only one element - the code for the name
Max
, needs to be added (and that is why you need to get rid of thebreak
in your code) - the empty line, is considered a comment on the last element of the first sequence, that comment needs to be moved
- your indentation of sequences is non-default
The final code would be like:
from pathlib import Path
import ruamel.yaml
path = Path('input.yaml')
yaml = ruamel.yaml.YAML()
yaml.indent(sequence=4, offset=2) # for the non-default indentation of sequences
data = yaml.load(path)
for key in data:
value = data[key]
if value['name'] == 'Stuart':
if len(value['hobbies']) == 1:
value['hobbies'][0] = value['hobbies'][0].capitalize()
value['hobbies'].append('Fishing')
elif value['name'] == 'Max':
last_item_index = len(value['hobbies']) - 1
value['hobbies'].append('Painting')
comments = value['hobbies'].ca
if not comments.items[last_item_index][0].value.strip():
# move empty comment lines from previous last item to new last item
comments.items[last_item_index + 1] = comments.items.pop(last_item_index)
yaml.dump(data, path)
Which gives something quite close to what you wanted to get
team_member:
name: Max
hobbies:
- Reading
- Painting
team_leader:
name: Stuart
hobbies:
- Dancing
- Fishing
¹Alternative for the first two lines: for key, value in data.items()
回答2:
Thanks Anthon your code worked I have to edit this code as follows:
import sys
import ruamel.yaml
from pathlib import Path
yaml = ruamel.yaml.YAML()
path = Path('input.yaml')
yaml.indent(sequence=4, offset=2) # for the non-default indentation of sequences
with open(path) as fp:
data = yaml.load(fp)
for key in data:
value = data[key]
if value['name'] == 'Stuart':
if len(value['hobbies']) == 1:
value['hobbies'][0] = value['hobbies'][0].capitalize()
value['hobbies'].append('Fishing')
elif value['name'] == 'Max':
last_item_index = len(value['hobbies']) - 1
value['hobbies'].append('Painting')
comments = value['hobbies'].ca
if not comments.items[last_item_index][0].value.strip():
# move empty comment lines from previous last item to new last item
comments.items[last_item_index + 1] = comments.items.pop(last_item_index)
yaml.dump(data, path)
来源:https://stackoverflow.com/questions/53899662/how-to-read-a-component-in-yaml-file-so-that-i-can-edit-its-key-value-using-rua