是否将嵌套的python dict转换为对象?

Convert nested Python dict to object?

我正在寻找一种优雅的方法来获取数据,使用一些嵌套的dict和列表(即javascript样式的对象语法)对dict进行属性访问。

例如:

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

应该可以这样访问:

1
2
3
4
5
6
7
>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
bar

我认为,如果没有递归,这是不可能的,但是有什么好方法可以获得dict的对象样式呢?


更新:在python 2.6及更高版本中,考虑namedtuple数据结构是否适合您的需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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']

备选方案(原始答案内容)是:

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

然后,您可以使用:

1
2
3
4
5
6
7
8
>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2


1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'


令人惊讶的是,没有人提到过这群人。此库专门用于提供对dict对象的属性样式访问,并完全按照OP的要求执行。演示:

1
2
3
4
5
6
7
8
9
>>> 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'

python 3库可在https://github.com/infinidat/munch上找到-信贷转到codyzu


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

然后向这个添加递归,就完成了。

编辑这是我实现它的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> 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'


有一个收款助手叫namedtuple,可以为您做到这一点:

1
2
3
4
5
6
7
8
9
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


根据我的感受,这是前面例子中最好的方面,下面是我的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()))


1
2
3
4
5
6
7
8
9
10
11
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

可用于任何深度的任何序列/dict/值结构。


如果您的dict来自json.loads(),您可以在一行中将其转换为对象(而不是dict):

1
2
3
4
import json
from collections import namedtuple

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

另请参见如何将JSON数据转换为Python对象。


如果要将dict键作为对象(或作为困难键的dict)访问,请以递归方式进行,并且还可以更新原始dict,可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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)

示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'}}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> 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'

我最终尝试了attrdict和bundle库,发现它们对于我的使用来说太慢了。在我和一个朋友研究了它之后,我们发现编写这些库的主要方法导致库在一个嵌套的对象中急剧递归,并在整个过程中复制字典对象。考虑到这一点,我们做了两个关键的更改。1)我们将属性设置为lazy-loaded 2)而不是创建dictionary对象的副本,而是创建一个轻量级代理对象的副本。这是最终的实现。使用此代码的性能提高是令人难以置信的。当使用attrdict或bunch时,这两个库单独占用了我请求时间的1/2和1/3(什么!?)这段代码将时间缩短到几乎为零(在0.5毫秒的范围内)。当然,这取决于您的需求,但是如果您在代码中使用了这个功能,那么一定要使用类似这样的简单功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

请参阅https://stackoverflow.com/users/704327/michael-mericakel的原始实现。

另一件需要注意的是,这个实现非常简单,并且没有实现您可能需要的所有方法。您需要根据需要在dictproxy或listproxy对象上编写这些内容。


x.__dict__.update(d)应该做得很好。


这应该让你开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

它还不适用于列表。您必须将列表包装在一个用户列表中,并重载__getitem__来包装dict。


我知道这里已经有很多答案了,我迟到了,但是这个方法将递归地"就地"将字典转换成类似对象的结构…作品在3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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


我来解释一下我一段时间前几乎用过的解决方案。但首先,我没有这样做的原因是因为以下代码:

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

print x.from

出现此错误:

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

因为"from"是一个python关键字,所以您不能允许某些字典键。

现在,我的解决方案允许直接使用字典项的名称访问它们。但它也允许您使用"字典语义"。下面是示例用法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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"


您可以使用自定义对象挂钩来利用标准库的json模块:

1
2
3
4
5
6
7
8
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)

示例用法:

1
2
3
4
5
6
7
8
9
10
>>> 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'

而且它不是严格的只读的,因为它与namedtuple一样,也就是说,您可以更改值,而不是结构:

1
2
3
>>> o.b.c = 3
>>> o.b.c
3

老问答,但我有更多的话要说。似乎没人谈论递归dict。这是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/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()

想上传我的版本。

1
2
3
4
5
6
7
8
9
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)

它保留导入到类中的类型的属性。我唯一关心的是从字典中重写您解析的方法。但其他方面似乎是坚实的!


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

# We got
# my_data.a == 1


把你的dict分配给一个空物体的__dict__怎么样?

1
2
3
4
5
6
7
8
9
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

当然,这在嵌套的dict示例中失败,除非递归地遍历dict:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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

您的示例list元素可能是一个Mapping,一个(key,value)对的列表,如下所示:

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

我偶然发现了一个需要递归地将dict列表转换为对象列表的情况,因此基于罗伯托的片段,这里为我做了什么工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

请注意,由于明显的原因,任何元组都将被转换为等价的列表。

