user1530318
user1530318

Reputation: 27697

How to avoid "RuntimeError: dictionary changed size during iteration" error?

Suppose I have a dictionary of lists:

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

Now I want to remove key-value pairs where the values are empty lists. I tried this code:

for i in d:
    if not d[i]:
        d.pop(i)

but this gives an error:

RuntimeError: dictionary changed size during iteration

I understand that entries can't be added or removed from a dictionary while iterating through it. How can I work around this limitation in order to solve the problem?


See Modifying a Python dict while iterating over it for citations that this can cause problems, and why.

Upvotes: 531

Views: 599690

Answers (14)

Rohit
Rohit

Reputation: 47

Use a list to collect the keys that should be removed; then use the pop dictionary method to remove the identified keys while iterating through the list (a separate object, so the error will not occur).

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

for i in d:
    if not d[i]:
        pop_list.append(i)

for x in pop_list:
    d.pop(x)

print(d)

Upvotes: 3

Ganesh S
Ganesh S

Reputation: 431

This approach can be used if the values in the dictionary are also unique:

keyToBeDeleted = None
for k, v in mydict.items():
    if(v == match):
        keyToBeDeleted = k
        break
mydict.pop(keyToBeDeleted, None)

Upvotes: -1

Mark Byers
Mark Byers

Reputation: 838216

In Python 3.x and 2.x you can use use list to force a copy of the keys to be made:

for i in list(d):

In Python 2.x calling .keys made a copy of the keys that you could iterate over while modifying the dict:

for i in d.keys():

but on Python 3.x, .keys returns a view object instead, so it won't fix your error.

Upvotes: 823

Md Shayon
Md Shayon

Reputation: 375

  • The Python "RuntimeError: dictionary changed size during iteration" occurs when we change the size of a dictionary when iterating over it.

  • To solve the error, use the copy() method to create a shallow copy of the dictionary that you can iterate over, e.g., my_dict.copy().

    my_dict = {'a': 1, 'b': 2, 'c': 3}
    
    for key in my_dict.copy():
        print(key)
        if key == 'b':
            del my_dict[key]
    
    print(my_dict) # ๐Ÿ‘‰๏ธ {'a': 1, 'c': 3}
    
  • You can also convert the keys of the dictionary to a list and iterate over the list of keys.

    my_dict = {'a': 1, 'b': 2, 'c': 3}
    
    for key in list(my_dict.keys()):
        print(key)
        if key == 'b':
            del my_dict[key]
    
    print(my_dict)  # ๐Ÿ‘‰๏ธ {'a': 1, 'c': 3}
    

Upvotes: 1

K.A
K.A

Reputation: 1659

To avoid "dictionary changed size during iteration error".

For example: "when you try to delete some key",

Just use 'list' with '.items()'. Here is a simple example:

my_dict = {
    'k1':1,
    'k2':2,
    'k3':3,
    'k4':4
 
    }
    
print(my_dict)

for key, val in list(my_dict.items()):
    if val == 2 or val == 4:
        my_dict.pop(key)

print(my_dict)

Output:

{'k1': 1, 'k2': 2, 'k3': 3, 'k4': 4}

{'k1': 1, 'k3': 3}

This is just an example. Change it based on your case/requirements.

Upvotes: 17

Hasham Beyg
Hasham Beyg

Reputation: 363

Python 3 does not allow deletion while iterating (using the for loop above) a dictionary. There are various alternatives to do it; one simple way is to change the line

for i in x.keys():

with

for i in list(x)

Upvotes: 2

Alvaro Romero Diaz
Alvaro Romero Diaz

Reputation: 320

You cannot iterate through a dictionary while itโ€™s changing during a for loop. Make a casting to list and iterate over that list. It works for me.

    for key in list(d):
        if not d[key]:
            d.pop(key)

Upvotes: 8

Ajayi Oluwaseun Emmanuel
Ajayi Oluwaseun Emmanuel

Reputation: 1009

For situations like this, I like to make a deep copy and loop through that copy while modifying the original dict.

If the lookup field is within a list, you can enumerate in the for loop of the list and then specify the position as the index to access the field in the original dict.

Upvotes: 0

Maria Zverina
Maria Zverina

Reputation: 11173

Just use dictionary comprehension to copy the relevant items into a new dict:

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = {k: v for k, v in d.items() if v}
>>> d
{'a': [1], 'b': [1, 2]}

For this in Python 2:

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = {k: v for k, v in d.iteritems() if v}
>>> d
{'a': [1], 'b': [1, 2]}

Upvotes: 73

Echo9k
Echo9k

Reputation: 584

Nested null values

Let's say we have a dictionary with nested keys, some of which are null values:

dicti = {
"k0_l0":{
    "k0_l1": {
        "k0_l2": {
                "k0_0":None,
                "k1_1":1,
                "k2_2":2.2
                }
        },
        "k1_l1":None,
        "k2_l1":"not none",
        "k3_l1":[]
    },
    "k1_l0":"l0"
}

Then we can remove the null values using this function:

def pop_nested_nulls(dicti):
    for k in list(dicti):
        if isinstance(dicti[k], dict):
            dicti[k] = pop_nested_nulls(dicti[k])
        elif not dicti[k]:
            dicti.pop(k)
    return dicti

Output for pop_nested_nulls(dicti)

{'k0_l0': {'k0_l1': {'k0_l2': {'k1_1': 1,
                               'k2_2': 2.2}},
           'k2_l1': 'not '
                    'none'},
 'k1_l0': 'l0'}

Upvotes: -1

singrium
singrium

Reputation: 3016

This worked for me:

d = {1: 'a', 2: '', 3: 'b', 4: '', 5: '', 6: 'c'}
for key, value in list(d.items()):
    if value == '':
        del d[key]
print(d)
# {1: 'a', 3: 'b', 6: 'c'}

Casting the dictionary items to list creates a list of its items, so you can iterate over it and avoid the RuntimeError.

Upvotes: 46

Alon Elharar
Alon Elharar

Reputation: 1429

You only need to use copy:

This way you iterate over the original dictionary fields and on the fly can change the desired dict d. It works on each Python version, so it's more clear.

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

In [2]: for i in d.copy():
   ...:     if not d[i]:
   ...:         d.pop(i)
   ...:         

In [3]: d
Out[3]: {'a': [1], 'b': [1, 2]}

(BTW - Generally to iterate over copy of your data structure, instead of using .copy for dictionaries or slicing [:] for lists, you can use import copy -> copy.copy (for shallow copy which is equivalent to copy that is supported by dictionaries or slicing [:] that is supported by lists) or copy.deepcopy on your data structure.)

Upvotes: 142

ucyo
ucyo

Reputation: 665

For Python 3:

{k:v for k,v in d.items() if v}

Upvotes: 13

Jon Clements
Jon Clements

Reputation: 142156

I would try to avoid inserting empty lists in the first place, but, would generally use:

d = {k: v for k,v in d.iteritems() if v} # re-bind to non-empty

If prior to 2.7:

d = dict( (k, v) for k,v in d.iteritems() if v )

or just:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]

Upvotes: 15

Related Questions