Whilst browsing Ciaran’s blog I was thinking about the following:
Clearly this Python code is fine:
for k,v in adict.items(): if f(v): del adict[k]
Why wouldn’t it be? Well, it deletes things from the dictionary, adict, as it is iterating over the dictionary. Which should always start the alarm bells ringing. (if you haven’t guessed, f is just some test function that tells us whether we want to delete the dictionary item, based on its value. For example, it could be:
def f(x): return len(x) > 1)
This code is fine because it uses
items returns a copy of the dictionary’s items, so it doesn’t matter what you do to the dictionary, the iteration will still proceed as intended.
So what about iteritems?:
for k,v in adict.iteritems(): if f(v): del adict[k]
No idea, documentation horribly silent on this point. Ah, but trying it (well, a simpler case):
>>> for k,v in d.iteritems(): ... del d[k] ... Traceback (most recent call last): File "", line 1, in RuntimeError: dictionary changed size during iteration
We get a RuntimeError raised. Would be nice if the documentation for iteritems mentioned that.
So, interesting fact: You can’t always replace
What about «
for k in adict»? Well, for one thing, Python built in dictionaries are not even documented as supporting the
__iter__ magic method that
for uses in this case. Though of course they do, and lots of people use it all the time. For another thing, the
__iter__ protocol documentation is also silent on how safe it is to mutate the underlying object. If you try it out, you get the same exception as when you use iteritems. Not safe to mutate underlying object.
for k in adict.keys()» (which you also see quite a lot) is okay if you want to delete dictionary entries. keys returns a copy of the key list, so the iteration cannot be harmed.
So, interesting fact: You can’t always replace «
for k in adict.keys()» with «
for k in adict».
I wouldn’t rely on always getting an exception raised for unsafe practices either.