I\'m searching for an elegant way to get data using attribute access on a dict with some nested dicts and lists (i.e. javascript-style object syntax).
For example:
How about this:
from functools import partial
d2o=partial(type, "d2o", ())
This can then be used like this:
>>> o=d2o({"a" : 5, "b" : 3})
>>> print o.a
5
>>> print o.b
3
Surprisingly no one has mentioned Bunch. This library is exclusively meant to provide attribute style access to dict objects and does exactly what the OP wants. A demonstration:
>>> from bunch import bunchify
>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = bunchify(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'
A Python 3 library is available at https://github.com/Infinidat/munch - Credit goes to codyzu
class Struct(object):
"""Comment removed"""
def __init__(self, data):
for name, value in data.iteritems():
setattr(self, name, self._wrap(value))
def _wrap(self, value):
if isinstance(value, (tuple, list, set, frozenset)):
return type(value)([self._wrap(v) for v in value])
else:
return Struct(value) if isinstance(value, dict) else value
Can be used with any sequence/dict/value structure of any depth.
I stumbled upon the case I needed to recursively convert a list of dicts to list of objects, so based on Roberto's snippet here what did the work for me:
def dict2obj(d):
if isinstance(d, dict):
n = {}
for item in d:
if isinstance(d[item], dict):
n[item] = dict2obj(d[item])
elif isinstance(d[item], (list, tuple)):
n[item] = [dict2obj(elem) for elem in d[item]]
else:
n[item] = d[item]
return type('obj_from_dict', (object,), n)
elif isinstance(d, (list, tuple,)):
l = []
for item in d:
l.append(dict2obj(item))
return l
else:
return d
Note that any tuple will be converted to its list equivalent, for obvious reasons.
Hope this helps someone as much as all your answers did for me, guys.
The simplest way would be using collections.namedtuple.
I find the following 4-liner the most beautiful, which supports nested dictionaries:
def dict_to_namedtuple(typename, data):
return namedtuple(typename, data.keys())(
*(dict_to_namedtuple(typename + '_' + k, v) if isinstance(v, dict) else v for k, v in data.items())
)
The output will look good as well:
>>> nt = dict_to_namedtuple('config', {
... 'path': '/app',
... 'debug': {'level': 'error', 'stream': 'stdout'}
... })
>>> print(nt)
config(path='/app', debug=config_debug(level='error', stream='stdout'))
>>> print(nt.debug.level)
'error'
My dictionary is of this format:
addr_bk = {
'person': [
{'name': 'Andrew', 'id': 123, 'email': 'andrew@mailserver.com',
'phone': [{'type': 2, 'number': '633311122'},
{'type': 0, 'number': '97788665'}]
},
{'name': 'Tom', 'id': 456,
'phone': [{'type': 0, 'number': '91122334'}]},
{'name': 'Jack', 'id': 7788, 'email': 'jack@gmail.com'}
]
}
As can be seen, I have nested dictionaries and list of dicts. This is because the addr_bk was decoded from protocol buffer data that converted to a python dict using lwpb.codec. There are optional field (e.g. email => where key may be unavailable) and repeated field (e.g. phone => converted to list of dict).
I tried all the above proposed solutions. Some doesn't handle the nested dictionaries well. Others cannot print the object details easily.
Only the solution, dict2obj(dict) by Dawie Strauss, works best.
I have enhanced it a little to handle when the key cannot be found:
# Work the best, with nested dictionaries & lists! :)
# Able to print out all items.
class dict2obj_new(dict):
def __init__(self, dict_):
super(dict2obj_new, self).__init__(dict_)
for key in self:
item = self[key]
if isinstance(item, list):
for idx, it in enumerate(item):
if isinstance(it, dict):
item[idx] = dict2obj_new(it)
elif isinstance(item, dict):
self[key] = dict2obj_new(item)
def __getattr__(self, key):
# Enhanced to handle key not found.
if self.has_key(key):
return self[key]
else:
return None
Then, I tested it with:
# Testing...
ab = dict2obj_new(addr_bk)
for person in ab.person:
print "Person ID:", person.id
print " Name:", person.name
# Check if optional field is available before printing.
if person.email:
print " E-mail address:", person.email
# Check if optional field is available before printing.
if person.phone:
for phone_number in person.phone:
if phone_number.type == codec.enums.PhoneType.MOBILE:
print " Mobile phone #:",
elif phone_number.type == codec.enums.PhoneType.HOME:
print " Home phone #:",
else:
print " Work phone #:",
print phone_number.number