A python class that acts like dict

Viewed 206263

I want to write a custom class that behaves like dict - so, I am inheriting from dict.

My question, though, is: Do I need to create a private dict member in my __init__() method?. I don't see the point of this, since I already have the dict behavior if I simply inherit from dict.

Can anyone point out why most of the inheritance snippets look like the one below?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

Instead of the simpler...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

Actually, I think I suspect the answer to the question is so that users cannot directly access your dictionary (i.e. they have to use the access methods that you have provided).

However, what about the array access operator []? How would one implement that? So far, I have not seen an example that shows how to override the [] operator.

So if a [] access function is not provided in the custom class, the inherited base methods will be operating on a different dictionary?

I tried the following snippet to test out my understanding of Python inheritance:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

I got the following error:

KeyError: < built-in function id>

What is wrong with the code above?

How do I correct the class myDict so that I can write code like this?

md = myDict()
md['id'] = 123

[Edit]

I have edited the code sample above to get rid of the silly error I made before I dashed away from my desk. It was a typo (I should have spotted it from the error message).

10 Answers

Here is an alternative solution:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2

This is my best solution. I used this many times.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

You can use like:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])

A python class that acts like dict

What's wrong with this?

Can anyone point out why most of the inheritance snippets look like the one below?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

Presumably there's a good reason to inherit from dict (maybe you're already passing one around and you want a more specific kind of dict) and you have a good reason to instantiate another dict to delegate to (because this will instantiate two dicts per instance of this class.) But doesn't that sound incorrect?

I never run into this use-case myself. I do like the idea of typing dicts where you are using dicts that are type-able. But in that case I like the idea of typed class attributes even moreso - and the whole point of a dict is you can give it keys of any hashable type, and values of any type.

So why do we see snippets like this? I personally think it's an easily made mistake that went uncorrected and thus perpetuated over time.

I would rather see, in these snippets, this, to demonstrate code reuse through inheritance:

class AlternativeOne(dict):
    __slots__ = ()
    def __init__(self):
        super().__init__()
        # other init code here
    # new methods implemented here

or, to demonstrate re-implementing the behavior of dicts, this:

from collections.abc import MutableMapping 

class AlternativeTwo(MutableMapping):
    __slots__ = '_mydict'
    def __init__(self):
        self._mydict = {}
        # other init code here
    # dict methods reimplemented and new methods implemented here

By request - adding slots to a dict subclass.

Why add slots? A builtin dict instance doesn't have arbitrary attributes:

>>> d = dict()
>>> d.foo = 'bar'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'foo'

If we create a subclass the way most are doing it here on this answer, we see we don't get the same behavior, because we'll have a __dict__ attribute, causing our dicts to take up to potentially twice the space:

my_dict(dict):
    """my subclass of dict""" 

md = my_dict()
md.foo = 'bar'

Since there's no error created by the above, the above class doesn't actually act, "like dict."

We can make it act like dict by giving it empty slots:

class my_dict(dict):
    __slots__ = ()

md = my_dict()

So now attempting to use arbitrary attributes will fail:

>>> md.foo = 'bar'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'my_dict' object has no attribute 'foo'

And this Python class acts more like a dict.

For more on how and why to use slots, see this Q&A: Usage of __slots__?

UserDict from the Python standard library is designed for this purpose.

I really don't see the right answer to this anywhere

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

All you are really having to do is define your own __init__ - that really is all that there is too it.

Another example (little more complex):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

This last example is handy when you want to embed some kind of logic.

Anyway... in short class GiveYourClassAName(dict) is enough to make your class act like a dict. Any dict operation you do on self will be just like a regular dict.

Don’t inherit from Python built-in dict, ever! for example update method woldn't use __setitem__, they do a lot for optimization. Use UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
Related