On Tue, Dec 29, 2020 at 10:30 AM Guido van Rossum <guido@python.org> wrote:
Interesting -- thanks for taking up the challenge. I still suspect that if we ran the corresponding benchmark at the C level, the first form would win,

I was thinking that might be the case -- but either way, there is little difference, and at least for ** unpacking, it's probably mostly used for small dicts anyway.
 
Does there need to be a single defined "protocol" for a mapping (other than the ABC)? -- that is, would **unpacking be able to use .items() and keys() be used in other contexts? 

And why does ** unpacking need to check at all (LBYL) couldn't it simply do something like:

{k: d[k] for k in d}

I don't understand why LBYL is considered such an anti-pattern. It helps produce much clearer error messages in this case for users who are exploring this feature, and distinguishing *early* between sequences and mappings is important for that.

Fair enough, though in this case, it's producing a not quite clear error message, whereas simply trying to call keys() would reflect the actual error. I was thinking about this a bit more, and realized that this pattern is used (at least) in the dict constructor and dict.update() -- but in both of those cases, they can take either a mapping or an iterable of (key, value) pairs. So it is required to make a distinction, and looking for keys() is as good a way as any (maybe the best, certainly well established)

(and if you pass a object with a keys() method, but no __getitem__ into dict(), you get: "TypeError: 'MinMap' object is not subscriptable" -- not anything it about it needing to be a Mapping)

But for **, which only supports Mappings, maybe there is no need to check for keys() -- it is clearly defined that iter(a_mapping) iterates over the keys, so that part should work, and if the __getitem__ doesn't work appropriately, then that's not really different than passing a iterable that doesn't produce valid (key, value) pairs to dict().

But this is all theoretical -- it's established, and a bit better docs should clear up the confusion.

One more note on the docstrings:

dict.update() says:

"... If E is present and has a .keys() method..."

Which nicely defines what is actually required.

Whereas dict() says:

"...dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs..."

Without saying anything about how it determines what whether it's a mapping. So maybe that could be made a bit more clear as well.
 
I also just noticed something else in the docs -- in typing, there is a Protocol type -- maybe we could/should pre-define a mapping protocol type?

Or maybe a MinimialMapping ABC, analogous to the Iterable ABC -- though no idea what to call it that would be clear :-)

-CHB


--
Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython