Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify list and dictionary during iteration, why does it fail on dict?

Let's consider this code which iterates over a list while removing an item each iteration:

x = list(range(5))  for i in x:     print(i)     x.pop() 

It will print 0, 1, 2. Only the first three elements are printed since the last two elements in the list were removed by the first two iterations.

But if you try something similar on a dict:

y = {i: i for i in range(5)}  for i in y:     print(i)     y.pop(i) 

It will print 0, then raise RuntimeError: dictionary changed size during iteration, because we are removing a key from the dictionary while iterating over it.

Of course, modifying a list during iteration is bad. But why is a RuntimeError not raised as in the case of dictionary? Is there any good reason for this behaviour?

like image 676
ducminh Avatar asked Apr 04 '18 08:04

ducminh


People also ask

Can you modify a dictionary while iterating?

To modify a Python dict while iterating over it, we can use the items method to get the key and value. to loop through the key value pairs in t with t. items() and the for loop. In it, we set t2[k] to the prefix + v where v is the value in the t dict.

Can we modify list while iterating Python?

The general rule of thumb is that you don't modify a collection/array/list while iterating over it. Use a secondary list to store the items you want to act upon and execute that logic in a loop after your initial loop.

Can you modify dict in Python?

Modifying a value in a dictionary is pretty similar to modifying an element in a list. You give the name of the dictionary and then the key in square brackets, and set that equal to the new value.

Why dict is faster than list?

The reason is because a dictionary is a lookup, while a list is an iteration. Dictionary uses a hash lookup, while your list requires walking through the list until it finds the result from beginning to the result each time.


2 Answers

I think the reason is simple. lists are ordered, dicts (prior to Python 3.6/3.7) and sets are not. So modifying a lists as you iterate may be not advised as best practise, but it leads to consistent, reproducible, and guaranteed behaviour.

You could use this, for example let's say you wanted to split a list with an even number of elements in half and reverse the 2nd half:

>>> lst = [0,1,2,3] >>> lst2 = [lst.pop() for _ in lst] >>> lst, lst2 ([0, 1], [3, 2]) 

Of course, there are much better and more intuitive ways to perform this operation, but the point is it works.

By contrast, the behaviour for dicts and sets is totally implementation specific since the iteration order may change depending on the hashing.

You get a RunTimeError with collections.OrderedDict, presumably for consistency with the dict behaviour. I don't think any change in the dict behaviour is likely after Python 3.6 (where dicts are guaranteed to maintain insertion ordered) since it would break backward compatibility for no real use cases.

Note that collections.deque also raises a RuntimeError in this case, despite being ordered.

like image 116
Chris_Rands Avatar answered Oct 09 '22 09:10

Chris_Rands


It wouldn't have been possible to add such a check to lists without breaking backward compatibility. For dicts, there was no such issue.

In the old, pre-iterators design, for loops worked by calling the sequence element retrieval hook with increasing integer indices until it raised IndexError. (I would say __getitem__, but this was back before type/class unification, so C types didn't have __getitem__.) len isn't even involved in this design, and there is nowhere to check for modification.

When iterators were introduced, the dict iterator had the size change check from the very first commit that introduced iterators to the language. Dicts weren't iterable at all before that, so there was no backward compatibility to break. Lists still went through the old iteration protocol, though.

When list.__iter__ was introduced, it was purely a speed optimization, not intended to be a behavioral change, and adding a modification check would have broken backward compatibility with existing code that relied on the old behavior.

like image 22
user2357112 supports Monica Avatar answered Oct 09 '22 09:10

user2357112 supports Monica