690

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:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

Should be accessible in this way:

>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
bar

I think, this is not possible without recursion, but what would be a nice way to get an object style for dicts?

6
  • 6
    I was trying to do something similar recently, but a recurring dictionary key ("from" - which is a Python keyword) prevented me from going through with it. Because as soon as you tried using "x.from" to access that attribute you'd get a syntax error. Commented Aug 20, 2009 at 13:08
  • 4
    that's a problem indeed, but i can abandon on "from" to make life easier in accessing large dict constructs :) typing x['a']['d'][1]['foo'] is really annoying, so x.a.d[1].foo rules. if you need from, you can access it via getattr(x, 'from') or use _from as attribute instead. Commented Aug 20, 2009 at 15:51
  • 8
    from_ rather than _from according to PEP 8. Commented Jan 14, 2013 at 7:32
  • 4
    Most of these "solutions" don't seem to work (even the accepted one, doesn't allow nested d1.b.c), I think it's clear you should be using something from a library, e.g. namedtuple from collections, as this answer suggests, ... Commented Apr 29, 2013 at 14:50
  • 2
    Bunch - Use a Python dict like an object: thechangelog.com/bunch-lets-use-python-dict-like-object Commented Mar 10, 2014 at 17:23

46 Answers 46

753

Update: In Python 2.6 and onwards, consider whether the namedtuple data structure suits your needs:

>>> from collections import namedtuple
>>> MyStruct = namedtuple('MyStruct', 'a b d')
>>> s = MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s
MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s.a
1
>>> s.b
{'c': 2}
>>> s.c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyStruct' object has no attribute 'c'
>>> s.d
['hi']

The alternative (original answer contents) is:

class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)

Then, you can use:

>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2
Sign up to request clarification or add additional context in comments.

12 Comments

Same here - this is particularly useful for reconstructing Python objects from document oriented databases like MongoDB.
To get prettier printing add: def repr__(self): return '<%s>' % str('\n '.join('%s : %s' % (k, repr(v)) for (k, v) in self.__dict.iteritems()))
Will this work with nested dictionaries? and dicts containing objects and or list etc. Are there any catches?
@Sam S: it won't create nested Structs from nested dictionaries, but in general the type of the value can be anything. Key is restricted to being suitable for an object slot
-1 because a) it doesn't work for nested dicts, which the question clearly asked for, and b) the same functionality currently exists in standard libraries in argparse.Namespace (which has also defined __eq__, __ne__, __contains__).
|
194

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

>>> from munch import DefaultMunch
>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> obj = DefaultMunch.fromDict(d)
>>> obj.b.c
2
>>> obj.a
1
>>> obj.d[1].foo
'bar'

7 Comments

And there is a python 3 compatible (appears to be 2.6-3.4) branch of Bunch named Munch: github.com/Infinidat/munch
Bunch is the best solution of all of these because it supports multiple types of serialization well, and is maintained. Great, thank you. I wish the python3 version was named the same thing, buecause why not.
So just a forewarning, Bunch and Attrdict as well for that matter are very slow. They consumed about 1/3 and 1/2 of my request time respectively when they saw frequent use in our application. Definitely not something to ignore. Talk more about it stackoverflow.com/a/31569634/364604.
While this does allow object-like access to dicts, it does so via getattr. This makes introspection in smart REPLs like IPython difficult.
if use python 2.+ => pypi.org/project/bunch if use python 3.+ => pypi.org/project/munch As you are in python3 , use dict.items() instead of dict.iteritems() iteritems() was removed in python3, so you can't use this method anymore.
|
145
class obj(object):
    def __init__(self, d):
        for k, v in d.items():
            if isinstance(k, (list, tuple)):
                setattr(self, k, [obj(x) if isinstance(x, dict) else x for x in v])
            else:
                setattr(self, k, obj(v) if isinstance(v, dict) else v)

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = obj(d)
>>> x.b.c
2
>>> x.d[1].foo
'bar'

