Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Python enforce change in size during iteration for dict, but not for list? [duplicate]

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 744
ducminh Avatar asked Apr 04 '18 08:04

ducminh


People also ask

Why does the dictionary change size during iteration?

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

Can you iterate over a dictionary Python?

You can iterate through a Python dictionary using the keys(), items(), and values() methods. keys() returns an iterable list of dictionary keys. items() returns the key-value pairs in a dictionary. values() returns the dictionary values.

How do you check if a key exists in a dictionary Python?

Check If Key Exists Using has_key() The has_key() method is a built-in method in Python that returns true if the dict contains the given key, and returns false if it isn't.


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 102
Chris_Rands Avatar answered Oct 11 '22 13: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 28
user2357112 supports Monica Avatar answered Oct 11 '22 14:10

user2357112 supports Monica