我正在寻找一种优雅的方式来获得数据使用属性访问字典与一些嵌套的字典和列表(即javascript风格的对象语法)。
例如:
>>> 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
我想,如果没有递归,这是不可能的,但是有什么更好的方法来获得字典的对象样式呢?
我正在寻找一种优雅的方式来获得数据使用属性访问字典与一些嵌套的字典和列表(即javascript风格的对象语法)。
例如:
>>> 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
我想,如果没有递归,这是不可能的,但是有什么更好的方法来获得字典的对象样式呢?
当前回答
为dict寻找一个简单的包装器类,支持属性样式的键访问/赋值(点表示法),我对现有选项不满意,原因如下。
数据类、pydantic等都很棒,但需要对内容进行静态定义。此外,它们不能在依赖dict的代码中替换dict,因为它们不共享相同的方法,并且不支持__getitem__()语法。
因此,我开发了MetaDict。它的行为完全类似于dict,但支持点表示法和IDE自动补全(如果对象被加载到RAM中),而没有其他解决方案的缺点和潜在的名称空间冲突。所有功能和使用示例都可以在GitHub上找到(见上面的链接)。
完全披露:我是MetaDict的作者。
我在尝试其他解决方案时遇到的缺点/限制:
Addict No key autocompletion in IDE Nested key assignment cannot be turned off Newly assigned dict objects are not converted to support attribute-style key access Shadows inbuilt type Dict Prodict No key autocompletion in IDE without defining a static schema (similar to dataclass) No recursive conversion of dict objects when embedded in list or other inbuilt iterables AttrDict No key autocompletion in IDE Converts list objects to tuple behind the scenes Munch Inbuilt methods like items(), update(), etc. can be overwritten with obj.items = [1, 2, 3] No recursive conversion of dict objects when embedded in list or other inbuilt iterables EasyDict Only strings are valid keys, but dict accepts all hashable objects as keys Inbuilt methods like items(), update(), etc. can be overwritten with obj.items = [1, 2, 3] Inbuilt methods don't behave as expected: obj.pop('unknown_key', None) raises an AttributeError
注意:我在这个stackoverflow中写了一个类似的答案,这是相关的。
其他回答
更新:在Python 2.6及以上版本中,考虑namedtuple数据结构是否适合您的需求:
>>> 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']
替代方案(原答案内容)为:
class Struct:
def __init__(self, **entries):
self.__dict__.update(entries)
然后,你可以使用:
>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2
这可以让你开始:
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
它还不适用于列表。你必须将列表包装在UserList中,并重载__getitem__来包装字典。
from mock import Mock
d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
my_data = Mock(**d)
# We got
# my_data.a == 1
老式问答,但我有更多的话题要谈。似乎没有人谈论递归字典。这是我的代码:
#!/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()
将字典转换为对象
from types import SimpleNamespace
def dict2obj(data):
"""将字典对象转换为可访问的对象属性"""
if not isinstance(data, dict):
raise ValueError('data must be dict object.')
def _d2o(d):
_d = {}
for key, item in d.items():
if isinstance(item, dict):
_d[key] = _d2o(item)
else:
_d[key] = item
return SimpleNamespace(**_d)
return _d2o(data)
参考答案