On Tue, Dec 29, 2020 at 10:30:16AM -0800, Guido van Rossum wrote: [Christopher]
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?
Yes, I think there should be one protocol, or interface if you prefer, for something to be considered dict-like. If some operations expect objects to quack like a dict, then it would be annoying if other related operations expect them to swim like a dict. The status quo right now is: (1) The dict constructor, dict.update, and dict union assignment `|=` all support the double-barrelled interface: - try keys() and `__getitem__` - otherwise fall back onto direct iteration. (2) Dict unpacking `**` only supports the keys/getitem interface. My guess is that's an oversight, not a deliberate difference in behaviour. I think that those four operations should operate in a consistant manner. Otherwise, we would be okay if we can write a object that quacks like a dict via keys/getitem, since that is supported by all four, but suppose you can't and have to use the fallback operation. So you dutifully add the direct iteration API, and now your object works in the dict constructor etc but not dict unpacking. So you have to *also* add the items() method you suggest, an otherwise unnecessary second way of doing the same thing. This is annoying and error-prone: being two methods, there is a risk that they will diverge in behaviour, or that documentation will diverge, and now users of your object need to worry about whether to use direct iteration or items(), and you know that people will keep asking what's the difference between them. It wouldn't be a disaster if we went this way, but it would add unnecessary friction. There is however a third possibility: extend the dict interface by adding a second fallback. I think we're stuck with keeping the existing order as first and second attempts, but we can tack items() at the end: - try keys() and `__getitem__` - try direct iteration - otherwise, try items() I don't hate this, but honestly I think it is YAGNI. [Guido]
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. Long ago we decided that the distinctive feature is that mappings have a `keys()` method whereas sequences don't (and users who add a `keys()` method to a sequence are just asking for trouble). So that's what we use.
I remember learning that EAPF was preferred over LBYL back when I started in Python 1.5 days. I think the main reasons were to encourage people to duck-type, and to avoid Time Of Check To Time Of Use errors, e.g. when opening files. But although EAFP was preferred, there are definitely times when LBYL is better, and I think that calling LBYL "unPythonic" is a hyper-correction. -- Steve