ChatGPT解决这个技术问题 Extra ChatGPT

将嵌套的 Python dict 转换为对象?

我正在寻找一种优雅的方式来使用带有一些嵌套字典和列表的字典(即 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

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

我最近试图做类似的事情,但是一个重复出现的字典键(“from” - 这是一个 Python 关键字)阻止我完成它。因为一旦您尝试使用“x.from”访问该属性,您就会收到语法错误。
这确实是个问题,但我可以放弃“from”,以使访问大型 dict 结构时更轻松:) 输入 x['a']['d'][1]['foo'] 真的很烦人,所以 xad [1].foo 规则。如果您需要 from,您可以通过 getattr(x, 'from') 访问它或使用 _from 作为属性。
from_ 而不是根据 PEP 8_from
这些“解决方案”中的大多数似乎都不起作用(即使是接受的解决方案,也不允许嵌套 d1.b.c),我认为很明显您应该使用库中的某些东西,例如 namedtuple from collections,作为 this answer建议,...
Bunch - 像对象一样使用 Python dict:thechangelog.com/bunch-lets-use-python-dict-like-object

m
martineau

更新:在 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

同样在这里 - 这对于从 MongoDB 等面向文档的数据库中重建 Python 对象特别有用。
要获得更漂亮的打印,请添加: def repr__(self): return '<%s>' % str('\n '.join('%s : %s' % (k, repr(v)) for (k, v ) 在 self.__dict.iteritems()))
这适用于嵌套字典吗?和包含对象和/或列表等的字典。有什么问题吗?
@Sam S:它不会从嵌套字典创建嵌套的 Struct ,但通常值的类型可以是任何东西。键被限制为适用于对象插槽
-1 因为 a) 它不适用于问题明确要求的嵌套 dicts,并且 b) 目前在 argparse.Namespace 的标准库中存在相同的功能(它还定义了 __eq____ne__、{ 4})。
k
kontinuity

令人惊讶的是,没有人提到 Bunch。该库专门用于提供对 dict 对象的属性样式访问,并且完全符合 OP 的要求。一个示范:

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

https://github.com/Infinidat/munch 提供 Python 3 库 - 归功于 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'

并且有一个名为 Munch 的 Bunch 的 python 3 兼容(似乎是 2.6-3.4)分支:github.com/Infinidat/munch
Bunch 是所有这些中最好的解决方案,因为它很好地支持了多种类型的序列化,并且得到了维护。太好了谢谢。我希望 python3 版本被命名为相同的东西,因为为什么不呢。
所以只是一个预警,Bunch 和 Attrdict 也非常缓慢。当他们在我们的应用程序中看到频繁使用时,他们分别消耗了我请求时间的大约 1/3 和 1/2。绝对不容忽视。更多地谈论它stackoverflow.com/a/31569634/364604
虽然这确实允许对 dicts 进行类似对象的访问,但它是通过 getattr 实现的。这使得 IPython 等智能 REPL 中的自省变得困难。
好奇这与 JSONpath github.com/kennknowles/python-jsonpath-rw 相比如何
f
funnydman
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'

好的!不过,我会用 .iteritems() 替换 .items(),以减少内存占用。
如果不是 OP 要求,这不是问题 - 但请注意,这不会递归处理列表中的列表中的对象。
我意识到这是一个旧答案,但现在最好使用 abstract base class 而不是那条丑陋的行 if isinstance(b, (list, tuple)):
提示:让 obj 类继承自 argparse.Namespace 以获得其他功能,例如可读字符串表示。
根据用例,通常我们会检查它不是字符串类型,而是序列类型
S
SilentGhost
x = type('new_dict', (object,), d)

然后对此添加递归,您就完成了。

编辑这是我将如何实现它:

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

为什么要创建类型对象而不实例化它们?那不是更合乎逻辑吗?我的意思是,为什么不做 top_instance = top() 并返回你返回 top 的地方?
很适合“叶子”数据,但示例很方便地省略了“树枝”,例如 xx.b,它们返回丑陋的 <class '__main__.new'>
D
David Golembiowski
# 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

可以与任何深度的任何序列/字典/值结构一起使用。