12 Comments

Good! I'd replace .items() by .iteritems(), though, for a smaller memory footprint.
If not an OP requirement, this is not an issue - but note that this won't recursively process objects in lists within lists.
I realise this is an old answer, but these days it would be better to use an abstract base class instead of that ugly line if isinstance(b, (list, tuple)):
Tip: Have the obj class inherit from argparse.Namespace for additional features like readable string representation.
I am wondering how an answer with a bug got 137 positive votes... if isinstance(k, (list, tuple)):: k is the key, you should test v instead!
|
80
x = type('new_dict', (object,), d)

then add recursion to this and you're done.

This is how I'd implement it:

>>> d
{'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> def obj_dic(d):
    top = type('new', (object,), d)
    seqs = tuple, list, set, frozenset
    for i, j in d.items():
        if isinstance(j, dict):
            setattr(top, i, obj_dic(j))
        elif isinstance(j, seqs):
            setattr(top, i, 
                type(j)(obj_dic(sj) if isinstance(sj, dict) else sj for sj in j))
        else:
            setattr(top, i, j)
    return top

>>> x = obj_dic(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

2 Comments

Why are you creating type-objects and not instantiating them? Wouldn't that be more logical? I mean, why not do top_instance = top() and returning that where you return top?
Nice for the "leaf" data, but the examples conveniently leave out "twigs" like x and x.b which return ugly <class '__main__.new'>
71
# Applies to Python-3 Standard Library
class Struct(object):
    def __init__(self, data):
        for name, value in data.items():
            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


# Applies to Python-2 Standard Library
class Struct(object):
    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.

5 Comments

This should be the answer. It works well for nesting. You can use this as an object_hook for json.load() as well.
Similar to SilentGhost's functional answer from 2009 --the leaf node data is accessible, but the parent/twigs display as object references. To pretty-print, def __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
Python 3.x users: it's just .items() instead of .iteritems() in line 4. (The function was renamed, but does essentialy the same)
Works perfect for nested objects, thanks! As a comment newbies as I, for python3 iteritems() must be changed to items()
The pretty-print equivalent for python 3 is: def __repr__(self): return ("{ " + str(", ".join([f"'{k}': {v}" for k, v in [(k, repr(v)) for (k, v) in self.__dict__.items()]])) + " }")
59

There's a collection helper called namedtuple, that can do this for you:

from collections import namedtuple

d_named = namedtuple('Struct', d.keys())(*d.values())

In [7]: d_named
Out[7]: Struct(a=1, b={'c': 2}, d=['hi', {'foo': 'bar'}])

In [8]: d_named.a
Out[8]: 1

4 Comments

This does not answer the question of recursion for the nested dicts.
you can't modify namedtuples.
Can we guarantee that the order of the list returned by keys() and values() match in order? I mean, if it is a OrderedDict, yes. But a standard dict? I know that CPython 3.6+ and PyPy "compact" dicts are ordered, but quoting documentation: "The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon"
this also works d_named = namedtuple('Struct', d)(**d)
44

If your dict is coming from json.loads(), you can turn it into an object instead (rather than a dict) in one line:

import json
from collections import namedtuple

json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

See also How to convert JSON data into a Python object.

1 Comment

Seems to be much slower than regular .loads if you have a huge JSON object
41

You can leverage the json module of the standard library with a custom object hook:

import json

class DictObject(object):
    def __init__(self, dict_):
        self.__dict__.update(dict_)

    @classmethod
    def from_dict(cls, d):
        return json.loads(json.dumps(d), object_hook=DictObject)

Example usage:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> o = DictObject.from_dict(d)
>>> o.a
1
>>> o.b.c
2
>>> o.d[0]
'hi'
>>> o.d[1].foo
'bar'
>>>

And it is not strictly read-only as it is with namedtuple, i.e. you can change values – not structure:

>>> o.b.c = 3
>>> o.b.c
3

2 Comments

I like the idea to use the json loading mechanism for nested elements. But we already have a dict and I don't like the fact the one needs to create a string out of it to map it into an object. I would rather like to have a solution that creates objects directly from a dict.
Needs to convert to string first but pretty generic as one could extend it via json.JSONEncoder and object_hook.
31

Taking what I feel are the best aspects of the previous examples, here's what I came up with:

class Struct:
    """The recursive class for building and representing objects with."""

    def __init__(self, obj):
        for k, v in obj.items():
            if isinstance(v, dict):
                setattr(self, k, Struct(v))
            else:
                setattr(self, k, v)

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

    def __repr__(self):
        return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for (k, v) in self.__dict__.items()))

