Convert nested Python dict to object?

2018-12-31 17:01发布

问题:

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?

回答1:

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


回答2:

class obj(object):
    def __init__(self, d):
        for a, b in d.items():
            if isinstance(b, (list, tuple)):
               setattr(self, a, [obj(x) if isinstance(x, dict) else x for x in b])
            else:
               setattr(self, a, obj(b) if isinstance(b, dict) else b)

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


回答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



回答4:

x = type(\'new_dict\', (object,), d)

then add recursion to this and you\'re done.

edit 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\'


回答5:

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


回答6:

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.iteritems():
      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__.iteritems()))


回答7:

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.



回答8:

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.



回答9:

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\'}}


回答10:

>>> 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\'


回答11:

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.



回答12:

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



回答13:

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.



回答14:

Old Q&A, but I get something more to talk. Seems no one talk about recursive dict. This is my code:

#!/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()


回答15:

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

import json

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

def dict2obj(d):
    return json.loads(json.dumps(d), object_hook=obj)

Example usage:

>>> d = {\'a\': 1, \'b\': {\'c\': 2}, \'d\': [\'hi\', {\'foo\': \'bar\'}]}
>>> o = dict2obj(d)
>>> o.a
1
>>> o.b.c
2
>>> o.d[0]
u\'hi\'
>>> o.d[1].foo
u\'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


回答16:

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.



回答17:

Wanted to upload my version of this little paradigm.

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)

It preserves the attributes for the type that\'s imported into the class. My only concern would be overwriting methods from within the dictionary your parsing. But otherwise seems solid!



回答18:

from mock import Mock
d = {\'a\': 1, \'b\': {\'c\': 2}, \'d\': [\"hi\", {\'foo\': \"bar\"}]}
my_data = Mock(**d)

# We got
# my_data.a == 1


回答19:

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\"


回答20:

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


回答21:

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


回答22:

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\"


回答23:

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.



回答24:

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


回答25:

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\': \'\'}


回答26:

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


回答27:

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


回答28:

Building off my answer to \"python: How to add property to a class dynamically?\":

class data(object):
    def __init__(self,*args,**argd):
        self.__dict__.update(dict(*args,**argd))

def makedata(d):
    d2 = {}
    for n in d:
        d2[n] = trydata(d[n])
    return data(d2)

def trydata(o):
    if isinstance(o,dict):
        return makedata(o)
    elif isinstance(o,list):
        return [trydata(i) for i in o]
    else:
        return o

You call makedata on the dictionary you want converted, or maybe trydata depending on what you expect as input, and it spits out a data object.

Notes:

  • You can add elifs to trydata if you need more functionality.
  • Obviously this won\'t work if you want x.a = {} or similar.
  • If you want a readonly version, use the class data from the original answer.


回答29:

I had some problems with __getattr__ not being called so I constructed a new style class version:

class Struct(object):
    \'\'\'The recursive class for building and representing objects with.\'\'\'
    class NoneStruct(object):
        def __getattribute__(*args):
            return Struct.NoneStruct()

        def __eq__(self, obj):
            return obj == None

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

    def __getattribute__(*args):
        try:
            return object.__getattribute__(*args)
        except:            
            return Struct.NoneStruct()

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

This version also has the addition of NoneStruct that is returned when an attribute is called that is not set. This allows for None testing to see if an attribute is present. Very usefull when the exact dict input is not known (settings etc.).

bla = Struct({\'a\':{\'b\':1}})
print(bla.a.b)
>> 1
print(bla.a.c == None)
>> True


回答30:

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