Advantages of UserDict class?
Asked Answered
P

5

30

What are advantages of using UserDict class?

I mean, what I really get if instead of

class MyClass(object):
    def __init__(self):
        self.a = 0
        self.b = 0
...
m = MyClass()
m.a = 5
m.b = 7

I will write the following:

class MyClass(UserDict):
    def __init__(self):
        UserDict.__init__(self)
        self["a"] = 0
        self["b"] = 0
...
m = MyClass()
m["a"] = 5
m["b"] = 7

Edit: If I understand right I can add new fields to an object in a runtime in both cases?

m.c = "Cool"

and

m["c"] = "Cool"
Piny answered 8/9, 2009 at 7:0 Comment(0)
A
48

UserDict.UserDict has no substantial added value since Python 2.2, since, as @gs mention, you can now subclass dict directly -- it exists only for backwards compatibility with Python 2.1 and earlier, when builtin types could not be subclasses. Still, it was kept in Python 3 (now in its proper place in the collections module) since, as the docs now mention,

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.

UserDict.DictMixin, in Python 2, is quite handy -- as the docs say,

The module defines a mixin, DictMixin, defining all dictionary methods for classes that already have a minimum mapping interface. This greatly simplifies writing classes that need to be substitutable for dictionaries (such as the shelve module).

You subclass it, define some fundamental methods (at least __getitem__, which is sufficient for a read-only mapping without the ability to get keys or iterate; also keys if you need those abilities; possibly __setitem__, and you have a R/W mapping without the ability of removing items; add __delitem__ for full capability, and possibly override other methods for reasons of performance), and get a full-fledged implementation of dict's rich API (update, get, and so on). A great example of the Template Method design pattern.

In Python 3, DictMixin is gone; you can get almost the same functionality by relying on collections.MutableMapping instead (or just collections.Mapping for R/O mappings). It's a bit more elegant, though not QUITE as handy (see this issue, which was closed with "won't fix"; the short discussion is worth reading).

Amabil answered 8/9, 2009 at 15:11 Comment(5)
since '09 things moved a bit in python 3, and DictMixin is back: docs.python.org/library/userdict.html#UserDict.DictMixin - though MutableMapping is indeed suggest for most uses.Dead
Is this still valid? Reading "Fluent Python", the author recommends subclassing UserDict because "major caveat: the code of the built-ins (written in C) does not call special (magic) methods overridden by user-defined classes". This books covers 3.x but does this warning not apply to 2.7 as well?Gutenberg
@Stefano: There is not now, and I'm pretty sure there has never been, any DictMixin in Python 3's standard library. The link you gave was at the time a link to the Python 2.7.2 documentation (currently a 404).Helladic
@Kundor you seem to be right, though I am left wondering why would I have posted a py2 doc link at the time. There's docs.python.org/3/library/collections.html#collections.UserDict in py3, maybe I got confused. Thanks for pointing that out!Dead
I see the UserDict in the official Python 3 documentation docs.python.org/3/library/collections.html#collections.UserDict but don't see anything about MutableMapping. Most likely, the answer is outdated.Subcommittee
B
8

Subclassing the dict gives you all the features of a dict, like if x in dict:. You normally do this if you want to extend the features of the dict, creating an ordered dict for example.

BTW: In more recent Python versions you can subclass dict directly, you don't need UserDict.

Burdensome answered 8/9, 2009 at 7:3 Comment(0)
L
7

It's tricky to overwrite dict correctly, while UserDict makes it easy. There was some discussion to remove it from Python3, but I believe it was kept for this reason. Example:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Oups MyDict.__setitem__ not called
d.update(c=3)  # Oups MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDict inherit collections.abc.MutableMapping, so don't have those drawback:

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}
Lizarraga answered 2/11, 2020 at 13:27 Comment(4)
such code is antipatternRapine
Why would it be antipattern ? Inheriting from MutableMapping / UserDict are very standard (those classes were designed for this)Lizarraga
implicitly changing the value. v*10Rapine
This is just a dummy demo showing why inheriting UserDict is simpler than dict. Of course in practice, the implementation would contain actual code.Lizarraga
G
5

Well, as of 3.6 there are certainly some disadvantages, as I just found out. Namely, isinstance(o, dict) returns False.

    from collections import UserDict

    class MyClass(UserDict):
        pass

    data = MyClass(a=1,b=2)

    print("a:", data.get("a"))
    print("is it a dict?:", isinstance(data, dict))

Not a dict!

a: 1
is it a dict?: False

Change to class MyClass(dict): and isinstance returns True.

However... with UserDict you can step into its implementation.

(pdb-ing into functions/methods is an easy way to see exactly how they work)


#assumes UserDict

di = MyClass()

import pdb

#pdb will have work if your ancestor is UserDict, but not with dict
#since it is c-based
pdb.set_trace()
di["a"]= 1

Gutenberg answered 17/3, 2020 at 18:18 Comment(1)
While UserDict does not inherit from a dictionary, the data attribute that stores the dict for UserDict objects is a normal dict and can be tested such as isinstance(mydict.data, dict)Jervis
C
0

One additional reason that these other answers don't address for the reasons to keep UserDict around is for the compatibility with PyPy. For dict, PyPy says here that:

Officially, CPython has no rule at all for when exactly overridden method of subclasses of built-in types get implicitly called or not. As an approximation, these methods are never called by other built-in methods of the same object. For example, an overridden __getitem__() in a subclass of dict will not be called by e.g. the built-in get() method.

So if you create a subclass of dict, use the get method, and are expecting the getitem to be called, it won't be. However, if you implement your subclass using UserDict, this will work as you would expect in both Python and PyPy, because UserDict implements the get method as such:

def get(self, key, default=None):
    if key in self:
        return self[key]
    return default

You can see the source code for UserDict here: https://github.com/python/cpython/blob/main/Lib/collections/__init__.py#L1117

Also - sure, you could just implement the get method yourself when you subclass dict. But why, when you can just use the methods already implemented in UserDict?

Capper answered 19/1 at 7:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.