[Python-ideas] Syntax for key-value iteration over mappings

Chris Angelico rosuav at gmail.com
Mon Jul 27 21:44:13 CEST 2015


On Tue, Jul 28, 2015 at 5:17 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Tue, Jul 28, 2015 at 02:25:00AM +1000, Chris Angelico wrote:
>
>> What my suggestion was positing was not so much DWIM as "iterate over
>> the keys and values of anything". A mapping type has a concept of keys
>> and values; an indexable sequence (list, tuple, etc) uses sequential
>> numbers as indices and its members as values.
> .............^^^^^^^
>
>
> Keys and indices are not the same thing, and Python is not Lua.
> ...
> Despite the apparent analogy of key <=> index, it's remarkable hard to
> think of any practical use for such a thing. I cannot think of any time
> I have wanted to, or might want to in the future, ducktype lists as
> dicts, with the indices treated as keys.

A namedtuple is completely different from a list, too. But you can
iterate over both. A generator is utterly different again, and you can
iterate over that the exact same way. Are you ducktyping namedtuples
as lists, with their attributes in definition order? Or are all of the
above simply special cases of "thing you can iterate over to get a
series of values"?

Many types have a concept of keys/indices and their associated values.
Yes, you're right, removing an element from a list changes the indices
of all those after it; but the same goes for any sort of mutation of
an iterable. With a dict, adding a new key/value pair can change the
order of all the others. Does the fact that a list would never dare do
such a thing mean that you shouldn't iterate over dicts and lists
using the same syntax? Clearly not, because we can already do
precisely that. You already have to be careful of mutating the thing
you're iterating over:

>>> l=[1,2,3]
>>> for x in l:
...  l.remove(x)
...  print(x)
...
1
3
>>> l
[2]

> Apart from practical problems such as the above, there's also a
> conceptual problem. Keys of a mapping are *intrinsic* properties of the
> mapping. But indices of a sequence are *extrinsic* properties. They
> aren't actually part of the sequence. Given the list [2,4,6] the "key"
> (actually index) 0 is not part of the list in any way.

Not sure the significance of this; whatever the indices are, they do
exist. There is a canonical index for the value 2, and it can be
determined by the aptly-named index() method:

>>> [2,4,6].index(2)
0

If you were to iterate over that list in some way which pairs indices
and values, it would give index 0 with value 2, index 1 with value 4,
index 2 with value 6, and StopIteration. This is the behaviour of
enumerate(), and nobody has ever complained that this is a bad way to
work with list indices.

> Conveniently the way we access keys and indices reflects this. Keys,
> being intrinsic to the mapping, is a method:
>
>     mapping.keys()
>
> while indices, being extrinsic, is a function which can be applied to
> any iterable, with any starting value:
>
>     enumerate(sequence, 1)
>     enumerate(mapping, -5)

Not sure the point of this distinction, especially given that the
starting value has to be 0 if the indexing into the original sequence
is to work.

> [... snip proposal to treat sets {element} as {element:True} ...]
>
>
>> This would create a new iteration invariant. We currently have:
>
> Why do we need this invariant? What does it gain us to be able to say
>
>     myset[element]
>
> and get True back, regardless of the value of element? Why not just say:
>
>     True
>
> We can invent any invariants we like, but if they're not useful, why add
> cruft to the language to support something that we aren't going to use?

The invariant is nothing to do with treating {element} as
{element:True}, that was just an example of how different types could
viably respond to this kind of protocol. The invariant comes from the
definition of index-value iteration, which is that iterable[index] is
value.

ChrisA


More information about the Python-ideas mailing list