I'm utilising a package which returns a nested dictionary.
It feels awkward to access this return object in my class methods with the dictionary syntax, when everything else is in object syntax.
Searching has brought me to the bunch / neobunch packages, which seems to achieve what I'm after. I've also seen namedtuples suggested but these do not easily support nested attributes and most solutions rely on using dictionaries within the namedtuple for nesting.
What would be a more natural way of achieving this?
data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} }
print(data['b']['b1']['b2a']['b3b']) # dictionary access
# print(data.b.b1.b2a.b3b) # desired access
import neobunch
data1 = neobunch.bunchify(data)
print(data1.b.b1.b2a.b3b)
The following class would let you do what you want:
class AttrDict(dict):
""" Dictionary subclass whose entries can be accessed by attributes
(as well as normally).
"""
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
@staticmethod
def from_nested_dict(data):
""" Construct nested AttrDicts from nested dictionaries. """
if not isinstance(data, dict):
return data
else:
return AttrDict({key: AttrDict.from_nested_dict(data[key])
for key in data})
data = {
"a": "aval",
"b": {
"b1": {
"b2b": "b2bval",
"b2a": {
"b3a": "b3aval",
"b3b": "b3bval"
}
}
}
}
data1 = AttrDict.from_nested_dict(data)
print(data1.b.b1.b2a.b3b) # -> b3bval
A simple class, built on the basic object can be used:
class afoo1(object):
def __init__(self, kwargs):
for name in kwargs:
val = kwargs[name]
if isinstance(val, dict):
val = afoo1(val)
setattr(self,name,val)
I am borrowing the argparse.Namespace
definition, tweaked to allow for nesting.
It would be used as
In [172]: dd={'a':'aval','b':{'b1':'bval'}}
In [173]: f=afoo1(dd)
In [174]: f
Out[174]: <__main__.afoo1 at 0xb3808ccc>
In [175]: f.a
Out[175]: 'aval'
In [176]: f.b
Out[176]: <__main__.afoo1 at 0xb380802c>
In [177]: f.b.b1
Out[177]: 'bval'
It could also have been defined with **kwargs
(along with *args
). A __repr__
definition might be nice as well.
As with other simple objects, attributes can be added, e.g. f.c = f
(a recursive definition). vars(f)
returns a dictionary, though it does not do any recursive conversion).
What about using __setattr__
method ?
>>> class AttrDict(dict):
... def __getattr__(self, name):
... if name in self:
... return self[name]
...
... def __setattr__(self, name, value):
... self[name] = self.from_nested_dict(value)
...
... def __delattr__(self, name):
... if name in self:
... del self[name]
...
... @staticmethod
... def from_nested_dict(data):
... """ Construct nested AttrDicts from nested dictionaries. """
... if not isinstance(data, dict):
... return data
... else:
... return AttrDict({key: AttrDict.from_nested_dict(data[key])
... for key in data})
...
>>> ad = AttrDict()
>>> ad
{}
>>> data = {'a': 'aval', 'b': {'b1':{'b2a':{'b3a':'b3aval','b3b':'b3bval'},'b2b':'b2bval'}} }
>>> ad.data = data
>>> ad.data
{'a': 'aval', 'b': {'b1': {'b2a': {'b3a': 'b3aval', 'b3b': 'b3bval'}, 'b2b': 'b2bval'}}}
>>> print(ad.data.b.b1.b2a.b3b)
b3bval
Building on @martineau's excellent answer, you can make the AttrDict class to work on nested dictionaries without explicitly calling the from_nested_dict() function:
class AttrDict(dict):
""" Dictionary subclass whose entries can be accessed by attributes
(as well as normally).
"""
def __init__(self, *args, **kwargs):
def from_nested_dict(data):
""" Construct nested AttrDicts from nested dictionaries. """
if not isinstance(data, dict):
return data
else:
return AttrDict({key: from_nested_dict(data[key])
for key in data})
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
for key in self.keys():
self[key] = from_nested_dict(self[key])