4

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.

Rob Cowie
  • 22,259
  • 6
  • 62
  • 56
Andrew Lee
  • 2,543
  • 3
  • 20
  • 31
  • 1
    Have you looked into overloading __getitem__ or __setitem__ so you can say foo['key']? (given foo is an instance of you class) – kioopi Jun 24 '12 at 22:21
  • @JonClements The use case for me is I have some objects which all should be able to be serialized into JSON objects to be returned in a web server application. I turn them into `dict`s since the web framework handles the `dict->JSON string` part automatically. – Andrew Lee Jun 25 '12 at 04:32
  • Also they cannot just be `dict`s in the first place since they are also SQLAlchemy ORM classes. – Andrew Lee Jun 25 '12 at 04:39

4 Answers4

6

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
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • Can't see a requirement for this as we don't have a use case - but my answer would also be based on ABC's - AFAICT a dict would just do the job - if attribute access was *really* required then a `namedtuple` could be more useful as the `value` part of `key`. – Jon Clements Jun 24 '12 at 22:47
  • The classes I am using `Dictable` for are SQLAlchemy ORM classes, so I suppose they don't do much besides map keys to values (along with some under the hood DB stuff). – Andrew Lee Jun 25 '12 at 04:37
1

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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • `__iter__` on a `Mapping` should generate the keys, not key-value pairs. – Fred Foo Jun 24 '12 at 22:45
  • @larsmans good point, I was just thinking how unpythonic it was. Will correct. – ecatmur Jun 24 '12 at 22:46
  • the strange thing is that I cannot find this requirement in the docs for the `Mapping` ABC, nor the glossary... – Fred Foo Jun 24 '12 at 22:50
  • 1
    @larsmans http://docs.python.org/dev/reference/datamodel.html#object.__iter__ *For mappings, it should iterate over the keys of the container* – ecatmur Jun 24 '12 at 22:54
0

You can certainly overload dict() but you almost never want to! Too many aspects of the standard library depend upon dict being available and you will break most of its functionality. You cab probably do something like this though:

class Dictable:
   def dict(self):
     return self.__dict__
ennuikiller
  • 46,381
  • 14
  • 112
  • 137
0

Most of the answers here are about making your class behave like a dict, which isn't actually what you asked. If you want to express the idea, "I am a class that can be turned into a dict," I would simply define a bunch of classes and have them each implement .dict(). Python favors duck-typing (what an object can do) over what an object is. The ABC doesn't add much. Documentation serves the same purpose.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662