3 Comments

Note that the constructor can be shortened to: def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) which also removes the explicit call to Struct
I'd rather not downvote my own answer, but looking back on this I've noticed that it doesn't recurse into sequence types. x.d[1].foo fails in this case.
the isinstance(v, dict) check would be better as isinstance(v, collections.Mapping) so it can handle future dict-like things
24

I ended up trying BOTH the AttrDict and the Bunch libraries and found them to be way too slow for my uses. After a friend and I looked into it, we found that the main method for writing these libraries results in the library aggressively recursing through a nested object and making copies of the dictionary object throughout. With this in mind, we made two key changes. 1) We made attributes lazy-loaded 2) instead of creating copies of a dictionary object, we create copies of a light-weight proxy object. This is the final implementation. The performance increase of using this code is incredible. When using AttrDict or Bunch, these two libraries alone consumed 1/2 and 1/3 respectively of my request time(what!?). This code reduced that time to almost nothing(somewhere in the range of 0.5ms). This of course depends on your needs, but if you are using this functionality quite a bit in your code, definitely go with something simple like this.

class DictProxy(object):
    def __init__(self, obj):
        self.obj = obj

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

    def __getattr__(self, key):
        try:
            return wrap(getattr(self.obj, key))
        except AttributeError:
            try:
                return self[key]
            except KeyError:
                raise AttributeError(key)

    # you probably also want to proxy important list properties along like
    # items(), iteritems() and __len__

class ListProxy(object):
    def __init__(self, obj):
        self.obj = obj

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

    # you probably also want to proxy important list properties along like
    # __iter__ and __len__

def wrap(value):
    if isinstance(value, dict):
        return DictProxy(value)
    if isinstance(value, (tuple, list)):
        return ListProxy(value)
    return value

See the original implementation here by https://stackoverflow.com/users/704327/michael-merickel.

The other thing to note, is that this implementation is pretty simple and doesn't implement all of the methods you might need. You'll need to write those as required on the DictProxy or ListProxy objects.

1 Comment

Note that for Python 3.10 and above, you'll need to use Mapping and Sequence from collections.abc instead of collections
20

If you want to access dict keys as an object (or as a dict for difficult keys), do it recursively, and also be able to update the original dict, you could do:

class Dictate(object):
    """Object view of a dict, updating the passed in dict when values are set
    or deleted. "Dictate" the contents of a dict...: """

    def __init__(self, d):
        # since __setattr__ is overridden, self.__dict = d doesn't work
        object.__setattr__(self, '_Dictate__dict', d)

    # Dictionary-like access / updates
    def __getitem__(self, name):
        value = self.__dict[name]
        if isinstance(value, dict):  # recursively view sub-dicts as objects
            value = Dictate(value)
        return value

    def __setitem__(self, name, value):
        self.__dict[name] = value
    def __delitem__(self, name):
        del self.__dict[name]

    # Object-like access / updates
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value
    def __delattr__(self, name):
        del self[name]

    def __repr__(self):
        return "%s(%r)" % (type(self).__name__, self.__dict)
    def __str__(self):
        return str(self.__dict)

Example usage:

