Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python idiom for dict-able classes?

Tags:

python

abc

I want to do something like this:

class Dictable:
    def dict(self):
        raise NotImplementedError

class Foo(Dictable):
    def dict(self):
        return {'bar1': self.bar1, 'bar2': self.bar2}

Is there a more pythonic way to do this? For example, is it possible to overload the built-in conversion dict(...)? Note that I don't necessarily want to return all the member variables of Foo, I'd rather have each class decide what to return.

Thanks.

like image 280
Andrew Lee Avatar asked Sep 03 '25 14:09

Andrew Lee


2 Answers

The Pythonic way depends on what you want to do. If your objects shouldn't be regarded as mappings in their own right, then a dict method is perfectly fine, but you shouldn't "overload" dict to handle dictables. Whether or not you need the base class depends on whether you want to do isinstance(x, Dictable); note that hasattr(x, "dict") would serve pretty much the same purpose.

If the classes are conceptually mappings of keys to values, then implementing the Mapping protocol seems appropriate. I.e., you'd implement

  • __getitem__
  • __iter__
  • __len__

and inherit from collections.Mapping to get the other methods. Then you get dict(Foo()) for free. Example:

class Foo(Mapping):
    def __getitem__(self, key):
        if key not in ("bar1", "bar2"):
            raise KeyError("{} not found".format(repr(key))
        return getattr(self, key)

    def __iter__(self):
        yield "bar1"
        yield "bar2"

    def __len__(self):
        return 2
like image 179
Fred Foo Avatar answered Sep 05 '25 03:09

Fred Foo


Firstly, look at collections.ABC, which describes the Python abstract base class protocol (equivalent to interfaces in static languages).

Then, decide if you want to write your own ABC or make use of an existing one; in this case, Mapping might be what you want.

Note that although the dict constructor (i.e. dict(my_object)) is not overrideable, if it encounters an iterable object that yields a sequence of key-value pairs, it will construct a dict from that; i.e. (Python 2; for Python 3 replace items with iteritems):

def __iter__(self):
    return {'bar1': self.bar1, 'bar2': self.bar2}.iteritems()

However, if your classes are intended to behave like a dict you shouldn't do this as it's different from the expected behaviour of a Mapping instance, which is to iterate over keys, not key-value pairs. In particular it would cause for .. in to behave incorrectly.

like image 20
ecatmur Avatar answered Sep 05 '25 04:09

ecatmur