我正在寻找一种优雅的方式来获得数据使用属性访问字典与一些嵌套的字典和列表(即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

我想,如果没有递归,这是不可能的,但是有什么更好的方法来获得字典的对象样式呢?


当前回答

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'

其他回答

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

下面是一个使用namedtuple的嵌套就绪版本:

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

=>

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

我不满意那些被标记和点赞的答案,所以这里有一个简单而通用的解决方案,用于将json风格的嵌套数据结构(由字典和列表组成)转换为普通对象的层次结构:

# tested in: Python 3.8
from collections import abc
from typings import Any, Iterable, Mapping, Union

class DataObject:
    def __repr__(self):
        return str({k: v for k, v in vars(self).items()})

def data_to_object(data: Union[Mapping[str, Any], Iterable]) -> object:
    """
    Example
    -------
    >>> data = {
    ...     "name": "Bob Howard",
    ...     "positions": [{"department": "ER", "manager_id": 13}],
    ... }
    ... data_to_object(data).positions[0].manager_id
    13
    """
    if isinstance(data, abc.Mapping):
        r = DataObject()
        for k, v in data.items():
            if type(v) is dict or type(v) is list:
                setattr(r, k, data_to_object(v))
            else:
                setattr(r, k, v)
        return r
    elif isinstance(data, abc.Iterable):
        return [data_to_object(e) for e in data]
    else:
        return data

如果只是将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

你的例子列表元素可能是一个映射,一个(键,值)对的列表,像这样:

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

在2021年,使用pydantic BaseModel -将嵌套字典和嵌套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'}])

对象to dict

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