425

I have checked all of the other questions with the same error yet found no helpful solution =/

I have a dictionary of lists:

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

in which some of the values are empty. At the end of creating these lists, I want to remove these empty lists before returning my dictionary. Current I am attempting to do this as follows:

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

however, this is giving me the runtime error. I am aware that you cannot add/remove elements in a dictionary while iterating through it...what would be a way around this then?

gsamaras
  • 69,751
  • 39
  • 173
  • 279
user1530318
  • 22,417
  • 13
  • 34
  • 47

12 Answers12

677

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 note that in Python 3.x this second method doesn't help with your error because keys returns an a view object instead of copynig the keys into a list.

Boris Verkhovskiy
  • 10,733
  • 7
  • 77
  • 79
Mark Byers
  • 767,688
  • 176
  • 1,542
  • 1,434
  • 1
    I believe you meant 'calling `keys` makes a copy of the ***keys*** that you can iterate over' aka the `plural` keys right? Otherwise how can one iterate over a single key? I'm not nit picking by the way, am genuinely interested to know if that is indeed key or keys – HighOnMeat Jun 17 '16 at 10:34
  • 8
    Or tuple instead of list as it is faster. – Brambor Sep 07 '18 at 02:54
  • 20
    To clarify the behavior of python 3.x, d.keys() returns an _iterable_ (not an iterator) which means that it's a view on the dictionary's keys directly. Using `for i in d.keys()` does actually work in python 3.x _in general_, but because it is iterating over an iterable view of the dictionary's keys, calling `d.pop()` during the loop leads to the same error as you found. `for i in list(d)` emulates the slightly inefficient python 2 behavior of copying the keys to a list before iterating, for special circumstances like yours. – Michael Krebs Nov 01 '18 at 14:08
  • your python3 solution is not work when you want to delete an object in inner dict. for example you have a dic A and dict B in dict A. if you want to delete object in dict B it occurs error – Ali-T Apr 18 '20 at 06:35
  • for python3 you can try `for i in list(d.keys()):` – tsveti_iko May 07 '20 at 13:27
  • 5
    In python3.x, `list(d.keys())` creates the same output as `list(d)`, calling `list` on a `dict` returns the keys. The `keys` call (though not that expensive) is unnecessary. – Sean Breckenridge May 08 '20 at 06:27
  • 5
    Surprisingly, `items()` [doc](https://docs.python.org/2/library/stdtypes.html#dict.items) claims to "return a copy of the dictionary’s list of (key, value) pairs.", yet looping through `items()` while poping items still gives **RuntimeError: dictionary changed size during iteration**. – Daniel Chin Sep 22 '21 at 19:47
102

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.)

mkrieger1
  • 14,486
  • 4
  • 43
  • 54
Alon Elharar
  • 1,029
  • 1
  • 7
  • 3
62

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]}
mkrieger1
  • 14,486
  • 4
  • 43
  • 54
Maria Zverina
  • 10,313
  • 3
  • 43
  • 61
  • 12
    `d.iteritems()` gave me an error. I used `d.items()` instead - using python3 – wcyn Dec 12 '16 at 09:54
  • 7
    This works for the problem posed in OP's question. However anyone who came here after hitting this RuntimeError in multi-threaded code, be aware that CPython's GIL can get released in the middle of the list comprehension as well and you have to fix it differently. – Yirkha Jul 21 '17 at 09:03
26

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.

mkrieger1
  • 14,486
  • 4
  • 43
  • 54
singrium
  • 2,304
  • 5
  • 24
  • 40
13

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]
Jon Clements
  • 132,101
  • 31
  • 237
  • 267
  • +1: last option is interesting because it only copies the keys of those items that need deleting. This may give better performance if only a small number of items need deleting relative to the size of the dict. – Mark Byers Aug 13 '12 at 20:45
  • @MarkByers yup - and if a large number do, then re-binding the dict to a new one that's filtered is a better option. It's always the expectation of how the structure should work – Jon Clements Aug 13 '12 at 20:50
  • 4
    One danger with rebinding is if somewhere in the program there was an object that held a reference to the old dict it wouldn't see the changes. If you're certain that's not the case, then sure... that's a reasonable approach, but it's important to understand that it's not quite the same as modifying the original dict. – Mark Byers Aug 13 '12 at 20:53
  • @MarkByers extremely good point - You and I know that (and countless others), but it's not obvious to all. And I'll put money on the table it hasn't also bitten you in the rear :) – Jon Clements Aug 13 '12 at 21:06
  • The point of avoiding to insert the empty entries is a very good one. – Magnus Bodin Dec 27 '19 at 20:13
13

For Python 3:

{k:v for k,v in d.items() if v}
Ahmad
  • 227
  • 3
  • 15
ucyo
  • 557
  • 6
  • 16
10

to avoid "dictionary changed size during iteration error".

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

just use 'list' with '.items()' , and 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 example and change it based on your case/requirements, i hope this helpful.

K.A
  • 755
  • 8
  • 17
9

You cannot iterate through a dictionary while its changing during 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)
3

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

for i in x.keys():

With

for i in list(x)
Hasham Beyg
  • 175
  • 11
1

The reason for the runtime error is that you cannot iterate through a data structure while its structure is changing during iteration.

One way to achieve what you are looking for is to use list to append the keys you want to remove and then use pop function on dictionary to remove the identified key while iterating through the list.

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)
Rohit
  • 17
  • 3
1
dictc={"stName":"asas"}
keys=dictc.keys()
for key in list(keys):
    dictc[key.upper()] ='New value'
print(str(dictc))
Mahmoud K.
  • 7,172
  • 1
  • 30
  • 43
vaibhav.patil
  • 193
  • 5
  • 17
0

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 index to access the field in the original dict.