希望这能像你们对我的回答一样帮助别人,伙计们。


这也很有效

1
2
3
4
5
6
7
8
9
10
class DObj(object):
    pass

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

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


下面是另一个实现:

1
2
3
4
5
6
7
8
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()))

[编辑]关于处理列表中的听写而不仅仅是其他听写的遗漏。添加修复。


我认为听写大多数时候都是由数字、字符串和听写组成的。所以我忽略了tuples、list和其他类型不出现在dict的最终维中的情况。

考虑到继承,结合递归,它方便地解决了打印问题,并且提供了两种查询数据的方法,一种是编辑数据的方法。

请参阅下面的示例,描述有关学生的一些信息的dict:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)

结果:

1
2
3
4
5
6
7
8
{'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': ''}


这个怎么样?

1
2
from functools import partial
d2o=partial(type,"d2o", ())

然后可以这样使用:

1
2
3
4
5
>>> o=d2o({"a" : 5,"b" : 3})
>>> print o.a
5
>>> print o.b
3

以下是实现SilentGhost原始建议的另一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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


基于我对"python:如何动态地向类添加属性"的回答?:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

您可以在要转换的字典上调用makedata,或者根据您期望的输入调用trydata,然后它会吐出一个数据对象。

笔记:

  • 如果需要更多的功能,可以将elifs添加到trydata中。
  • 很明显,如果你想要x.a = {}或类似的产品,这是行不通的。
  • 如果需要只读版本,请使用原始答案中的类数据。

1
2
3
4
5
6
7
8
9
10
11
12
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))

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

下面是一个名为duple的嵌套就绪版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import namedtuple

class Struct(object):
    def __new__(cls, data):
        if isinstance(data, dict):
            return namedtuple(
                'Struct', data.iterkeys()
            )(
                *(Struct(val) for val in data.values())
            )
        elif isinstance(data, (tuple, list, set, frozenset)):
            return type(data)(Struct(_) for _ in data)
        else:
            return data

= >

1
2
3
4
5
6
7
8
>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo':"bar"}]}
>>> s = Struct(d)
>>> s.d
['hi', Struct(foo='bar')]
>>> s.d[0]
'hi'
>>> s.d[1].foo
'bar'

我的字典是这样的格式:

1
2
3
4
5
6
7
8
9
10
11
addr_bk = {
    'person': [
        {'name': 'Andrew', 'id': 123, 'email': '[email protected]',
         'phone': [{'type': 2, 'number': '633311122'},
                   {'type': 0, 'number': '97788665'}]
        },
        {'name': 'Tom', 'id': 456,
         'phone': [{'type': 0, 'number': '91122334'}]},
        {'name': 'Jack', 'id': 7788, 'email': '[email protected]'}
    ]
}

可以看到,我有嵌套字典和字典列表。这是因为addr_bk是从使用lwpb.codec转换为python dict的协议缓冲区数据中解码出来的。有可选字段(例如,email=>其中键可能不可用)和重复字段(例如,phone=>转换为dict列表)。

我尝试了上述所有的解决方案。有些词典处理嵌套词典不好。其他人无法轻松打印对象详细信息。

只有DawieStrauss的dict2obj(dict)解决方案最有效。

当找不到钥匙时,我对它进行了一些改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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

然后,我测试了它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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

这是将字典列表转换为对象的另一种可选方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def dict2object(in_dict):
    class Struct(object):
        def __init__(self, in_dict):
            for key, value in in_dict.items():
                if isinstance(value, (list, tuple)):
                    setattr(
                        self, key,
                        [Struct(sub_dict) if isinstance(sub_dict, dict)
                         else sub_dict for sub_dict in value])
                else:
                    setattr(
                        self, key,
                        Struct(value) if isinstance(value, dict)
                        else value)
    return [Struct(sub_dict) for sub_dict in in_dict] \
        if isinstance(in_dict, list) else Struct(in_dict)

这个小类从不给我任何问题,只需扩展它并使用copy()方法:

1
2
3
4
5
6
7
8
9
  import simplejson as json

  class BlindCopy(object):

    def copy(self, json_str):
        dic = json.loads(json_str)
        for k, v in dic.iteritems():
            if hasattr(self, k):
                setattr(self, k, v);

我对没有被调用的__getattr__有一些问题,所以我构建了一个新的样式类版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()))

此版本还添加了NoneStruct,当调用未设置的属性时返回。这允许无测试查看是否存在属性。当不知道准确的听写输入(设置等)时非常有用。

1
2
3
4
5
bla = Struct({'a':{'b':1}})
print(bla.a.b)
>> 1
print(bla.a.c == None)
>> True