Python 3: dict & dict.keys()


E

Ethan Furman

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.

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


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'


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?
 
Ad

Advertisements

S

Steven D'Aprano

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.
 
E

Ethan Furman

Surely not. The fundamental behaviour of Python's data model hasn't
changed.

Poetic effect. Dramatic license. Blah blah. ;)

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

Actually, I would recommend `list(d)`, which also works the same in both 2 and 3.
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top