Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Is there a way to rename a dictionary key, without reassigning its value to a new name and removing the old name key; and without iterating through dict key/value?

In case of OrderedDict do the same, while keeping that key's position.

what exactly do you mean by "without reassigning its value to a new name and removing the old name key"? the way i see it, that is the definition of renaming a key, and all of the answers below reassign the value and remove the old key name. you have yet to accept an answer, so perhaps these haven't accomplished what you're seeking? abcd Apr 6, 2015 at 18:09 You really need to specify version number(s). As of Python 3.7, the language spec now guarantees that dicts follow insertion order . That also makes OrderedDict mostly obsolete (unless a) you want code that also backports to 2.x or 3.6- or b) you care about the issues listed in Will OrderedDict become redundant in Python 3.7? ). And back in 3.6, dictionary insertion order was guaranteed by the CPython implementation (but not the language spec). smci Mar 4, 2019 at 5:38 @smci In Python 3.7 dicts follow insertion order, however there are still different from OrderedDict . dicts ignore order when they are being compared for equality, whereas OrderedDict take order into account when being compared. I know you linked to something that explains it, but I thought your comment might have misled those who may not have read that link. Flimm Dec 5, 2019 at 13:08 @smci I disagree with your opinion that differences between dict and OrderedDict can be glossed over and that OrderedDict is now obsolete. I think also debating this opinion is out-of-topic for this question. The question you linked to is a better place to talk about it. Interestingly, the only upvoted answer on the question you linked to agrees that the differences matter and that OrderedDict is not obsolete. If you have a different answer, go post it there and let the community vote. stackoverflow.com/q/50872498/247696 Flimm Dec 6, 2019 at 8:21
mydict[k_new] = mydict.pop(k_old)

This will move the item to the end of the dict, unless k_new was already existing in which case it will overwrite the value in-place.

For a Python 3.7+ dict where you additionally want to preserve the ordering, the simplest is to rebuild an entirely new instance. For example, renaming key 2 to 'two':

>>> d = {0:0, 1:1, 2:2, 3:3}
>>> {"two" if k == 2 else k:v for k,v in d.items()}
{0: 0, 1: 1, 'two': 2, 3: 3}

The same is true for an OrderedDict, where you can't use dict comprehension syntax, but you can use a generator expression:

OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())

Modifying the key itself, as the question asks for, is impractical because keys are hashable which usually implies they're immutable and can't be modified.

Nope, OrderedDict will preserve insertion order, which is not what the question asked about. – wim Dec 5, 2017 at 16:20 When using the approach with rebuilding an entirely new instance (although only single key has to be changed), what would be the performance for really huge dictionaries (lot of items)? – Nerxis Jun 15, 2021 at 7:46 If renaming keys inside a loop iterating through the dictionary, this method will generate RuntimeError: dictionary keys changed during iteration. Rebuilding the container will fix this issue too. For example the list() in for key_old, value in list(d.items()): – Bob Stein Nov 22, 2022 at 17:30 this works beautifully, but does not maintain the original order because the new key gets added at the end by default (in python3). – szeitlin Oct 1, 2018 at 21:14 @szeitlin you shouldn't rely on dict order, even if python3.6+ intializes it in ordered form, previous versions don't and it's not really a feature just an afteraffect of how py3.6+ dicts were change. Use OrderedDict if you care about order. – Granitosaurus Nov 15, 2018 at 8:05 Thanks @Granitosaurus, I did not need you to explain that to me. That was not the point of my comment. – szeitlin Nov 15, 2018 at 19:30 @szeitlin your comment implied that the fact that it changes the dict order matters in some way, shape or form when in reality no one should rely on dictionary order so this fact is completely irrelevant – Granitosaurus Nov 16, 2018 at 7:51 So, the above argumentative comments were relevant in Python 3.6, but as of 3.7+, dicts are definitely ordered. – mattdm Jul 31, 2021 at 14:45

In case of renaming all dictionary keys:

target_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
new_keys = ['k4','k5','k6']
for key,n_key in zip(target_dict.keys(), new_keys):
    target_dict[n_key] = target_dict.pop(key)
                Is target_dict.keys() guaranteed to be the same order as in definition? I thought a Python dict is unordered, and the order from a dict's keys view is unpredictable
