141

Let's say we have a Python dictionary d, and we're iterating over it like so:

for k, v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

(f and g are just some black-box transformations.)

In other words, we try to add/remove items to d while iterating over it using iteritems.

Is this well defined? Could you provide some references to support your answer?


See also How to avoid "RuntimeError: dictionary changed size during iteration" error? for the separate question of how to avoid the problem.

8

9 Answers 9

98

Alex Martelli weighs in on this here.

It may not be safe to change the container (e.g. dict) while looping over the container. So del d[f(k)] may not be safe. As you know, the workaround is to use d.copy().items() (to loop over an independent copy of the container) instead of d.iteritems() or d.items() (which use the same underlying container).

It is okay to modify the value at an existing index of the dict, but inserting values at new indices (e.g. d[g(k)] = v) may not work.

Sign up to request clarification or add additional context in comments.

16 Comments

I think this is a key answer for me. A lot of use cases will have one process inserting things and another cleaning things up/deleting them so the advice to use d.items() works. Python 3 caveats not withstanding
More information about the Python 3 caveats can be found in PEP 469 where the semantic equivalents of the aforementioned Python 2 dict methods are enumerated.
"It is okay to modify the value at an existing index of the dict" -- do you have a reference for this?
@JonathonReinhart: No, I don't have a reference for this, but I think it is pretty standard in Python. For example, Alex Martelli was a Python core developer and demonstrates its usage here.
You have to use deep copy, not just copy, import copy copy.deepcopy(this_dict), see the other answer. Just d.copy().items() is not enough to avoid update problems on the run.
|
69

It is explicitly mentioned on the Python doc page (for Python 2.7) that

Using iteritems() while adding or deleting entries in the dictionary may raise a RuntimeError or fail to iterate over all entries.

Similarly for Python 3.

The same holds for iter(d), d.iterkeys() and d.itervalues(), and I'll go as far as saying that it does for for k, v in d.items(): (I can't remember exactly what for does, but I would not be surprised if the implementation called iter(d)).

6 Comments

I will embarrass myself for the sake of the community by stating that I used the very code snippet. Thinking that since I didn't get a RuntimeError I thought everything was good. And it was, for a while. Anally retentive unit tests were giving me the thumbs up and and it was even running well when it was released. Then, I started getting bizarre behavior. What was happening was that items in the dictionary were getting skipped over and so not all items in the dictionary were being scanned. Kids, learn from the mistakes that I have made in my life and just say no! ;)
Can I run in to problems if I'm changing the value at the current key (but not adding or removing any keys?) I would imaaaagine that this shouldn't cause any problems, but I'd like to know!
@GershomMaes I don't know of any, but you may still be running into a minefield should your loop body make use of the value and not expecting it to change.
d.items() should be safe in Python 2.7 (the game changes with Python 3), as it makes what is essentially a copy of d, so you're not modifying what you're iterating over.
It would be interesting to know if this is also true for viewitems()
|
38

You cannot do that, at least with d.iteritems(). I tried it, and Python fails with

RuntimeError: dictionary changed size during iteration

If you instead use d.items(), then it works.

In Python 3, d.items() is a view into the dictionary, like d.iteritems() in Python 2. To do this in Python 3, instead use d.copy().items(). This will similarly allow us to iterate over a copy of the dictionary in order to avoid modifying the data structure we are iterating over.

5 Comments

FYI, the literal translation (as e.g. used by 2to3) of Py2's d.items() to Py3 is list(d.items()), although d.copy().items() is probably of comparable efficiency.
If the dict object is very large, is d.copy().items() efficiet?
@dragonfly I guess that's a rhetorical question? An alternate workaround suggested in other answers is to collect the changes you want to perform to a new list, and then separately apply those changes when you are out of the loop. If you need to change only some of the entries in the dictionary, this might require less memory (obviously also depending on the complexity of the individual values).
You have to use deep copy, not just copy, import copy copy.deepcopy(this_dict), see the other answer. Just d.copy().items() is not enough to avoid update problems on the run.
Ignore the deepcopy comment, it's not needed.
18

I have a large dictionary containing Numpy arrays, so the dict.copy().keys() thing suggested by @murgatroid99 was not feasible (though it worked). Instead, I just converted the keys_view to a list and it worked fine (in Python 3.4):

for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

I realize this doesn't dive into the philosophical realm of Python's inner workings like the answers above, but it does provide a practical solution to the stated problem.

3 Comments

Note that dict.copy() is not a deep copy so it doesn't really matter what's in your dict since the values won't be copied.
@philippjfr Your comment is not against this solution here, though, you only say that the mere dict.copy().keys() would not solve it either, but you would need a deep copy instead. That stands only against the first sentence, not against the solution. I guess that is not clear to everyone reading it. Making a list and then making a dictionary from it seems to work. Only loops over dictionaries are not good since you cannot change a dictionary while looping over it without the risk of losing data during updates.
If you wanted to use a deep copy, not just copy, use import copy copy.deepcopy(this_dict), see the other answer. Just d.copy().items() is not enough to avoid update problems on the run.
6

