Reputation: 27697
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
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
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
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
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
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)
{'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
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
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
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
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
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
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
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
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