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

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


当前回答

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

其他回答

这可以让你开始:

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

通过PyPI包attrdict可以很容易地得到问题的精确解。关于这个包的一个有趣的事实是,字典可以作为键或属性访问。这就是解决方案

from attrdict import AttrDict

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

x = AttrDict(d)

print(x.a, x['a'])
print(x.b.c, x['b']['c'])
print(x.d[1].foo, x['d'][1]['foo'])

输出如下(显然没有错误)-

1 1
2 2
bar bar

注意:它于2019年2月2日首次发布,这意味着在提出这个问题时,这个第三方pypi包还不存在。但是如果有人现在想通过键或属性访问dict值,这个包肯定可以像魔术一样帮助你,只需要一行代码。

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

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

注意,由于显而易见的原因,任何元组都将被转换为与其列表相当的元素。

希望这能像你们的答案对我一样帮助到别人。

我的字典是这样的:

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

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

我尝试了上述所有建议的解决方案。有些不能很好地处理嵌套字典。其他的则不容易打印对象的详细信息。

只有Dawie Strauss的dic2obj (dict)解决方案最有效。

我已经增强了一点,当找不到钥匙时处理:

# 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

然后,我用:

# 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

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