pythondictionary

Translate a python string such as 'a.b.c=v' into a dictionary


How can I read a 'key=value' strings such as ['foo=bar', 'a.b.c=ddd'] and translate them in to a dictionary? The dictionary should either be updated if the key already exists, or have the new key added if it doesn't.

The example given in the paragraph above should translate to the following...

{'foo': 'bar', 'a': {'b': {'c': 'ddd'}}}

But it is instead translates to this incorrect dictionary...

{'foo': 'bar', 'a': {}, 'b': {}, 'c': 'ddd'}

How can I fix the code in the example under the comment 'Set the master config value for the current setting'?

Example Code

### For context - in reality I use an argument parser with two args.
###   --config-file - Path to a YAML configuration file. If present,
###     this file will be read into Python as a dictionary called
###     `master_config`. If not, an empty dictionary of the same name
###     will be created.
###   --configs - Single 'key=value' settings that will be read into
###     `master_config` and either override settings defined in the
###     YAML configuration file, or set a value.

master_config = {'foo': 'baz'}
configs = ['foo=bar', 'a.b.c=ddd']

for config in configs:

    # Split the setting into key and value.
    k, v = config.split('=', 1)

    # Split the key, if it's nested.
    ks = k.split('.')

    # Set the master config value for the current setting.
    l = len(ks)
    for i, x in enumerate(ks):
        if i != l-1:
            if not master_config.get(x):
                master_config[x] = {}
        else:
            master_config[x] = v

print(master_config)

Solution

  • Work backwards to create the nested dict and then update your master_config

    k = 'a'
    v = 'ddd'
    
    ks = k.split('.')[::-1]
    
    master_config = {}
    
    if len(ks) == 1:
        master_config[k] = v
    else:
        nested_dict = {ks[0]: v}
        for x in ks[1:]:
            nested_dict = {x: nested_dict}
        
        master_config.update(nested_dict)
    

    Edit: New solution using a recursive function to deal with key collisions. It will attempt to update the deepest key that stores a dictionary. If the key does not exist or the value is not a dictionary, it will overwrite instead.

    k = 'foo.buzz'
    v = 'fizz'
    
    ks = k.split('.')
    
    master_config = {'foo': {'bar': 'fizz'}}
    
    def update(config, *args):
        if len(args) == 2:
            k, v = args
            config[k] = v
        else:
            k, *args = args
            if k not in config or not isinstance(config[k], dict):
                config[k] = {}
    
            return update(config[k], *args)
    
    update(master_config, *(ks + [v]))