The following code shows that this is not well defined:

def f(x):
    return x

def g(x):
    return x+1

def h(x):
    return x+10

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[g(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[h(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

The first example calls g(k), and throws an exception (dictionary changed size during iteration).

The second example calls h(k) and throws no exception, but outputs:

{21: 'axx', 22: 'bxx', 23: 'cxx'}

Which, looking at the code, seems wrong - I would have expected something like:

{11: 'ax', 12: 'bx', 13: 'cx'}

2 Comments

I can understand why you might expect {11: 'ax', 12: 'bx', 13: 'cx'} but the 21,22,23 should give you clue as to what actually happened: your loop went through items 1, 2, 3, 11, 12, 13 but didn't manage to pick up the second round of new items as they got inserted in front of the items you had already iterated over. Change h() to return x+5 and you get another x: 'axxx' etc. or 'x+3' and you get the magnificent 'axxxxx'
Yeah, my mistake I'm afraid - my expected output was {11: 'ax', 12: 'bx', 13: 'cx'} as you said, so I'll update my post about it. Either way, this is clearly not well defined behaviour.
4

Python 3 you should just:

prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict() 
for k,v in t.items():
    t2[k] = prefix + v

or use:

t2 = t1.copy()

You should never modify original dictionary, it leads to confusion as well as potential bugs or RunTimeErrors. Unless you just append to the dictionary with new key names.

Comments

2

This question asks about using an iterator (and funny enough, that Python 2 .iteritems iterator is no longer supported in Python 3) to delete or add items, and it must have a No as its only right answer as you can find it in the accepted answer. Yet: most of the searchers try to find a solution, they will not care how this is done technically, be it an iterator or a recursion, and there is a solution for the problem:

You cannot loop-change a dict without using an additional (recursive) function.

This question should therefore be linked to a question that has a working solution:

By the same recursive methods, you will also able to add items as the question asks for as well.


Since my request to link this question was declined, here is a copy of the solution that can delete items from a dict. See How can I remove a key:value pair wherever the chosen key occurs in a deeply nested dictionary? (= "delete") for examples / credits / notes.

import copy

def find_remove(this_dict, target_key, bln_overwrite_dict=False):
    if not bln_overwrite_dict:
        this_dict = copy.deepcopy(this_dict)

    for key in this_dict:
        # if the current value is a dict, dive into it
        if isinstance(this_dict[key], dict):
            if target_key in this_dict[key]:
                this_dict[key].pop(target_key)

            this_dict[key] = find_remove(this_dict[key], target_key)

    return this_dict

dict_nested_new = find_remove(nested_dict, "sub_key2a")

The trick

The trick is to find out in advance whether a target_key is among the next children (= this_dict[key] = the values of the current dict iteration) before you reach the child level recursively. Only then you can still delete a key:value pair of the child level while iterating over a dictionary. Once you have reached the same level as the key to be deleted and then try to delete it from there, you would get the error:

RuntimeError: dictionary changed size during iteration

The recursive solution makes any change only on the next values' sub-level and therefore avoids the error.

Comments

0

I got the same problem and I used following procedure to solve this issue.

Python List can be iterate even if you modify during iterating over it. so for following code it will print 1's infinitely.

for i in list:
   list.append(1)
   print 1

So using list and dict collaboratively you can solve this problem.

d_list=[]
 d_dict = {} 
 for k in d_list:
    if d_dict[k] is not -1:
       d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted)
       d_dict[g(k)] = v # add a new item 
       d_list.append(g(k))

2 Comments

I'm not sure if it is safe to modify a list during iteration (although it may work in some cases). See this question for example...
@Roman If you want to delete elements of a list, you can safely iterate over it in reverse order, since in normal order the index of the next element would change upon deletion. See this example.
0

Today I had a similar use-case, but instead of simply materializing the keys on the dictionary at the beginning of the loop, I wanted changes to the dict to affect the iteration of the dict, which was an ordered dict.

I ended up building the following routine, which can also be found in jaraco.itertools:

def _mutable_iter(dict):
    """
    Iterate over items in the dict, yielding the first one, but allowing
    it to be mutated during the process.
    >>> d = dict(a=1)
    >>> it = _mutable_iter(d)
    >>> next(it)
    ('a', 1)
    >>> d
    {}
    >>> d.update(b=2)
    >>> list(it)
    [('b', 2)]
    """
    while dict:
        prev_key = next(iter(dict))
        yield prev_key, dict.pop(prev_key)

The docstring illustrates the usage. This function could be used in place of d.iteritems() above to have the desired effect.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.