– ttimasdf
                Jan 14, 2020 at 7:55
                The for loop can be simplified  target_dict = dict(zip(new_keys, list(target_dict.values())))
– vinodhraj
                Sep 6, 2022 at 18:41

You can use this OrderedDict recipe written by Raymond Hettinger and modify it to add a rename method, but this is going to be a O(N) in complexity:

def rename(self,key,new_key):
    ind = self._keys.index(key)  #get the index of old key, O(N) operation
    self._keys[ind] = new_key    #replace old key with new key in self._keys
    self[new_key] = self[key]    #add the new key, this is added at the end of self._keys
    self._keys.pop(-1)           #pop the last item in self._keys

Example:

dic = OrderedDict((("a",1),("b",2),("c",3)))
print dic
dic.rename("a","foo")
dic.rename("b","bar")
dic["d"] = 5
dic.rename("d","spam")
for k,v in  dic.items():
    print k,v

output:

OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
spam 5
                @MERose Add the Python file somewhere in your module search path and import OrderedDict from it. But I would recommend: Create a class that inherits from OrderedDict and add a rename method to it.
– Ashwini Chaudhary
                Jun 9, 2015 at 10:09
                Seems like OrderedDict has since been rewritten to use a doubly-linked list, so there's probably still a way to do this, but it requires a lot more acrobatics. :-/
– szeitlin
                Oct 1, 2018 at 21:36

A few people before me mentioned the .pop trick to delete and create a key in a one-liner.

I personally find the more explicit implementation more readable:

d = {'a': 1, 'b': 2}
v = d['b']
del d['b']
d['c'] = v

The code above returns {'a': 1, 'c': 2}

This will return a TypeError: 'dict' object is not callable If with temp_dict('k3') you are trying to reference the value of the k3 key then you should use square brackets and not parentheses. However, this will just add a new key to the dictionary and won't rename an existing key, as requested. – Mewtwo Nov 11, 2019 at 10:28

Other answers are pretty good.But in python3.6, regular dict also has order. So it's hard to keep key's position in normal case.

def rename(old_dict,old_name,new_name):
    new_dict = {}
    for key,value in zip(old_dict.keys(),old_dict.values()):
        new_key = key if key != old_name else new_name
        new_dict[new_key] = old_dict[key]
    return new_dict

In Python 3.6 (onwards?) I would go for the following one-liner

test = {'a': 1, 'old': 2, 'c': 3}
old_k = 'old'
new_k = 'new'
new_v = 4  # optional
print(dict((new_k, new_v) if k == old_k else (k, v) for k, v in test.items()))

which produces

{'a': 1, 'new': 4, 'c': 3}

May be worth noting that without the print statement the ipython console/jupyter notebook present the dictionary in an order of their choosing...

I am using @wim 's answer above, with dict.pop() when renaming keys, but I found a gotcha. Cycling through the dict to change the keys, without separating the list of old keys completely from the dict instance, resulted in cycling new, changed keys into the loop, and missing some existing keys.

To start with, I did it this way:

for current_key in my_dict:
    new_key = current_key.replace(':','_')
    fixed_metadata[new_key] = fixed_metadata.pop(current_key)

I found that cycling through the dict in this way, the dictionary kept finding keys even when it shouldn't, i.e., the new keys, the ones I had changed! I needed to separate the instances completely from each other to (a) avoid finding my own changed keys in the for loop, and (b) find some keys that were not being found within the loop for some reason.

I am doing this now:

current_keys = list(my_dict.keys())
for current_key in current_keys:
    and so on...

Converting the my_dict.keys() to a list was necessary to get free of the reference to the changing dict. Just using my_dict.keys() kept me tied to the original instance, with the strange side effects.

In case someone wants to rename all the keys at once providing a list with the new names:

def rename_keys(dict_, new_keys):
     new_keys: type List(), must match length of dict_
    # dict_ = {oldK: value}
    # d1={oldK:newK,} maps old keys to the new ones:  
    d1 = dict( zip( list(dict_.keys()), new_keys) )
          # d1{oldK} == new_key 
    return {d1[oldK]: value for oldK, value in dict_.items()}