d = {'a': 'b', 1: 2}
dd = Dictate(d)
assert dd.a == 'b'  # Access like an object
assert dd[1] == 2  # Access like a dict
# Updates affect d
dd.c = 'd'
assert d['c'] == 'd'
del dd.a
del dd[1]
# Inner dicts are mapped
dd.e = {}
dd.e.f = 'g'
assert dd['e'].f == 'g'
assert d == {'c': 'd', 'e': {'f': 'g'}}

Comments

16
>>> def dict2obj(d):
        if isinstance(d, list):
            d = [dict2obj(x) for x in d]
        if not isinstance(d, dict):
            return d
        class C(object):
            pass
        o = C()
        for k in d:
            o.__dict__[k] = dict2obj(d[k])
        return o


>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

1 Comment

Could you provide an explanation on what the code does?
16

In 2021, use pydantic BaseModel - will convert nested dicts and nested json objects to python objects and vice versa:

https://pydantic-docs.helpmanual.io/usage/models/

>>> class Foo(BaseModel):
...     count: int
...     size: float = None
... 
>>> 
>>> class Bar(BaseModel):
...     apple = 'x'
...     banana = 'y'
... 
>>> 
>>> class Spam(BaseModel):
...     foo: Foo
...     bars: List[Bar]
... 
>>> 
>>> m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])

Object to dict

>>> print(m.dict())
{'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}

Object to JSON

>>> print(m.json())
{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}

Dict to object

>>> spam = Spam.parse_obj({'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y2'}]})
>>> spam
Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y2')])

JSON to object

>>> spam = Spam.parse_raw('{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}')
>>> spam
Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')])

3 Comments

This one should be the accepted answer. Thank you so much! I spent an hour searching for a solution that plays nicely with type-annotated classes.
While this works if you know the structure of the dict, you're not able to create a pydantic model dynamically. For that you need to call create_model(__model_name="ModelName", **some_dict). This however only works with the top level, so you have to make it generate pydantic model recursively.. But creating pydantic models is slower than other modules such as named_tuples or dataclasses.
What is the overhead of pydantic compared to munch compared to namedtuples?
11

x.__dict__.update(d) should do fine.

6 Comments

thanks for your answer, but what is x? the dict or a standard object? could you give me a hint please.
x is your object. Every object has a dict. By updating the dict of a object you are actually updating the key vars in it.
The bold words are _ _ dict _ _
This won't handle nested dictionaries.
Not every object has a __dict__: try object().__dict__ and you'll get AttributeError: 'object' object has no attribute '__dict__'
|
9

Typically you want to mirror dict hierarchy into your object but not list or tuples which are typically at lowest level. So this is how I did this:

class defDictToObject(object):

    def __init__(self, myDict):
        for key, value in myDict.items():
            if type(value) == dict:
                setattr(self, key, defDictToObject(value))
            else:
                setattr(self, key, value)

So we do:

myDict = { 'a': 1,
           'b': { 
              'b1': {'x': 1,
                    'y': 2} },
           'c': ['hi', 'bar'] 
         }

and get:

x.b.b1.x 1

x.c ['hi', 'bar']

Comments

8

This should get your started:

class dict2obj(object):
    def __init__(self, d):
        self.__dict__['d'] = d

    def __getattr__(self, key):
        value = self.__dict__['d'][key]
        if type(value) == type({}):
            return dict2obj(value)

        return value

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)
print x.a
print x.b.c
print x.d[1].foo

It doesn't work for lists, yet. You'll have to wrap the lists in a UserList and overload __getitem__ to wrap dicts.

1 Comment

To make it work for lists, use the if isinstance(d, list) clause from Anon's answer.
8

I know there's already a lot of answers here already and I'm late to the party but this method will recursively and 'in place' convert a dictionary to an object-like structure... Works in 3.x.x

def dictToObject(d):
    for k,v in d.items():
        if isinstance(v, dict):
            d[k] = dictToObject(v)
    return namedtuple('object', d.keys())(*d.values())

