41

Let's say I have a pretty complex dictionary.

{'fruit':'orange','colors':{'dark':4,'light':5}}

Anyway, my objective is to scan every key in this complex multi-level dictionary. Then, append "abc" to the end of each key.

So that it will be:

{'fruitabc':'orange','colorsabc':{'darkabc':4,'lightabc':5}}

How would you do that?

martineau
  • 119,623
  • 25
  • 170
  • 301
TIMEX
  • 259,804
  • 351
  • 777
  • 1,080

15 Answers15

76

Keys cannot be changed. You will need to add a new key with the modified value then remove the old one, or create a new dict with a dict comprehension or the like.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 10
    +1 That's actually right (I don't understand why people downvote without stating their objections)... – 3lectrologos Feb 06 '10 at 14:24
  • 15
    The downvotes were probably because you found a problem not a solution. It's something that can be solved, just not quite in the way that was asked. – James Brooks Feb 06 '10 at 15:48
  • 4
    He's the only one to answer the question, keys cannot be changed, which was what the question actually requested. Upvote. – Rhubarb Jun 28 '10 at 02:20
21

For example like this:

def appendabc(somedict):
    return dict(map(lambda (key, value): (str(key)+"abc", value), somedict.items()))

def transform(multilevelDict):
    new = appendabc(multilevelDict)

    for key, value in new.items():
        if isinstance(value, dict):
            new[key] = transform(value)

    return new

print transform({1:2, "bam":4, 33:{3:4, 5:7}})

This will append "abc" to each key in the dictionary and any value that is a dictionary.

EDIT: There's also a really cool Python 3 version, check it out:

def transform(multilevelDict):
    return {str(key)+"abc" : (transform(value) if isinstance(value, dict) else value) for key, value in multilevelDict.items()}

print(transform({1:2, "bam":4, 33:{3:4, 5:7}}))
AndiDog
  • 68,631
  • 21
  • 159
  • 205
  • 4
    The same works in py2, just without the syntactic sugar: `def transform(multilevelDict): return dict((str(key)+"abc" , (transform(value) if isinstance(value, dict) else value)) for key, value in multilevelDict.items())` – Jochen Ritzel Feb 06 '10 at 14:41
14

I use the following utility function that I wrote that takes a target dict and another dict containing the translation and switches all the keys according to it:

def rename_keys(d, keys):
    return dict([(keys.get(k), v) for k, v in d.items()])

So with the initial data:

data = { 'a' : 1, 'b' : 2, 'c' : 3 }
translation = { 'a' : 'aaa', 'b' : 'bbb', 'c' : 'ccc' }

We get the following:

>>> data
{'a': 1, 'c': 3, 'b': 2}
>>> rename_keys(data, translation)
{'aaa': 1, 'bbb': 2, 'ccc': 3}
Yuval Adam
  • 161,610
  • 92
  • 305
  • 395
  • 1
    You could change `return dict([(keys.get(k), v) for k, v in d.items()])` to `return dict([(keys.get(k,k), v) for k, v in d.items()])` so that if a mapping isn't provided, it passes along the key without mangling it – maschwenk Mar 28 '16 at 20:07
11
>>> mydict={'fruit':'orange','colors':{'dark':4,'light':5}}

>>> def f(mydict):
...  return dict((k+"abc",f(v) if hasattr(v,'keys') else v) for k,v in mydict.items())
... 
>>> f(mydict)
{'fruitabc': 'orange', 'colorsabc': {'darkabc': 4, 'lightabc': 5}}
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
6

My understanding is that you can't change the keys, and that you would need to make a new set of keys and assign their values to the ones the original keys were pointing to.

I'd do something like:

def change_keys(d):
  if type(d) is dict:
    return dict([(k+'abc', change_keys(v)) for k, v in d.items()])
  else:
    return d

new_dict = change_keys(old_dict)
Andrew
  • 191
  • 2
  • 6
5

here's a tight little function:

def keys_swap(orig_key, new_key, d):
    d[new_key] = d.pop(orig_key)

for your particular problem:

def append_to_dict_keys(appendage, d):
    #note that you need to iterate through the fixed list of keys, because
    #otherwise we will be iterating through a never ending key list!
    for each in d.keys():
        if type(d[each]) is dict:
            append_to_dict_keys(appendage, d[each])
        keys_swap(each, str(each) + appendage, d)

append_to_dict_keys('abc', d)
mgoldwasser
  • 14,558
  • 15
  • 79
  • 103
2
#! /usr/bin/env python

d = {'fruit':'orange', 'colors':{'dark':4,'light':5}}

def add_abc(d):
  newd = dict()
  for k,v in d.iteritems():
    if isinstance(v, dict):
      v = add_abc(v)
    newd[k + "abc"] = v
  return newd

d = add_abc(d)
print d
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
1

Something like that

def applytoallkeys( dic, func ):
    def yielder():
        for k,v in dic.iteritems():
            if isinstance( v, dict):
                yield func(k), applytoallkeys( v, func )
            else:
                yield func(k), v
    return dict(yielder())

def appendword( s ):
    def appender( x ):
        return x+s
    return appender

d = {'fruit':'orange','colors':{'dark':4,'light':5}}
print applytoallkeys( d, appendword('asd') )

I kinda like functional style, you can read just the last line and see what it does ;-)

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
0
for k in theDict: theDict[k+'abc']=theDict.pop(k)
  • 1
    This doesn't meet the requirements because it isn't recursive: dictionaries within the dictionary will not have their keys fixed. – Daniel Lyons Jul 26 '13 at 17:55
