Changing a value in a yaml file using Python

2019-05-22 15:50发布

问题:

I have a .yaml file I want to update with a Python code. Let's say it looks something like that:

  state: 'present'

I'd like to have a code that changes the state and saves the file. I'm trying with something like this and fail:

def set_state(state):
    with open("file_to_edit.yaml", 'rw') as f:
        doc = yaml.load(f)
    doc['state'] = state
    yaml.dump(f)

I am using the 'yaml' package for Python.

回答1:

The problem is that yaml.dump doesn't actually write to a file. Instead, it returns the modified YAML as a string unless you write it directly to the file.

The following should work:

def set_state(state):
    with open('file_to_edit.yaml') as f:
        doc = yaml.load(f)

    doc['state'] = state

    with open('file_to_edit.yaml', 'w') as f:
        yaml.dump(doc, f)


回答2:

Before anything else: never use yaml.load() if you don't have to, as it is in principle unsafe to do so. For this kind of simple structure (without tags) you should use yaml.safe_load() (and the corresponding safe_dump() that will complain if your data cannot be safe_loaded after dumping).

yaml.dump() has the following signature:

def dump(documents, stream=None, Dumper=Dumper,
         default_style=None, default_flow_style=None,
         canonical=None, indent=None, width=None,
         allow_unicode=None, line_break=None,
         encoding='utf-8', explicit_start=None, explicit_end=None,
         version=None, tags=None)

Of this only the first one needs to be given, that should be your doc variable. If you don't specify a stream, then dump() writes data structure to an in memory file-object (like StringIO) and after writing returns the value as string.

So although you could do:

with open("file_to_edit.yaml", 'w') as f:
    f.write(yaml.safe_dump(doc))

this is inefficient and shows little understanding of how yaml.safe_dump() works.

If you want to open the file for reading and writing you have to make sure you both reset the index in the file and truncate its content. This is usually not worth the effort so it is safer to re-open the file for writing:

def set_state(state):
    file_name = "file_to_edit.yaml"
    with open(file_name) as f:
        doc = yaml.safe_load(f)
    doc['state'] = state
    with open(file_name, 'w') as f:
        yaml.safe_dump(doc, f, default_flow_style=False)

(of course you make the filename a variable when you want to make sure you overwrite the original, so you cannot mistype it).

If you don't specify default_flow_style=False, your output will look like:

{state: deleted}

The output will not include the superfluous quotes around present in your input. You can specify default_style="'" as well, but this will also put quotes around state.
If losing the quotes is a problem and you really want the output to look like the input, you should be using ruamel.yaml (disclaimer I am the author of that package), which can preserve the quotes on individual strings, handles YAML 1.2 (instead of YAML 1.1) and also preserves comments in your file.



回答3:

You can try the following (it creates a new file to store the result):

with open("output_file", 'a') as out:
    with open("input_file", 'r') as f:
        for line in f:
            line = re.sub(r"state: present", "state: installed", line)
            out.write(line)


回答4:

My guess is that you didn't indent properly, since your function in its current state seems not to do anything. Python does care about indentation.

Try this:

def set_state(state):
   with open("file_to_edit.yaml", 'rw') as f:
      doc = yaml.load(f)
   doc['state'] = state
   yaml.dump(f)