On Mon, Dec 28, 2020 at 3:54 AM Steven D'Aprano <steve@pearwood.info> wrote:
> Steven D'Aprano wrote:
> > Why do you want something that isn't a mapping to be usable with mapping
> > unpacking?
> I think mapping is not `abc.Mapping` class only.

You don't have to inherit from Mapping for this to work. Double-star
unpacking already supports duck-typing:

>>> class MyMapping:
...     def keys(self):
...             return iter('abc')
...     def __getitem__(self, key):
...             if key in ('a', 'b', 'c'):
...                     return key.upper()
...             raise KeyError
>>> def demo(**kwargs):
...     print(kwargs)

Thanks! this would have shortcutted this conversation if it come earlier. The OP, and me, took the error message at its word, and also, in experimenting, didn't happen on the right part of the Mapping API that needed to be supported.

I don't know about the OP, but all I wanted was a clear definition of the part of the API needed to support **, and apparently it's a keys() method that returns an iterator of the keys, and a __getitem__ that then returns the values associated with those keys. Which is fine.

Though frankly, I would rather have had it use .items() -- seems more efficient to me, and you do need both the keys and the values, and items() is just as much part of the Mapping API as keys.

But there is an argument that the ** operator should be able to be supported only with dunder methods -- which could be done if it used the iterator protocol to get the keys, rather than the keys() method, which does not appear to work now. though to be fair, all you need to do to get that is add a __len__ and derive from Mapping.

and to the OP's question a decorator that makes a Mapping from a dataclass would be pretty easy to write.

>>> import collections.abc
>>> issubclass(MyMapping, collections.abc.Mapping)
>>> demo(**MyMapping())
{'a': 'A', 'b': 'B', 'c': 'C'}

So we already support duck-typing here.

We can't use the fallback iteration interface:

>>> class MyOtherMapping:
...     def __iter__(self):
...             return zip('xyz', 'XYZ')
>>> demo(**MyOtherMapping())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __main__.demo() argument after ** must be a mapping, not MyOtherMapping

so that's a possible enhancement: dict unpacking should(?) fall back on
iteration of (key, value) pairs just as dict.update does. I would
cautiously support adding that as an enhancement.

> What about:
> `Iterator[Tuple[str, int]]`

I said I would *cautiously* support that, because there are some subtle
issues to do with exceptions, but we can worry about that later.

> ```
> @dataclass
> class MyMap:
>     x: int
>     y: int
> ```
> Is this "mapping"?


>>> issubclass(MyMap, collections.abc.Mapping)

It doesn't even duck-type as a mapping. It does not support len, keys or

>>> obj = MyMap(2, 3)
>>> len(obj)
TypeError: object of type 'MyMap' has no len()
>>> obj.keys()
AttributeError: 'MyMap' object has no attribute 'keys'
>>> obj['x']
TypeError: 'MyMap' object is not subscriptable

so it is certainly not a mapping.

Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/UYDIPMY2HXGL4OLEEFXBTZ2T4CK6TSVU/
Code of Conduct: http://python.org/psf/codeofconduct/

Christopher Barker, PhD

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