# Dictionary created from JSON file
d = {
    'primaryKey': 'id', 
    'metadata': 
        {
            'rows': 0, 
            'lastID': 0
        }, 
    'columns': 
        {
            'col2': {
                'dataType': 'string', 
                'name': 'addressLine1'
            }, 
            'col1': {
                'datatype': 'string', 
                'name': 'postcode'
            }, 
            'col3': {
                'dataType': 'string', 
                'name': 'addressLine2'
            }, 
            'col0': {
                'datatype': 'integer', 
                'name': 'id'
            }, 
            'col4': {
                'dataType': 'string', 
                'name': 'contactNumber'
            }
        }, 
        'secondaryKeys': {}
}

d1 = dictToObject(d)
d1.columns.col1 # == object(datatype='string', name='postcode')
d1.metadata.rows # == 0

2 Comments

What do you mean "object-like structure"?
Object-like because of the duck typing principle. What I mean is you can access its properties like you would if it was an instance of a class. but it's NOT an object in that sense, it's a named tuple.
8

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'

Comments

6

This also works well too

class DObj(object):
    pass

dobj = Dobj()
dobj.__dict__ = {'a': 'aaa', 'b': 'bbb'}

print dobj.a
>>> aaa
print dobj.b
>>> bbb

2 Comments

Thanks for the answer. You have a typo on line 4, it should be: dobj = DObj(). When trying to run this code in another env I get this error: TypeError: 'DObj' object is not subscriptable , do you know a fix for this? :)
Nvm, I found the cause, my dictionary is not flat.
5
from mock import Mock
d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
my_data = Mock(**d)

# We got
# my_data.a == 1

2 Comments

ModuleNotFoundError: No module named 'mock'.
mock was a third party package, now incorporated into unittest. from unittest.mock import Mock should work. One downside is, if your code is production code, it now depends on what should theoretically be just test code. Another is, this only works for shallow dictionaries. But, at the top level, a nested dictionary gets returned correctly, unlike many of the answers above.
4

Let me explain a solution I almost used some time ago. But first, the reason I did not is illustrated by the fact that the following code:

d = {'from': 1}
x = dict2obj(d)

print x.from

gives this error:

  File "test.py", line 20
    print x.from == 1
                ^
SyntaxError: invalid syntax

Because "from" is a Python keyword there are certain dictionary keys you cannot allow.


Now my solution allows access to the dictionary items by using their names directly. But it also allows you to use "dictionary semantics". Here is the code with example usage:

class dict2obj(dict):
    def __init__(self, dict_):
        super(dict2obj, 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(it)
            elif isinstance(item, dict):
                self[key] = dict2obj(item)

    def __getattr__(self, key):
        return self[key]

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)

assert x.a == x['a'] == 1
assert x.b.c == x['b']['c'] == 2
assert x.d[1].foo == x['d'][1]['foo'] == "bar"

1 Comment

class Struct: def init__(self, **entries): self.__dict.update(entries)
4

This preserves the attributes for the type that's imported into the class:

class Struct(dict):
  def __init__(self,data):
    for key, value in data.items():
      if isinstance(value, dict):
        setattr(self, key, Struct(value))
      else:   
        setattr(self, key, type(value).__init__(value))

      dict.__init__(self,data)

My only concern would be overwriting methods from within the dictionary your parsing.

1 Comment

Although a nice idea, this doesn't seem to work for the OP's example, in fact it seems to modify the passed in dictionary! In fact, df.a doesn't even work.
4

This is my code using recursive dict:

#!/usr/bin/env python