0

I use this for converting docopt POSIX-compliant command-line keys to PEP8 keys

(e.g. "--option" --> "option", "" --> "option2", "FILENAME" --> "filename")


arguments = docopt.docopt(__doc__)  # dictionary
for key in arguments.keys():
    if re.match('.*[-<>].*', key) or key != key.lower():
        value = arguments.pop(key)
        newkey = key.lower().translate(None, '-<>')
        arguments[newkey] = value
tenfishsticks
  • 452
  • 5
  • 13
0

You could do this with recursion:

import collections
in_dict={'fruit':'orange','colors':{'dark':4,'light':5}}

def transform_dict(d):
    out_dict={}
    for k,v in d.iteritems():
        k=k+'abc'
        if isinstance(v,collections.MutableMapping):
            v=transform_dict(v)            
        out_dict[k]=v
    return out_dict
out_dict=transform_dict(in_dict)
print(out_dict)

# {'fruitabc': 'orange', 'colorsabc': {'darkabc': 4, 'lightabc': 5}}
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
0

Hi I'm a new user but finding an answer for same question, I can't get anything fully functional to my problem, I make this little piece of cake with a full nested replace of keys, you can send list with dict or dict. Finally your dicts can have list with dict or more dict nested and it is all replaced with your new key needs. To indicate who key want replace with a new key use "to" parameter sending a dict. See at end my little example. P/D: Sorry my bad english. =)

def re_map(value, to):
    """
    Transform dictionary keys to map retrieved on to parameters.
    to parameter should have as key a key name to replace an as value key name
    to new dictionary.
    this method is full recursive to process all levels of
    @param value: list with dictionary or dictionary
    @param to: dictionary with re-map keys
    @type to: dict
    @return: list or dict transformed
    """
    if not isinstance(value, dict):
        if not isinstance(value, list):
            raise ValueError(
                         "Only dict or list with dict inside accepted for value argument.")  # @IgnorePep8

    if not isinstance(to, dict):
        raise ValueError("Only dict accepted for to argument.")

    def _re_map(value, to):
        if isinstance(value, dict):
            # Re map dictionary key.
            # If key of original dictionary is not in "to" dictionary use same
            # key otherwise use re mapped key on new dictionary with already
            # value.
            return {
                    to.get(key) or key: _re_map(dict_value, to)
                    for key, dict_value in value.items()
            }
        elif isinstance(value, list):
            # if value is a list iterate it a call _re_map again to parse
            # values on it.
            return [_re_map(item, to) for item in value]
        else:
            # if not dict or list only return value.
            # it can be string, integer or others.
            return value

    result = _re_map(value, to)

    return result

if __name__ == "__main__":
    # Sample test of re_map method.
    # -----------------------------------------
    to = {"$id": "id"}
    x = []
    for i in range(100):
        x.append({
             "$id": "first-dict",
             "list_nested": [{
                 "$id": "list-dict-nested",
                 "list_dic_nested": [{
                    "$id": "list-dict-list-dict-nested"
                     }]
             }],
             "dict_nested": {
                 "$id": "non-nested"
             }
             })

    result = re_map(x, to)

    print(str(result))
0

A functional (and flexible) solution: this allows an arbitrary transform to be applied to keys (recursively for embedded dicts):

def remap_keys(d, keymap_f):
  """returns a new dict by recursively remapping all of d's keys using keymap_f"""
  return dict([(keymap_f(k), remap_keys(v, keymap_f) if isinstance(v, dict) else v) 
    for k,v in d.items()])

Let's try it out; first we define our key transformation function, then apply it to the example:

def transform_key(key):
  """whatever transformation you'd like to apply to keys"""
  return key + "abc"

remap_keys({'fruit':'orange','colors':{'dark':4,'light':5}}, transform_key)
{'fruitabc': 'orange', 'colorsabc': {'darkabc': 4, 'lightabc': 5}}

(note: if you're still on Python 2.x, you'll need to replace d.items() on the last line with d.iteritems() -- thanks to @Rudy for reminding me to update this post for Python 3).

sxc731
  • 2,418
  • 2
  • 28
  • 30
0

Based on @AndiDog's python 3 version and similar to @sxc731's version but with a flag for whether to apply it recursively:

def transform_keys(dictionary, key_fn, recursive=True):
    """
    Applies function to keys and returns as a new dictionary.

    Example of key_fn:
      lambda k: k + "abc"
    """
    return {key_fn(key): (transform_keys(value, key_fn=key_fn, recursive=recursive)
                          if recursive and isinstance(value, dict) else value)
            for key, value in dictionary.items()}
ludvigolsen
  • 181
  • 8
0

you should also consider that there is the possibility of nested dicts in nested lists, which will not be covered by the above solutions. This function ads a prefix and/or a postfix to every key within the dict.

def transformDict(multilevelDict, prefix="", postfix=""):
"""adds a prefix and/or postfix to every key name in a dict"""
new_dict = multilevelDict
if prefix != "" or postfix != "":
    new_key = "%s#key#%s" % (prefix, postfix)
    new_dict = dict(map(lambda (key, value): (new_key.replace('#key#', str(key)), value), new_dict.items()))
    for key, value in new_dict.items():
        if isinstance(value, dict):
            new_dict[key] = transformDict(value, prefix, postfix)
        elif isinstance(value, list):
            for index, item in enumerate(value):
                if isinstance(item, dict):
                    new_dict[key][index] = transformDict(item, prefix, postfix)
return new_dict
Gerald Bäck
  • 445
  • 1
  • 5
  • 7