I came up with this function which does not mutate the original dictionary. This function also supports list of dictionaries too.

import functools
from typing import Union, Dict, List
def rename_dict_keys(
    data: Union[Dict, List[Dict]], old_key: str, new_key: str
    This function renames dictionary keys
    :param data:
    :param old_key:
    :param new_key:
    :return: Union[Dict, List[Dict]]
    if isinstance(data, dict):
        res = {k: v for k, v in data.items() if k != old_key}
            res[new_key] = data[old_key]
        except KeyError:
            raise KeyError(
                "cannot rename key as old key '%s' is not present in data"
                % old_key
        return res
    elif isinstance(data, list):
        return list(
                functools.partial(
                    rename_dict_keys, old_key=old_key, new_key=new_key
                data,
    raise ValueError("expected type List[Dict] or Dict got '%s' for data" % type(data))
                Seems complex at first, but is actually quite versatile and could be reused in my code (I altered it so new and old key inputs can also be lists).
– Moritz
                Jul 20, 2022 at 12:36

@helloswift123 I like your function. Here is a modification to rename multiple keys in a single call:

def rename(d, keymap):
    :param d: old dict
    :type d: dict
    :param keymap: [{:keys from-keys :values to-keys} keymap]
    :returns: new dict
    :rtype: dict
    new_dict = {}
    for key, value in zip(d.keys(), d.values()):
        new_key = keymap.get(key, key)
        new_dict[new_key] = d[key]
    return new_dict
def DictKeyChanger(dict,OldKey,NewKey):
    ListAllKey=list(dict.keys())
    for x in range(0,len(NewKey)):
        dict[NewKey[x]]=dict[OldKey[x]] if OldKey[x] in ListAllKey else None
    for x in ListAllKey:
        dict.pop(x)
    return dict
NewDict=DictKeyChanger(OldDict,OldKey,NewKey)
print(NewDict)#===>>{'A': 'v1', 'B': 'v2', 'C': 'v3'}

Notes:

  • The length of list OldKey and list NewKey must be equal.
  • The length of the list OldKey must be equal to the listNewKey, if the key does not exist in the OldKey, put 'noexis' instead as shown as.
  • Example:

    OldDict={'a':'v1', 'b':'v2', 'c':'v3'}
    OldKey=['a','b','c','noexis','noexis']
    NewKey=['A','B','C','D','E']
    NewDict=DictKeyChanger(OldDict,OldKey,NewKey)
    print(NewDict)#===>>{'A': 'v1', 'B': 'v2', 'C': 'v3', 'D': None, 'E': None}
    

    For the keeping of order case (the other one is trivial, remove old and add new one): I was not satisfied with the ordered-dictionary needing reconstruction (at least partially), obviously for efficiency reasons, so I've put together a class (OrderedDictX) that extends OrderedDict and allows you to do key changes efficiently, i.e. in O(1) complexity. The implementation can also be adjusted for the now-ordered built-in dict class.

    It uses 2 extra dictionaries to remap the changed keys ("external" - i.e. as they appear externally to the user) to the ones in the underlying OrderedDict ("internal") - the dictionaries will only hold keys that were changed so as long as no key changing is done they will be empty.

    Performance measurements:

    import timeit
    import random
    # Efficiency tests
    from collections import MutableMapping
    class OrderedDictRaymond(dict, MutableMapping):
        def __init__(self, *args, **kwds):
            if len(args) > 1:
                raise TypeError('expected at 1 argument, got %d', len(args))
            if not hasattr(self, '_keys'):
                self._keys = []
            self.update(*args, **kwds)
        def rename(self,key,new_key):
            ind = self._keys.index(key)  #get the index of old key, O(N) operation
            self._keys[ind] = new_key    #replace old key with new key in self._keys
            self[new_key] = self[key]    #add the new key, this is added at the end of self._keys
            self._keys.pop(-1)           #pop the last item in self._keys
            dict.__delitem__(self, key)
        def clear(self):
            del self._keys[:]
            dict.clear(self)
        def __setitem__(self, key, value):
            if key not in self:
                self._keys.append(key)
            dict.__setitem__(self, key, value)
        def __delitem__(self, key):
            dict.__delitem__(self, key)
            self._keys.remove(key)
        def __iter__(self):
            return iter(self._keys)
        def __reversed__(self):
            return reversed(self._keys)
        def popitem(self):
            if not self:
                raise KeyError
            key = self._keys.pop()
            value = dict.pop(self, key)
            return key, value
        def __reduce__(self):
            items = [[k, self[k]] for k in self]
            inst_dict = vars(self).copy()
            inst_dict.pop('_keys', None)
            return (self.__class__, (items,), inst_dict)
        setdefault = MutableMapping.setdefault
        update = MutableMapping.update
        pop = MutableMapping.pop
        keys = MutableMapping.keys
        values = MutableMapping.values
        items = MutableMapping.items
        def __repr__(self):
            pairs = ', '.join(map('%r: %r'.__mod__, self.items()))
            return '%s({%s})' % (self.__class__.__name__, pairs)
        def copy(self):
            return self.__class__(self)
        @classmethod
        def fromkeys(cls, iterable, value=None):
            d = cls()
            for key in iterable:
                d[key] = value
            return d
    class obj_container:
        def __init__(self, obj) -> None:
            self.obj = obj
    def change_key_splice(container, k_old, k_new):
        od = container.obj
        container.obj = OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())
    def change_key_raymond(container, k_old, k_new):
        od = container.obj
        od.rename(k_old, k_new)
    def change_key_odx(container, k_old, k_new):
        odx = container.obj
        odx.change_key(k_old, k_new)
    NUM_ITEMS = 20000
    od_splice = OrderedDict([(x, x) for x in range(NUM_ITEMS)])
    od_raymond = OrderedDictRaymond(od_splice.items())
    odx = OrderedDictX(od_splice.items())
    od_splice, od_raymond, odx = [obj_container(d) for d in [od_splice, od_raymond, odx]]
    assert odx.obj == od_splice.obj
    assert odx.obj == od_raymond.obj
    # Pick randomly half of the keys to change
    keys_to_change = random.sample(range(NUM_ITEMS), NUM_ITEMS//2)
    print(f'OrderedDictX: {timeit.timeit(lambda: [change_key_odx(odx, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
    print(f'OrderedDictRaymond: {timeit.timeit(lambda: [change_key_raymond(od_raymond, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
    print(f'Splice: {timeit.timeit(lambda: [change_key_splice(od_splice, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
    assert odx.obj == od_splice.obj
    assert odx.obj == od_raymond.obj
    

    And results:

    OrderedDictX: 0.06587849999999995
    OrderedDictRaymond: 1.1131364
    Splice: 1165.2614647
    

    As expected, the splicing method is extremely slow (didn't expect it to be that much slower either though) and uses a lot of memory, and the O(N) solution of @Ashwini Chaudhary (bug-fixed though, del also needed) is also slower, 17X times in this example.

    Of course, this solution being O(1), compared to the O(N) OrderedDictRaymond the time difference becomes much more apparent as the dictionary size increases, e.g. for 5 times more elements (100000), the O(N) is now 100X slower:

    NUM_ITEMS = 100000
    OrderedDictX: 0.3636919999999999
    OrderedDictRaymond: 36.3963971
    

    Here's the code, please comment if you see issues or have improvements to propose as this might still be error-prone.

    from collections import OrderedDict
    class OrderedDictX(OrderedDict):
        def __init__(self, *args, **kwargs):
            # Mappings from new->old (ext2int), old->new (int2ext).
            # Only the keys that are changed (internal key doesn't match what the user sees) are contained.
            self._keys_ext2int = OrderedDict()
            self._keys_int2ext = OrderedDict()
            self.update(*args, **kwargs)
        def change_key(self, k_old, k_new):
            # Validate that the old key is part of the dict
            if not self.__contains__(k_old):
                raise Exception(f'Cannot rename key {k_old} to {k_new}: {k_old} not existing in dict')
            # Return if no changing is actually to be done
            if len(OrderedDict.fromkeys([k_old, k_new])) == 1:
                return
            # Validate that the new key would not conflict with another one
            if self.__contains__(k_new):
                raise Exception(f'Cannot rename key {k_old} to {k_new}: {k_new} already in dict')
            # Change the key using internal dicts mechanism
            if k_old in self._keys_ext2int:
                # Revert change temporarily
                k_old_int = self._keys_ext2int[k_old]
                del self._keys_ext2int[k_old]
                k_old = k_old_int
                # Check if new key matches the internal key
                if len(OrderedDict.fromkeys([k_old, k_new])) == 1:
                    del self._keys_int2ext[k_old]
                    return
            # Finalize key change
            self._keys_ext2int[k_new] = k_old
            self._keys_int2ext[k_old] = k_new
        def __contains__(self, k) -> bool:
            if k in self._keys_ext2int:
                return True
            if not super().__contains__(k):
                return False
            return k not in self._keys_int2ext
        def __getitem__(self, k):
            if not self.__contains__(k):
                # Intentionally raise KeyError in ext2int
                return self._keys_ext2int[k]
            return super().__getitem__(self._keys_ext2int.get(k, k))
        def __setitem__(self, k, v):
            if k in self._keys_ext2int:
                return super().__setitem__(self._keys_ext2int[k], v)
            # If the key exists in the internal state but was renamed to a k_ext,
            # employ this trick: make it such that it appears as if k_ext has also been renamed to k
            if k in self._keys_int2ext:
                k_ext = self._keys_int2ext[k]
                self._keys_ext2int[k] = k_ext
                k = k_ext
            return super().__setitem__(k, v)
        def __delitem__(self, k):
            if not self.__contains__(k):
                # Intentionally raise KeyError in ext2int
                del self._keys_ext2int[k]
            if k in self._keys_ext2int:
                k_int = self._keys_ext2int[k]
                del self._keys_ext2int[k]
                del self._keys_int2ext[k_int]
                k = k_int
            return super().__delitem__(k)
        def __iter__(self):
            yield from self.keys()
        def __reversed__(self):
            for k in reversed(super().keys()):
                yield self._keys_int2ext.get(k, k)
        def __eq__(self, other: object) -> bool:
            if not isinstance(other, dict):
                return False
            if len(self) != len(other):
                return False
            for (k, v), (k_other, v_other) in zip(self.items(), other.items()):
                if k != k_other or v != v_other:
                    return False
            return True
        def update(self, *args, **kwargs):
            for k, v in OrderedDict(*args, **kwargs).items():
                self.__setitem__(k, v)
        def popitem(self, last=True) -> tuple:
            if not last:
                k = next(iter(self.keys()))
            else:
                k = next(iter(reversed(self.keys())))
            v = self.__getitem__(k)
            self.__delitem__(k)
            return k, v
        class OrderedDictXKeysView:
            def __init__(self, odx: 'OrderedDictX', orig_keys):
                self._odx = odx
                self._orig_keys = orig_keys
            def __iter__(self):
                for k in self._orig_keys:
                    yield self._odx._keys_int2ext.get(k, k)
            def __reversed__(self):
                for k in reversed(self._orig_keys):
                    yield self._odx._keys_int2ext.get(k, k)
        class OrderedDictXItemsView:
            def __init__(self, odx: 'OrderedDictX', orig_items):
                self._odx = odx
                self._orig_items = orig_items
            def __iter__(self):
                for k, v in self._orig_items:
                    yield self._odx._keys_int2ext.get(k, k), v
            def __reversed__(self):
                for k, v in reversed(self._orig_items):
                    yield self._odx._keys_int2ext.get(k, k), v
        def keys(self):
            return self.OrderedDictXKeysView(self, super().keys())
        def items(self):
            return self.OrderedDictXItemsView(self, super().items())
        def copy(self):
            return OrderedDictX(self.items())    
    # FIXME: move this to pytest
    if __name__ == '__main__':
        MAX = 25
        items = [(i+1, i+1) for i in range(MAX)]
        keys = [i[0] for i in items]
        d = OrderedDictX(items)
        # keys() before change
        print(list(d.items()))
        assert list(d.keys()) == keys
        # __contains__ before change
        assert 1 in d
        # __getitem__ before change
        assert d[1] == 1
        # __setitem__ before change
        d[1] = 100
        assert d[1] == 100
        d[1] = 1
        assert d[1] == 1
        # __delitem__ before change
        assert MAX in d
        del d[MAX]
        assert MAX not in d
        d[MAX] = MAX
        assert MAX in d
        print('== Tests before key change finished ==')
        # change_key and __contains__
        assert MAX-1 in d
        assert MAX*2 not in d
        d.change_key(MAX-1, MAX*2)
        assert MAX-1 not in d
        assert MAX*2 in d
        # items() and keys()
        items[MAX-2] = (MAX*2, MAX-1)
        keys[MAX-2] = MAX*2
        assert list(d.items()) == items
        assert list(d.keys()) == keys
        print(list(d.items()))
        # __getitem__
        assert d[MAX*2] == MAX-1
        # __setitem__
        d[MAX*2] = MAX*3
        items[MAX-2] = (MAX*2, MAX*3)
        keys[MAX-2] = MAX*2
        assert list(d.items()) == items
        assert list(d.keys()) == keys
        # __delitem__
        del d[MAX]
        items = items[:-1]
        keys = keys[:-1]
        assert list(d.items()) == items
        assert list(d.keys()) == keys
        d[MAX] = MAX
        items.append((MAX, MAX))
        keys.append(MAX)
        # __iter__
        assert list(d) == keys
        # __reversed__
        print(list(reversed(d.items())))
        assert list(reversed(d)) == list(reversed(keys))
        assert list(reversed(d.keys())) == list(reversed(keys))
        assert list(reversed(d.items())) == list(reversed(items))
        # pop_item()
        assert d.popitem() == (MAX, MAX)
        assert d.popitem() == (MAX*2, MAX*3)
        items = items[:-2]
        keys = keys[:-2]
        assert list(d.items()) == items
        assert list(d.keys()) == keys
        # update()
        d.update({1: 1000, MAX-2: MAX*4})
        items[0] = (1, 1000)
        items[MAX-3] = (MAX-2, MAX*4)
        assert list(d.items()) == items
        assert list(d.keys()) == keys
        # move_to_end()
        d.move_to_end(1)
        items = items[1:] + [items[0]]
        keys = keys[1:] + [keys[0]]
        assert list(d.items()) == items
        assert list(d.keys()) == keys
        # __eq__
        d.change_key(1, 2000)
        other_d = OrderedDictX(d.items())
        assert d == other_d
        assert other_d == d
    

    In my case, I had a function call returning a dict, which had a key I was hoping to rename in a single line, so none of these worked for me. Starting in python 3.8, you can use the walrus operator to keep it to one line if you are not looking for an inplace operation and the dict is not yet defined.

    old_dict = get_dict()  
    # old_dict = {'a': 1, 'b': 2, 'c': 3}
    new_dict = {'new1': (x := get_dict()).pop('b'), **x}  
    # new_dict = {'a': 1, 'new1': 2, 'c': 3}
    

    I have combined some answers from the above thread and come up with the solution below. Although it is simple it can be used as a building block for making more complex key updates from a dictionary.

    test_dict = {'a': 1, 'b': 2, 'c': 3}
    print(test_dict)
    # {'a': 1, 'b': 2, 'c': 3}
    prefix = 'up'
    def dict_key_update(json_file):    
        new_keys = []
        old_keys = []
        for i,(key,value) in enumerate(json_file.items()):
            old_keys.append(key)
            new_keys.append(str(prefix) + key) # i have updated by adding a prefix to the 
            # key
        for old_key, new_key in zip(old_keys,new_keys):
            print('old {}, new {}'.format(old_key, new_key))
            if new_key!=old_key:  
               json_file[new_key] = json_file.pop(old_key)
         return json_file
    test_dict = dict_key_update(test_dict)
    print(test_dict)
    # {'upa': 1, 'upb': 2, 'upc': 3}