class Object( dict ):
    def __init__( self, data = None ):
        super( Object, self ).__init__()
        if data:
            self.__update( data, {} )
    
    def __update( self, data, did ):
        dataid = id(data)
        did[ dataid ] = self
        
        for k in data:
            dkid = id(data[k])
            if did.has_key(dkid):
                self[k] = did[dkid]
            elif isinstance( data[k], Object ):
                self[k] = data[k]
            elif isinstance( data[k], dict ):
                obj = Object()
                obj.__update( data[k], did )
                self[k] = obj
                obj = None
            else:
                self[k] = data[k]
                
    def __getattr__( self, key ):
        return self.get( key, None )
    
    def __setattr__( self, key, value ):
        if isinstance(value,dict):
            self[key] = Object( value )
        else:
            self[key] = value
        
    def update( self, *args ):
        for obj in args:
            for k in obj:
                if isinstance(obj[k],dict):
                    self[k] = Object( obj[k] )
                else:
                    self[k] = obj[k]
        return self
    
    def merge( self, *args ):
        for obj in args:
            for k in obj:
                if self.has_key(k):
                    if isinstance(self[k],list) and isinstance(obj[k],list):
                        self[k] += obj[k]
                    elif isinstance(self[k],list):
                        self[k].append( obj[k] )
                    elif isinstance(obj[k],list):
                        self[k] = [self[k]] + obj[k]
                    elif isinstance(self[k],Object) and isinstance(obj[k],Object):
                        self[k].merge( obj[k] )
                    elif isinstance(self[k],Object) and isinstance(obj[k],dict):
                        self[k].merge( obj[k] )
                    else:
                        self[k] = [ self[k], obj[k] ]
                else:
                    if isinstance(obj[k],dict):
                        self[k] = Object( obj[k] )
                    else:
                        self[k] = obj[k]
        return self
    
def test01():
    class UObject( Object ):
        pass
    obj = Object({1:2})
    d = {}
    d.update({
        "a": 1,
        "b": {
            "c": 2,
            "d": [ 3, 4, 5 ],
            "e": [ [6,7], (8,9) ],
            "self": d,
        },
        1: 10,
        "1": 11,
        "obj": obj,
    })
    x = UObject(d)
    
    
    assert x.a == x["a"] == 1
    assert x.b.c == x["b"]["c"] == 2
    assert x.b.d[0] == 3
    assert x.b.d[1] == 4
    assert x.b.e[0][0] == 6
    assert x.b.e[1][0] == 8
    assert x[1] == 10
    assert x["1"] == 11
    assert x[1] != x["1"]
    assert id(x) == id(x.b.self.b.self) == id(x.b.self)
    assert x.b.self.a == x.b.self.b.self.a == 1
    
    x.x = 12
    assert x.x == x["x"] == 12
    x.y = {"a":13,"b":[14,15]}
    assert x.y.a == 13
    assert x.y.b[0] == 14

def test02():
    x = Object({
        "a": {
            "b": 1,
            "c": [ 2, 3 ]
        },
        1: 6,
        2: [ 8, 9 ],
        3: 11,
    })
    y = Object({
        "a": {
            "b": 4,
            "c": [ 5 ]
        },
        1: 7,
        2: 10,
        3: [ 12 , 13 ],
    })
    z = {
        3: 14,
        2: 15,
        "a": {
            "b": 16,
            "c": 17,
        }
    }
    x.merge( y, z )
    assert 2 in x.a.c
    assert 3 in x.a.c
    assert 5 in x.a.c
    assert 1 in x.a.b
    assert 4 in x.a.b
    assert 8 in x[2]
    assert 9 in x[2]
    assert 10 in x[2]
    assert 11 in x[3]
    assert 12 in x[3]
    assert 13 in x[3]
    assert 14 in x[3]
    assert 15 in x[2]
    assert 16 in x.a.b
    assert 17 in x.a.c
    
if __name__ == '__main__':
    test01()
    test02()

Comments

3

What about just assigning your dict to the __dict__ of an empty object?

class Object:
    """If your dict is "flat", this is a simple way to create an object from a dict

    >>> obj = Object()
    >>> obj.__dict__ = d
    >>> d.a
    1
    """
    pass

Of course this fails on your nested dict example unless you walk the dict recursively:

