[Python-ideas] __iter__(), keys(), and the mapping protocol
Jonathan Fine
jfine2358 at gmail.com
Thu Sep 13 04:07:33 EDT 2018
Someone wrote:
Granted, my only strong argument is that the ** unpacking operator
depends on this method to do its job, and it's currently alone amongst
Python's operators in depending on a non-dunder to do so
I like this argument. And I think it's important. Here's some background facts
class dict(object)
| dict() -> new empty dictionary
| dict(mapping) -> new dictionary initialized from a mapping object's
| (key, value) pairs
| dict(iterable) -> new dictionary initialized as if via:
| d = {}
| for k, v in iterable:
| d[k] = v
| dict(**kwargs) -> new dictionary initialized with the name=value pairs
| in the keyword argument list. For example: dict(one=1, two=2)
>>> list(zip('abc', range(3)))
[('a', 0), ('b', 1), ('c', 2)]
>>> dict(list(zip('abc', range(3))))
{'b': 1, 'a': 0, 'c': 2}
>>> dict(zip('abc', range(3)))
{'b': 1, 'a': 0, 'c': 2}
>>> dict(**zip('abc', range(3)))
TypeError: type object argument after ** must be a mapping, not zip
>>> dict(**list(zip('abc', range(3))))
TypeError: type object argument after ** must be a mapping, not list
Now for my opinions. (Yours might be different.)
First, it is my opinion that it is not reasonable to insist that the
argument after ** must be a mapping. All that is required to construct
a dictionary is a sequence of (key, value) pairs. The dict(iterable)
construction proves that point.
Second, relaxing the ** condition is useful. Consider the following.
>>> class NS: pass
>>> ns = NS()
>>> ns.a = 3
>>> ns.b = 5
>>> ns.__dict__
{'b': 5, 'a': 3}
>>> def fn(**kwargs): return kwargs
>>> fn(**ns)
TypeError: fn() argument after ** must be a mapping, not NS
>>> fn(**ns.__dict__)
{'b': 5, 'a': 3}
The Zen of Python says
Namespaces are one honking great idea -- let's do more of those!
I see many advantages in using a namespace to build up the keyword
arguments for a function call. For example, it could do data
validation (of both keys/names and values). And once we have the
namespace, used for this purpose, I find it natural to call it like so
>>> fn(**ns)
I don't see any way to do this, other than defining NS.keys and
NS.__getitem__. But why should Python itself force me to expose
ns.__dict__ in that way. I don't want my users getting a value via
ns[key].
By the way, in JavaScript the constructs obj.aaa and obj['aaa'] are
always equivalent.
POSTSCRIPT:
Here are some additional relevant facts.
>>> fn(**dict(ns))
TypeError: 'NS' object is not iterable
>>> def tmp(self): return iter(self.__dict__.items())
>>> NS.__iter__ = tmp
>>> fn(**dict(ns))
{'b': 5, 'a': 3}
>>> list(ns)
[('b', 5), ('a', 3)]
I find allowing f(**dict(ns)) but forbidding f(**ns) to be a
restriction of functionality removes, rather than adds, values.
Perhaps (I've not thought it through), *args and **kwargs should be
treated as special contexts. Just as bool(obj) calls obj.__bool__ if
available.
https://docs.python.org/3.3/reference/datamodel.html#object.__bool__
In other words, have *args call __star__ if available, and **kwargs
call __starstar__ if available. But I've not thought this through.
--
Jonathan
More information about the Python-ideas
mailing list