A python class that acts like dict
Asked Answered
W

11

168

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).

Wellfounded answered 25/10, 2010 at 12:42 Comment(0)
R
48

Check the documentation on emulating container types. In your case, the first parameter to add should be self.

Richert answered 25/10, 2010 at 12:47 Comment(1)
For the sake of completeness, here is the link to the documentation mentioned by @björn-pollex for the latest Python 2.x (2.7.7 as of the time of writing): Emulating Container Types (Sorry for not using the comments function, I'm just not allowed to do so by stackoverflow.)Jerry
S
147
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}
Shark answered 1/6, 2014 at 5:10 Comment(2)
If you're going to subclass dict, then you should use the object itself (using super) instead of simply delegating to the instance's __dict__ - which essentially means you're creating two dicts for every instance.Barrier
self.__dict__ is not the same as the actual dictionary content. Every python object, regardless of its type, has a _dict__ which contains all the object attributes (methods, fields, etc). You do not want to mess around with this unless you want to write code that is modifying itself...Toxicology
I
122

Like this

class CustomDictOne(dict):
   def __init__(self, *arg, **kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Now you can use the built-in functions, like dict.get() as self.get().

You do not need to wrap a hidden self._dict. Your class already is a dict.

Individuate answered 25/10, 2010 at 13:39 Comment(6)
This. There's no point in inheriting from dict without calling its constructor first.Cementation
Note that your inherited dict actually contains 2 dict-instance: The 1st is the inherited container, and the 2nd is the dict holding the class-attributes - you may avoid that by using slots.Oceania
The __dict__ is actually only created when it is first accessed, so as long as users don't attempt to use it, it's fine. __slots__ would be nice though.Barrier
Use spaces after commas you savage! ;-)Glenglencoe
@AaronHall or @ankostis, would one of you be kind enough to detail adding __slots__ to the above? Have checked other posts and toyed around a bit, but as of now it has escaped me.Christi
@andrewtavis I have posted an answer that demonstrates slots.Barrier
R
48

Check the documentation on emulating container types. In your case, the first parameter to add should be self.

Richert answered 25/10, 2010 at 12:47 Comment(1)
For the sake of completeness, here is the link to the documentation mentioned by @björn-pollex for the latest Python 2.x (2.7.7 as of the time of writing): Emulating Container Types (Sorry for not using the comments function, I'm just not allowed to do so by stackoverflow.)Jerry
D
13

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"])
Drummond answered 14/4, 2020 at 7:37 Comment(0)
B
10

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__?

Barrier answered 14/11, 2020 at 16:59 Comment(0)
P
10

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

Pannier answered 17/1, 2021 at 17:43 Comment(2)
YES. Most answers here are still fine, but this simplifies the whole process. With UserDict instead of dict as the base class, you get a "data" attribute, for when you need to interact with your class as if it were just a dictionary (e.g. if you want to json encode the dictionary data).Thrombophlebitis
The User<Type> classes were created before it was possible to subclass the builtin types they intend to emulate, and I find them unnecessarily inefficient.Barrier
R
9

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
Reaganreagen answered 9/9, 2019 at 14:38 Comment(2)
This is bad because you don't define any custom method and also there are other issues as other answer has said.Thracian
this is exactly what I needed. Thank you!Blastocyst
T
2

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.

Toughie answered 7/4, 2020 at 19:3 Comment(0)
C
1

The problem with this chunk of code:

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

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


md = myDict()
md.add('id', 123)

...is that your 'add' method (...and any method you want to be a member of a class) needs to have an explicit 'self' declared as its first argument, like:

def add(self, 'id', 23):

To implement the operator overloading to access items by key, look in the docs for the magic methods __getitem__ and __setitem__.

Note that because Python uses Duck Typing, there may actually be no reason to derive your custom dict class from the language's dict class -- without knowing more about what you're trying to do (e.g, if you need to pass an instance of this class into some code someplace that will break unless isinstance(MyDict(), dict) == True), you may be better off just implementing the API that makes your class sufficiently dict-like and stopping there.

Catima answered 25/10, 2010 at 12:54 Comment(0)
H
0

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
Heeley answered 24/7, 2019 at 8:7 Comment(3)
Based on what should someone never inherit from the built-in dict? From the documentation (docs.python.org/3/library/collections.html#collections.UserDict): "The need for this class has been partially supplanted by the ability to subclass directly from dict; however, this class can be easier to work with because the underlying dictionary is accessible as an attribute." Also on the same page: The collections module is "Deprecated since version 3.3, will be removed in version 3.9: Moved Collections Abstract Base Classes to the collections.abc module."... which doesn't have UserDict.Uremia
I suspect this is inspired by, or at least resonates with, treyhunner.com/2019/04/…Lanellelanette
@Uremia regarding the second point: there would be little point in deprecating a whole module and putting it one namespace level deeper. The deprecation warning applies to the abstract base classes (ABCs) that used to live in collections and now live in collections.abc. This applies to every class in collections.abc. UserDict is not an ABC. Neither are Counter and defaultdict. A full module deprecation would be noted very differently in the docs.Vacate
T
0

these days I would use collections.abc (docs here). this gives you some implementations for free, and you only implement to bare minimum subset of methods.

from collections.abc import MutableMapping

class D(MutableMapping):
    '''
    Mapping that works like both a dict and a mutable object, i.e.
    d = D(foo='bar')
    and 
    d.foo returns 'bar'
    '''
    # ``__init__`` method required to create instance from class.
    def __init__(self, *args, **kwargs):
        '''Use the object dict'''
        self.__dict__.update(*args, **kwargs)
    # The next five methods are requirements of the ABC.
    def __setitem__(self, key, value):
        self.__dict__[key] = value
    def __getitem__(self, key):
        return self.__dict__[key]
    def __delitem__(self, key):
        del self.__dict__[key]
    def __iter__(self):
        return iter(self.__dict__)
    def __len__(self):
        return len(self.__dict__)
    # The final two methods aren't required, but nice for demo purposes:
    def __str__(self):
        '''returns simple dict representation of the mapping'''
        return str(self.__dict__)
    def __repr__(self):
        '''echoes class, id, & reproducible representation in the REPL'''
        return '{}, D({})'.format(super(D, self).__repr__(), 
                                  self.__dict__)

example stolen from: https://mcmap.net/q/145296/-how-would-i-implement-a-dict-with-abstract-base-classes-in-python-duplicate

Thekla answered 14/3 at 6:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.