OK, so some more exploration:

Other may already know this, but it seems that the dict constructor
essentially checks if the passed in object is a Mapping ABC:

if it is, it iterates on teh keys and used __getitem__ to get the values.

if it is not, it iterates over key, value pairs.

So if you want your custom class to present the Mapping interface, and be
able to be passed in to dict(), then you shoudl subclass from
collections.Mapping.ABC

If you do not want your class to present the Mapping interface, then your
__Iter__ should return an iterator that returns key.value pairs.

Here is some code that explores that with a custom Mapping class.

https://github.com/PythonCHB/PythonTopics/blob/master/Sources/code/dict_making/dict_making.py

(also below)

The trick here is that with, e.g. the dataclasses decorator, I'm not sure
how to make it derive from the Mapping ABC -- I'm sure it's doable, but I
haven't dug into that yet.

Personally, I'm a big fan of duck typing -- so rather than check for the
Mapping ABC, I think it should check for .items() first, and use that if
it's there, and then use the regular iterator otherwise. Then all you'd
have to do to make your class "dictable" would be to provide an items()
iterator.

But again -- there are two ways to make your classes dictable -- why do we
need a third?

-CHB

Sample code:

"""
an exploration of how the dict constructor knows whether it is
working with a MApping of a general iterable.  It looks like
the code is something like this:

if isinstance(input, collections.abc.Mapping):
    self.update = {key: input[key] key in input}
else:
    self.update = {key: val for key, val in input}

So if your custom class present themapping interface -- that is, iterating
over it returns the keys, than you want to be a MApping ABC subclass.ABC

But if your custom class is not a Mapping, then you want __iter__ to return
an
iterator over teh key, value pairs.
"""

from collections.abc import Mapping


def test_iter(instance):
    yield ("this")
    yield ("that")


class DictTest(Mapping):

    def __init__(self, this="this", that="that"):
        self.this = this
        self.that = that

    def __iter__(self):
        print("iter called")
        return test_iter(self)

    def __getitem__(self, key):
        return getattr(self, key)

    def __len__(self):
        print("__len__ called")


if __name__ == "__main__":

    dt = DictTest()
    print(dict(dt))

    dt = DictTest(this=45, that=654)
    print(dict(dt))



On Sat, May 11, 2019 at 1:51 PM Christopher Barker <python...@gmail.com>
wrote:

> On Sat, May 11, 2019 at 1:38 PM Brendan Barnwell <brenb...@brenbarn.net>
> wrote:
>
>> > protocol for something different -- I can't image what that would be.
>> > Sure, on the general case, maybe, but for a class that has a "natural"
>> > use case for dictifying, how else would you want to iterate over it??
>>
>>         One example would be. . . dicts.  Calling dict on a dict gives
>> you a
>> dict, but iterating over the dict just gives you the keys.  Certain
>> other mapping types (such as Pandas Series and DataFrames) also have
>> this behavior where iterating over the mapping gives you only the keys.
>>
>
> well, there is that ... :-) And that IS the mapping protocol. I guess i
> was thinking that you had an object that was NOT a Mapping already.
>
> If it IS a Mapping, then presumably it already can be passed into the dict
> constructor, yes?
>
> So if you want your class "dictable", then you can implement either the
> Mapping ABC, or the iteration protocol. I"m having an even harder time
> imagining why you would want a Mapping interface that isn't the same as
> what you'd want turned into a dict.
>
> So why again do we yet another way to do it?
>
> Anyway, I was playing with this, and write a prototype decorator that
> makes a datacalss "dictable" -- that it, it can be passed in to the dict
> constructor to make a dict. This is done by providing an iterator that
> returns (key, value) pairs.
>
> The code is in this git rep, if anyone wants to comment on it or improve,
> it, or...
>
>
> https://github.com/PythonCHB/PythonTopics/blob/master/Sources/code/dict_making/dictable_dataclass.py
> (also here for your reading pleasure)
>
> I'm not sure how the dict constructor decides an input is a mapping or an
> iterable -- maybe I'll play with that now.
>
> -CHB
>
> #!/usr/bin/env python
>
> """
> A prototype of a decorator that adds an iterator to a dataclass
> so it can be passed in to the dict() constructor to make a dict.
>
> If this is thought to be useful it could be added to the dataclass
> decorator itself, to give all decorators this functionality.
> """
>
>
> from dataclasses import dataclass
>
>
> class DataClassIterator:
>     """
>     Iterator for dataclasses
>
>     This used the class' __dataclass_fields__ dict to iterate through the
>     fields and their values
>     """
>
>     def __init__(self, dataclass_instance):
>         self.dataclass_instance = dataclass_instance
>         self.keys_iter =
> iter(dataclass_instance.__dataclass_fields__.keys())
>
>     def __iter__(self):
>         return self
>
>     def __next__(self):
>         key = next(self.keys_iter)
>         return (key, getattr(self.dataclass_instance, key))
>
>
> def _dunder_iter(self):
>     """
>     function used as the __iter__ method in the dictable_dataclass
> decorator
>     """
>     return DataClassIterator(self)
>
>
> def dictable_dataclass(the_dataclass):
>     """
>     class decorator for making a dataclass iterable in a way that is
> compatible
>     with the dict() constructor
>     """
>     the_dataclass.__iter__ = _dunder_iter
>
>     return the_dataclass
>
>
> # Example from the dataclass docs:
> @dictable_dataclass
> @dataclass
> class InventoryItem:
>     '''Class for keeping track of an item in inventory.'''
>     name: str
>     unit_price: float
>     quantity_on_hand: int = 0
>
>     def total_cost(self) -> float:
>         return self.unit_price * self.quantity_on_hand
>
>
> if __name__ == "__main__":
>     # try it out:
>     inv_item = InventoryItem("sneakers", 50.0, 20)
>
>     print("an InventoryItem:\n", inv_item)
>     print()
>
>     print("And the dict you can make from it:")
>     print(dict(inv_item))
>
>
>
>
>
>
>
>
>
>
>
>> --
>> Brendan Barnwell
>> "Do not follow where the path may lead.  Go, instead, where there is no
>> path, and leave a trail."
>>     --author unknown
>> _______________________________________________
>> Python-ideas mailing list
>> Python-ideas@python.org
>> https://mail.python.org/mailman/listinfo/python-ideas
>> 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
>


-- 
Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to