python 3 dict: .keys(), .values(), and .item()

Steve D'Aprano steve+python at pearwood.info
Sat Jan 7 19:48:56 EST 2017


On Sun, 8 Jan 2017 08:48 am, Ethan Furman wrote:

> In Python 2 we have:
> 
>    dict().keys()   \
>    dict().items()   -->  separate list() of the results
>    dict().values() /
> 
> and
> 
>    dict().iter_keys()   \
>    dict().iter_items()   -->  integrated iter() of the results
>    dict().iter_values() /

You missed another group of methods:

viewkeys()
viewitems()
viewvalues()

which are integrated, iterable, set-like views of the keys/items/values.


> By "separate list" I mean a snapshot of the dict at the time, and by
> "integrated iter()" I mean  changes to the dict during iteration are
> seen by the iter.
> 
> In Python 3 the iter_* methods replaced the list() type methods, 

Its actually the view* methods.


> which 
> makes sense from the point-of-view of moving to a more iterator based
> language; however, as a result of that change the typical "iterate over
> a dict" operation now has a built-in gotcha: modifying the dict during
> the iteration can now cause exceptions.

Even in Python 2, modifying the dict during iteration can cause exceptions:

for key in adict:  # iterate over the dict directly
    ...


As usual, the standard rule applies: don't add or remove elements of a
collection while you are iterating over it.

This doesn't work either:

for i, x in enumerate(alist):
    if condition(x):
        del alist(i)


and has a similar solution:

for i, x in enumerate(list(alist)):
    ...

except that's more commonly written using slicing:

for i, x in enumerate(alist[:]):
    ...


For both dicts and lists, it is safe to modify existing elements:

    adict[key] = new_value  # provided key already exists

    alist[index] = new_value


are both safe, but insertions and deletions are not.


> The solution, of course, is simple: surround the iterator call with
> list():

There's nothing magical about list. You could use tuple, or (sometimes) set
or frozenset. But list is good.


>    list(dict.keys())
>    list(dict.items())
>    list(dict.values())
> 
>    for k, v in list(flag._value2member_map_.items()):
>        ...
> 
> The solution, however, feels a lot more boilerplate-ish.  Either the
> programmer takes a lot more care to remember the current state of the dict
> (since it's no longer a snapshot), or "list()" is sprinkled around every
> iterator access.

Not *every* view access. Only the ones where you insert/delete elements.


> In other words, what used to be a completely safe operation now is not.
> 
> Thoughts?

The old Python 2 keys/values/items methods used to make a copy of the
elements, whether you needed a copy or not. Now making a copy is your
responsibility. That feels more Pythonic to me.



-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.



More information about the Python-list mailing list