# For a nested dict, you need to recursively update __dict__
def dict2obj(d):
    """Convert a dict to an object

    >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
    >>> obj = dict2obj(d)
    >>> obj.b.c
    2
    >>> obj.d
    ["hi", {'foo': "bar"}]
    """
    try:
        d = dict(d)
    except (TypeError, ValueError):
        return d
    obj = Object()
    for k, v in d.iteritems():
        obj.__dict__[k] = dict2obj(v)
    return obj

And your example list element was probably meant to be a Mapping, a list of (key, value) pairs like this:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': [("hi", {'foo': "bar"})]}
>>> obj = dict2obj(d)
>>> obj.d.hi.foo
"bar"

Comments

3

Here is another way to implement SilentGhost's original suggestion:

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)
  else:
    return d

Comments

3

I stumbled upon the case I needed to recursively convert a list of dicts to list of objects, so based on Rob'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.

Comments

2

Here's another implementation:

class DictObj(object):
    def __init__(self, d):
        self.__dict__ = d

def dict_to_obj(d):
    if isinstance(d, (list, tuple)): return map(dict_to_obj, d)
    elif not isinstance(d, dict): return d
    return DictObj(dict((k, dict_to_obj(v)) for (k,v) in d.iteritems()))

[Edit] Missed bit about also handling dicts within lists, not just other dicts. Added fix.

3 Comments

Note that setting dict to the source dictionary means that any changes to attributes on the resulting object will also affect the dictionary that created the object, and vice-versa. This may lead to unexpected results if the dictionary is used for something other than creating the object.
@Mark: Actually, a new dictionary is being passed to DictObj every time, rather than just passing through the same dict object, so this won't actually occur. It's neccessary to do this, as I need to translate the values within a dictionary as well, so it would be impossible to pass through the original dict object without mutating it myself.
There are quite a few answers to this, the accepted answer didn't look like it would be recursive or handle lists. I read through them all and this one looked the simplest at first sight, I tested and it worked. Great answer.
2
class Struct(dict):
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        self[name] = value

    def copy(self):
        return Struct(dict.copy(self))

Usage:

points = Struct(x=1, y=2)
# Changing
points['x'] = 2
points.y = 1
# Accessing
points['x'], points.x, points.get('x') # 2 2 2
points['y'], points.y, points.get('y') # 1 1 1
# Accessing inexistent keys/attrs 
points['z'] # KeyError: z
points.z # AttributeError: z
# Copying
points_copy = points.copy()
points.x = 2
points_copy.x # 1

1 Comment

Could you edit the post by elaborating on the code?
2

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

1 Comment

This doesn't answer the question. The author want to convert nested dict. Try {'a': 1, 'b': {'b1': 2}} and you will get AttributeError
2

I think a dict consists of number, string and dict is enough most time. So I ignore the situation that tuples, lists and other types not appearing in the final dimension of a dict.

Considering inheritance, combined with recursion, it solves the print problem conveniently and also provides two ways to query a data,one way to edit a data.

See the example below, a dict that describes some information about students:

group=["class1","class2","class3","class4",]
rank=["rank1","rank2","rank3","rank4","rank5",]
data=["name","sex","height","weight","score"]

#build a dict based on the lists above
student_dic=dict([(g,dict([(r,dict([(d,'') for d in data])) for r in rank ]))for g in group])

#this is the solution
class dic2class(dict):
    def __init__(self, dic):
        for key,val in dic.items():
            self.__dict__[key]=self[key]=dic2class(val) if isinstance(val,dict) else val


student_class=dic2class(student_dic)

#one way to edit:
student_class.class1.rank1['sex']='male'
student_class.class1.rank1['name']='Nan Xiang'

#two ways to query:
print student_class.class1.rank1
print student_class.class1['rank1']
print '-'*50
for rank in student_class.class1:
    print getattr(student_class.class1,rank)

Results:

{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
--------------------------------------------------
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.