labjunky
labjunky

Reputation: 831

Python override dictionary access methods

I have a class devices that inherits from the dict class. devices is a dictionary of dictionaries. I would like to access its keys and values by using a method call syntax, and as well as using the normal syntax. i.e.

class Devices(dict):
    def __init__(self):
        self['device'] = {'name':'device_name'}
    def __getattr__(self, name):
        value = self[name]
        return value
...

mydevices = Devices()
mydevice = mydevices.device #works and mydevices['device'] also works

#But what code do I need for the following?
name = mydevices.device.name 
mydevices.device.name = 'another_name'

I know that if I override the __getattr__ and __setattr__ methods I can achieve this, but as you can see from my code I am not sure how to access a nested dictionary. Does anyone know how to achieve this?

thanks, labjunky

Upvotes: 3

Views: 3585

Answers (3)

shx2
shx2

Reputation: 64318

You can use this tricky class, which is a subclass of dict, and whose keys can be accssed either as items (d[k]) or as attributes (d.k):

class Struct(dict):
     def __init__(self,*args, **kwargs):
        dict.__init__(self,*args, **kwargs)
        self.__dict__ = self  # yes, it is tricky

The key here that the interpreter would check the __dict__ before calling __[gs]etattr__, so you don't even need to override them.

Now you can do:

class Devices(Struct):
    pass

devices = Devices()
devices.device = Struct(name='devicename')
devices.device.name = 'anothername'

Upvotes: 1

tmrlvi
tmrlvi

Reputation: 2361

In general, to access a dictionary values use dictionary[key] and not dictionary.key. In the first case the attribute way works because you overrode __getattr__ instead of __getitem__. That's why it works only for you class, but not for the inner dictionary.

Now, to access the dictionary in __getitem__ (__getattr__ is for attribute, like dict.keys where the word keys is the attribute) you need to be a bit careful. You need to call the __getitem__ of the dictionary class (otherwise you'll have infinite recursion). There are two ways to achieve it. The first one will be persistent if you choose to change you object hereditary:

def __getitem__(self, name):
    value = super(Devices,self).__getitem__(name)
    return value

Another way is:

def __getitem__(self, name):
    value = dict.__getitem__(self, name)
    return value

If you still want to use attributes to access the values you'll have to replace the dictionary with your class whenever you use it (so self['device'] = {'name':'device_name'} will become self['device'] = MyDictClass({'name':'device_name'}).


BTW: Note that you have an error in the __init__. The line self = {'device':{'name':'device_name'}} in __init__ actually doesn't do anything. (It puts the dictionary in the local variable self, but this variable disappears as soon as you exit the function.)

You should use operation that change the object itself instead. I.e.:

def __init__(self):
    self['device'] = {'name':'device_name'}

Upvotes: 1

ebarr
ebarr

Reputation: 7842

So your answer is pretty much complete anyway. You can define the kind of structure you want with:

class Devices(dict):
    def __init__(self,inpt={}):
        super(Devices,self).__init__(inpt)

    def __getattr__(self, name):
        return self.__getitem__(name)

    def __setattr__(self,name,value):
        self.__setitem__(name,value)

Then a use case would be:

In [6]: x = Devices()

In [7]: x.devices = Devices({"name":"thom"})

In [8]: x.devices.name
Out[8]: 'thom'

Basically just nest your attribute-look-up dictionaries.

Upvotes: 2

Related Questions