Python 3: dict & dict.keys()

Steven D'Aprano steve+comp.lang.python at pearwood.info
Tue Jul 23 22:11:47 EDT 2013


On Tue, 23 Jul 2013 18:16:08 -0700, Ethan Furman wrote:

> Back in Python 2.x days I had a good grip on dict and dict.keys(), and
> when to use one or the other.
> 
> Then Python 3 came on the scene with these things called 'views', and
> while range couldn't be bothered, dict jumped up and down shouting, "I
> want some!"
>
> So now, in Python 3, .keys(), .values(), even .items() all return these
> 'view' thingies.
> 
> And everything I thought I knew about when to use one or the other went
> out the window.

Surely not. The fundamental behaviour of Python's data model hasn't 
changed. Lists are lists, views are views, and iterators are iterators. 
Only the way you get each has changed.

- If in Python 2, you used the viewkeys() method, that's been renamed 
  keys() in Python 3. So d.viewkeys() => d.keys().

- If in Python 2, you used the keys() method, it returns a list, and
  like any function that has been made lazy instead of eager in Python 3
  (e.g. map, zip, filter) if you want the same behaviour, simply call
  list manually. So d.keys() => list(d.keys()).

- If in Python 2, you used the iterkeys() methods, it returns a simple
  iterator, not a view. So d.iterkeys() => iter(d.keys()).

None of these distinctions really matter if all you are doing is 
iterating over the keys, without modifying the dict. Not in Python 2, nor 
in Python 3.

And naturally the same applies to the various flavours of *items and 
*values.


> For example, if you need to modify a dict while iterating over it, use
> .keys(), right?  Wrong:
> 
> --> d = {1: 'one', 2:'two', 3:'three'} --> for k in d.keys():
> ...   if k == 1:
> ...     del d[k]
> ...
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
> RuntimeError: dictionary changed size during iteration


Fundamentally, this behaviour has not changed from Python 2: you should 
not iterate over a data structure while changing it, instead you should 
make a copy of the data you iterate over. In Python 2, d.keys() makes a 
copy and returns a list, so in Python 3 you would call list(d.keys()).


> If you need to manipulate the keys (maybe adding some, maybe deleting
> some) before doing something else with final key collection, use
> .keys(), right?  Wrong:
> 
> --> dk = d.keys()
> --> dk.remove(2)
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
> AttributeError: 'dict_keys' object has no attribute 'remove'


Repeat after me: "In Python 2, d.keys() returns a list of keys, so if I 
want a list of keys in Python 3, call list explicitly list(d.keys())."


> I understand that the appropriate incantation in Python 3 is:
> 
> --> for k in list(d)
> ...    ...
> 
> or
> 
> --> dk = list(d)
> --> dk.remove(2)
> 
> which also has the added benefit of working the same way in Python 2.
> 
> So, my question boils down to:  in Python 3 how is dict.keys() different
> from dict?  What are the use cases?

*shrug* For most purposes, there is no difference, especially when merely 
iterating over the dict. Such differences as exist are trivial:

- if you need an actual callable function or method, say to pass to some
  other function, you can do this:

for method in (d.items, d.keys, d.values):
    process(method)


instead of this:

# untested
for method in (d.items, d.keys, lambda d=d: iter(d)):
    process(method)


- d.keys() is a view, not the dict itself. That's a pretty fundamental
  difference: compare dir(d.keys()) with dir(d).


Basically, views are set-like, not list-like.



-- 
Steven



More information about the Python-list mailing list