这应该是答案。它适用于嵌套。您也可以将其用作 json.load() 的 object_hook。
类似于 SilentGhost 2009 年的功能性答案——叶节点数据是可访问的,但父/树枝显示为对象引用。要打印漂亮,def __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
Python 3.x 用户:它只是第 4 行中的 .items() 而不是 .iteritems()。(该函数已重命名,但 essentialy the same
非常适合嵌套对象,谢谢!作为评论新手,对于python3 iteritems() 必须更改为 items()
python 3 的漂亮打印等效项是:def __repr__(self): return ("{ " + str(", ".join([f"'{k}': {v}" for k, v in [(k, repr(v)) for (k, v) in self.__dict__.items()]])) + " }")
w
wim

有一个名为 namedtuple 的集合助手可以为您执行此操作:

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

这并不能回答嵌套字典的递归问题。
你不能修改命名元组。
能否保证keys()和values()返回的列表顺序是按顺序匹配的?我的意思是,如果它是 OrderedDict,是的。但是一个标准的字典?我知道 CPython 3.6+ 和 PyPy“紧凑”字典是有序的,但引用文档:“这个新实现的顺序保留方面被认为是实现细节,不应依赖”
这也有效d_named = namedtuple('Struct', d)(**d)
C
Community

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

import json
from collections import namedtuple

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

另见How to convert JSON data into a Python object


如果您有一个巨大的 JSON 对象,它似乎比常规 .loads 慢得多
这就是答案。谢了哥们。
v
valex

以我认为前面例子中最好的方面为例,这就是我想出的:

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()))

请注意,构造函数可以缩短为: def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) 这也删除了对 Struct 的显式调用
我宁愿不反对我自己的答案,但回顾这一点,我注意到它不会递归到序列类型。 xd[1].foo 在这种情况下失败。
isinstance(v, dict) 检查比 isinstance(v, collections.Mapping) 更好,因此它可以处理未来类似 dict 的事情
V
Vanni Totaro

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

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)

示例用法:

>>> 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 一样不是严格只读的,即您可以更改值 - 而不是结构:

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

迄今为止最好的答案
我喜欢为嵌套元素使用 json 加载机制的想法。但是我们已经有了一个字典,我不喜欢需要从中创建一个字符串来将其映射到一个对象的事实。我宁愿有一个直接从字典创建对象的解决方案。
需要先转换为字符串,但非常通用,因为可以通过 json.JSONEncoderobject_hook 扩展它。
C
Community

我最终尝试了 AttrDictBunch 库,发现它们对于我的使用来说太慢了。在我和一个朋友研究之后,我们发现编写这些库的主要方法导致库通过嵌套对象积极递归并在整个过程中复制字典对象。考虑到这一点,我们进行了两项关键更改。 1)我们使属性延迟加载 2)我们不是创建字典对象的副本,而是创建轻量级代理对象的副本。这是最终的实现。使用此代码的性能提升令人难以置信。当使用 AttrDict 或 Bunch 时,这两个库分别消耗了我请求时间的 1/2 和 1/3(什么!?)。这段代码将该时间减少到几乎没有(在 0.5 毫秒的范围内)。这当然取决于您的需求,但如果您在代码中大量使用此功能,那么一定要使用像这样简单的东西。

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-merickel 的原始实现 here

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


t
the Tin Man

如果您想将 dict 键作为对象(或作为困难键的 dict)访问,请递归执行,并且还能够更新原始 dict,您可以这样做:

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)

示例用法:

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

A
Anon
>>> 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'

R
Reed Sandberg

在 2021 年,使用 pydantic BaseModel - 将嵌套的 dicts 和嵌套的 json 对象转换为 python 对象,反之亦然:

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'}])

对象听写

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

对象转 JSON

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

口述反对

>>> 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 到对象

>>> 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')])

这应该是公认的答案。太感谢了!我花了一个小时寻找一个可以很好地与类型注释类配合使用的解决方案。
如果您知道 dict 的结构,这可以工作,但您无法动态创建 pydantic 模型。为此,您需要调用 create_model(__model_name="ModelName", **some_dict)。但是,这仅适用于顶层,因此您必须使其递归地生成 pydantic 模型。但是创建 pydantic 模型比其他模块(例如 named_tuples 或 dataclasses)慢。
与 munch 相比,与 namedtuples 相比,pydantic 的开销是多少?
A
Alex Rodrigues

x.__dict__.update(d) 应该没问题。


