How to make python class support item assignment?
Asked Answered
B

8

28

While looking over some code in Think Complexity, I noticed their Graph class assigning values to itself. I've copied a few important lines from that class and written an example class, ObjectChild, that fails at this behavior.

class Graph(dict):
    def __init__(self, vs=[], es=[]):
        for v in vs:
            self.add_vertex(v)

        for e in es:
            self.add_edge(e)

    def add_edge(self, e):
        v, w = e
        self[v][w] = e
        self[w][v] = e

    def add_vertex(self, v):
        self[v] = {}

class ObjectChild(object):
    def __init__(self, name):
        self['name'] = name

I'm sure the different built in types all have their own way of using this, but I'm not sure whether this is something I should try to build into my classes. Is it possible, and how? Is this something I shouldn't bother with, relying instead on simple composition, e.g. self.l = [1, 2, 3]? Should it be avoided outside built in types?

I ask because I was told "You should almost never inherit from the builtin python collections"; advice I'm hesitant to restrict myself to.

To clarify, I know that ObjectChild won't "work", and I could easily make it "work", but I'm curious about the inner workings of these built in types that makes their interface different from a child of object.

Bonehead answered 6/12, 2012 at 3:57 Comment(2)
This is tangential to your question, but using [] as a default argument value is likely to cause surprises. See this question.Gainer
I'm getting the idea from you all that inheriting from dict, or list, or tuple is the expected style in Python. It also looks like some of their behavior might be different and require inheritance because they're written in c, rather than in Python. Let me know if I'm wrong!Bonehead
I
5

Disclaimer : I might be wrong.

the notation :

self[something]

is legit in the Graph class because it inherits fro dict. This notation is from the dictionnaries ssyntax not from the class attribute declaration syntax.

Although all namespaces associated with a class are dictionnaries, in your class ChildObject, self isn't a dictionnary. Therefore you can't use that syntax.

Otoh, in your class Graph, self IS a dictionnary, since it is a graph, and all graphs are dictionnaries because they inherit from dict.

Idaho answered 6/12, 2012 at 4:10 Comment(0)
C
20

In Python 3 and later, just add these simple functions to your class:

class some_class(object):
    def __setitem__(self, key, value):
        setattr(self, key, value)

    def __getitem__(self, key):
        return getattr(self, key)
Cottager answered 2/7, 2020 at 12:35 Comment(1)
Thank you! Found what I was looking for after a few days of searching!Lindbom
V
14

They are accomplishing this magic by inheriting from dict. A better way of doing this is to inherit from UserDict or the newer collections.MutableMapping

You could accomplish a similar result by doing the same:

import collections

class ObjectChild(collections.MutableMapping):
    def __init__(self, name):
        self['name'] = name

You can also define two special functions to make your class dictionary-like: __getitem__(self, key) and __setitem__(self, key, value). You can see an example of this at Dive Into Python - Special Class Methods.

Ventris answered 6/12, 2012 at 4:7 Comment(1)
I think UserDict is obsolete (it's from the days when it was illegal to inherit from dict). MutableMapping also isn't something that provides item access (it's an Abstract Base Class). You need to write (or inherit) __getitem__ and __setitem__ methods, if you want to implement dictionary- or list-like item assignment and access.Jobber
M
6

Is using something like this ok?

def mk_opts_dict(d):
    ''' mk_options_dict(dict) -> an instance of OptionsDict '''
    class OptionsDict(object):
        def __init__(self, d):
            self.__dict__ = d

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

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

    return OptionsDict(d)
Mercantile answered 22/6, 2014 at 5:16 Comment(0)
I
5

Disclaimer : I might be wrong.

the notation :

self[something]

is legit in the Graph class because it inherits fro dict. This notation is from the dictionnaries ssyntax not from the class attribute declaration syntax.

Although all namespaces associated with a class are dictionnaries, in your class ChildObject, self isn't a dictionnary. Therefore you can't use that syntax.

Otoh, in your class Graph, self IS a dictionnary, since it is a graph, and all graphs are dictionnaries because they inherit from dict.

Idaho answered 6/12, 2012 at 4:10 Comment(0)
I
3

I realize this is an old post, but I was looking for some details around item assignment and stumbled upon the answers here. Ted's post wasn't completely wrong. To avoid inheritance from dict, you can make a class inherit from MutableMapping, and then provide methods for __setitem__ and __getitem__.

Additionally, the class will need to support methods for __delitem__, __iter__, __len__, and (optionally) other inherited mixin methods, like pop. The documentation has more info on the details.

from collections.abc import MutableMapping
class ItemAssign(MutableMapping):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __setitem__(self, k, v):
        setattr(self, k, v)
    def __getitem__(self, k):
        getattr(self, k)
    def __len__(self):
        return 2
    def __delitem__(self, k):
        self[k] = None
    def __iter__(self):
        yield self.a
        yield self.b

Example use:

>>> x = ItemAssign("banana","apple")
>>> x["a"] = "orange"
>>> x.a
'orange'
>>> del x["a"]
>>> print(x.a)
None
>>> x.pop("b")
'apple'
>>> print(x.b)
None

Hope this serves to clarify how to properly implement item assignment for others stumbling across this post :)

Incredible answered 21/12, 2018 at 6:12 Comment(0)
M
0

Your ObjectChild doesn't work because it's not a subclass of dict. Either of these would work:

class ObjectChild(dict):
    def __init__(self, name):
        self['name'] = name

or

class ObjectChild(object):
    def __init__(self, name):
        self.name = name
Mocha answered 6/12, 2012 at 4:7 Comment(0)
V
0

You don't need to inherit from dict. If you provide setitem and getitem methods, you also get the desired behavior I believe.

class a(object):
    def __setitem__(self, k, v):
        self._data[k] = v
    def __getitem__(self, k):
        return self._data[k]
    _data = {}
Velours answered 3/2, 2015 at 15:39 Comment(1)
Does not work. Downvoted because it would be such an easy thing to check.Lassiter
H
0

Little memo about <dict> inheritance

For those who want to inherit dict. In this case MyDict will have a shallow copy of original dict in it.

class MyDict(dict):
    ...

d = {'a': 1}
md = MyDict(d)

print(d['a'])   # 1
print(md['a'])  # 1

md['a'] = 'new'

print(d['a'])   # 1
print(md['a'])  # new

This could lead to problem when you have a tree of nested dicts and you want to covert part of it to an object. Changing this object will not affect its parent

root = {
    'obj': {
        'a': 1,
        'd': {'x': True}
    }
}
obj = MyDict(root['obj'])

obj['a'] = 2
print(root)   # {'obj': {'a': 1, 'd': {'x': True}}} # 'a' is the same

obj['d']['x'] = False
print(root)   # {'obj': {'a': 1, 'd': {'x': True}}} # 'x' chanded
Hannan answered 12/6, 2020 at 9:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.