Why the ** syntax uses keys() instead of __iter__

This is just curiosity on my part. By creating dummy classes with just one method on them printing things, I discovered that writing foo(**a) calls a.keys(), then getitem on a using the resulting keys.
Why was it chosen to use a normal-named method, keys, rather than a dunder like __iter__ ?

Before even raising the question of whether it should be changed or not and what precautions and costs would be involved, I find it very surprising that a native syntax behavior depends on a method not part of the datamodel - which adresses mappings, but only “recommends” that they implement keys, not presenting it as required.
Another point, for uses of the mixin class collections.abc.MutableMapping, the __iter__ method is user-provided while the keys method is mixin and actually a wrapper around __iter__, so calling keys instead of __iter__ costs more execution time.

It can’t use __iter__, because sequences also have that so things like range would unpack successfully. Maybe it’d make sense to make a __keys__ method, but then you’d need __items__ and __values__, presumably a keys()/items()/values() builtin (which would be shadowed an awful lot), all for no real gain.

Well keys is not a reserved method name since it’s not a dunder, so the exact same kind of bug could happen to a type having a keys method which would return an iterable thing. Yes, it’s probably less frequent, but it’s not forbidden by the documentation whereas dunders are reserved.

I guess I see a point : for a range, the second part, the getitem part, would succeed since in that case the iterated values are valid subscripts (for 1-argument ranges that is).
But the foo(**range(15)) call would still fail since the “keys” are numbers and not strings. So I don’t think it would have enabled any unintended behavior.

I’m wondering whether all implementations work that way or if it’s only cpython.

Checking the language specification, it doesn’t mention the use of keys(), just saying that it will look for mappings in general. It probably should.

An instance of builtin dict is required internally, so it creates a new dict instance and merges in the mapping. Ultimately the merge is implemented by dict_merge() in Objects/dictobject.c. This function is also used by the update() method of a dict when it’s merging in a mapping that has a keys() method.

>>> print(dict.update.__doc__)
D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
In either case, this is followed by: for k in F:  D[k] = F[k]
1 Like

[lightly edited] It is possible that a.__iter__ is used, just not directly. a.keys() is intended to be an iterable live view of a, in which case it must have a reference to a, in which case a.keys().__iter__ should produce the same names as a.iter and could actually invoke a.__iter__. A perhaps better question is why access a.__iter__ via a.keys() instead of directly. The answer, already given, is to avoid iterating sequences.