感谢您的回答,但是 x 是什么?字典还是标准对象?你能给我一个提示吗?
x 是你的对象。每个对象都有一个字典。通过更新对象的字典,您实际上是在更新其中的键变量。
粗体字是 _ _ dict _ _
这不会处理嵌套字典。
并非每个对象都有 __dict__:尝试 object().__dict__,您会得到 AttributeError: 'object' object has no attribute '__dict__'
V
VengaVenga

通常,您希望将 dict 层次结构镜像到您的对象中,而不是通常处于最低级别的列表或元组。所以这就是我这样做的方式:

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)

所以我们这样做:

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

并得到:

x.b.b1.x 1

x.c ['嗨','酒吧']


A
Aidan Haddon-Wright

我知道这里已经有很多答案了,我迟到了,但是这种方法将递归地“就地”将字典转换为类似对象的结构......适用于 3.xx

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

您是什么意思“类对象结构”?
由于鸭子类型原理,所以类似于对象。我的意思是,如果它是一个类的实例,您可以访问它的属性。但它不是那个意义上的对象,它是一个命名的元组。
A
Aaron Digulla

这应该让你开始:

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__ 以包装字典。


要使其适用于列表,请使用 Anon 答案中的 if isinstance(d, list) 子句。
f
forward
from mock import Mock
d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
my_data = Mock(**d)

# We got
# my_data.a == 1

ModuleNotFoundError: No module named 'mock'
n
naren

这也很好用

class DObj(object):
    pass

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

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

感谢你的回答。第 4 行有错字,应该是:dobj = DObj()。当尝试在另一个环境中运行此代码时,我收到此错误:TypeError: 'DObj' object is not subscriptable,您知道解决此问题的方法吗? :)
NVM,我找到原因了,我的字典不平。
V
VisioN

最简单的方法是使用 collections.namedtuple

我发现以下 4-liner 最漂亮,它支持嵌套字典:

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())
    )

输出看起来也不错:

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

D
Dawie Strauss

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

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

print x.from

给出这个错误:

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

因为“from”是 Python 关键字,所以您不能允许某些字典键。

现在我的解决方案允许通过直接使用它们的名称来访问字典项。但它也允许您使用“字典语义”。这是带有示例用法的代码:

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"

类结构:def init__(self, **entries): self.__dict.update(entries)
t
truease.com

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

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

E
Erik

想上传我的这个小范式的版本。

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)

它保留了导入到类中的类型的属性。我唯一担心的是从您的解析字典中覆盖方法。但在其他方面似乎很可靠!


虽然是个好主意,但这似乎不适用于 OP 的示例,实际上它似乎修改了传入的字典!事实上,df.a 甚至不起作用。
r
rob

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

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

N
NiKo

我偶然发现了我需要将字典列表递归转换为对象列表的情况,所以根据罗伯托的片段在这里为我做了什么:

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

请注意,出于显而易见的原因,任何元组都将转换为其等效列表。

希望这对某人有帮助,就像你所有的答案对我一样,伙计们。


h
hobs

将您的 dict 分配给一个空对象的 __dict__ 怎么样?

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:

# 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

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

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

B
Brian

这是另一个实现:

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()))

[编辑] 错过了处理列表中的字典,而不仅仅是其他字典。添加了修复。


请注意,将 dict 设置为源字典意味着对结果对象上的属性的任何更改也会影响创建对象的字典,反之亦然。如果字典用于创建对象以外的其他用途,这可能会导致意外结果。
@Mark:实际上,每次都将一个新字典传递给 DictObj ,而不是仅仅传递同一个 dict 对象,所以这实际上不会发生。这样做是必要的,因为我还需要翻译字典中的值,所以如果不自己改变它,就不可能通过原始 dict 对象。
对此有很多答案,接受的答案看起来不像是递归或处理列表。我通读了所有这些,乍一看,这个看起来最简单,我进行了测试并且它有效。很好的答案。
u
user222758
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))

用法:

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

o
onno

这个怎么样:

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

然后可以这样使用:

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

这不能回答问题。作者想转换嵌套的dict。试试 {'a': 1, 'b': {'b1': 2}} 你会得到 AttributeError
t
the Tin Man

我认为字典由数字、字符串和字典组成,大多数时候就足够了。所以我忽略了元组、列表和其他类型没有出现在字典的最终维度中的情况。

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

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

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)

结果:

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