嵌套字典的类对象属性访问

Posted

技术标签:

【中文标题】嵌套字典的类对象属性访问【英文标题】:Object-like attribute access for nested dictionary 【发布时间】:2016-10-28 07:28:51 【问题描述】:

我正在使用一个返回嵌套字典的包。 当其他一切都在对象语法中时,使用字典语法在我的类方法中访问这个返回对象感觉很尴尬。 搜索将我带到了 bundle / neobunch 包,这似乎实现了我所追求的。我也看到过命名元组的建议,但这些并不容易支持嵌套属性,并且大多数解决方案都依赖于在命名元组中使用字典进行嵌套。

实现这一点的更自然的方法是什么?

data = 'a': 'aval', 'b': 'b1':'b2a':'b3a':'b3aval','b3b':'b3bval','b2b':'b2bval' 

print(data['b']['b1']['b2a']['b3b'])  # dictionary access
# print(data.b.b1.b2a.b3b)  # desired access

import neobunch
data1 = neobunch.bunchify(data)
print(data1.b.b1.b2a.b3b)

【问题讨论】:

【参考方案1】:

下面的类可以让你做你想做的事(适用于 Python 2 和 3):

class AttrDict(dict):
    """ Dictionary subclass whose entries can be accessed by attributes (as well
        as normally).

    >>> obj = AttrDict()
    >>> obj['test'] = 'hi'
    >>> print obj.test
    hi
    >>> del obj.test
    >>> obj.test = 'bye'
    >>> print obj['test']
    bye
    >>> print len(obj)
    1
    >>> obj.clear()
    >>> print len(obj)
    0
    """
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

    @classmethod
    def from_nested_dicts(cls, data):
        """ Construct nested AttrDicts from nested dictionaries. """
        if not isinstance(data, dict):
            return data
        else:
            return cls(key: cls.from_nested_dicts(data[key]) for key in data)


if __name__ == '__main__':

    data = 
        "a": "aval",
        "b": 
            "b1": 
                "b2b": "b2bval",
                "b2a": 
                    "b3a": "b3aval",
                    "b3b": "b3bval"
                
            
        
    

    data1 = AttrDict.from_nested_dicts(data)
    print(data1.b.b1.b2a.b3b)  # -> b3bval

【讨论】:

您能否详细说明代码为何/如何工作?谢谢! @BartKleijngeld:你不明白哪些部分? 我不明白字典键如何以某种方式成为data1 对象的属性。我觉得我错过了一些非常简单的东西,但如果你能向我解释那部分,我将不胜感激:)。 @BartKleijngeld:当然……这是一只相当奇怪的野兽。 AttrDict 是一个字典子类,它使用自身 作为类'__dict__。后者是通常存储实例属性的地方,通常使用.(点)表示法引用,例如obj.attr,但由于它是一个字典,您仍然可以使用常规[] 表示法来访问它们,例如@987654329 @。这是从javascript借来的想法。 ActiveState 上有一个不同的配方,我在其他几个地方也看到过。【参考方案2】:

在@martineau's 出色答案的基础上,您可以使 AttrDict 类在嵌套字典上工作,而无需显式调用 from_nested_dict() 函数:

class AttrDict(dict):
""" Dictionary subclass whose entries can be accessed by attributes
    (as well as normally).
"""
def __init__(self, *args, **kwargs):
    def from_nested_dict(data):
        """ Construct nested AttrDicts from nested dictionaries. """
        if not isinstance(data, dict):
            return data
        else:
            return AttrDict(key: from_nested_dict(data[key])
                                for key in data)

    super(AttrDict, self).__init__(*args, **kwargs)
    self.__dict__ = self

    for key in self.keys():
        self[key] = from_nested_dict(self[key])

【讨论】:

一个绝妙的补充! @saravana-kumar 这太棒了。如果密钥不存在,需要进行哪些修改才能返回 None?覆盖 getitem 方法仅适用于 d[key] 表示法,而不适用于 d.key。谢谢 @saravana-kumar 看起来像简单地覆盖 getattr 以返回 None 效果很好,因为只有在“通常的地方”中找不到属性时才会调用该方法 @saravana-kumar 不幸的是,仅此一项不适用于嵌套字典。首先调用__getattribute__,如果未找到属性,则调用__getattr__。但是对于嵌套的字典,有一个__getattr__ 调用链。因此,从 __getattr__ 返回 None 会尝试在 NoneType 对象上返回 __getattr__。不确定您是否知道一个好的解决方案。【参考方案3】:

试试Dotsi 或EasyDict。它们都支持嵌套字典的点表示法。

>>> import dotsi
>>> data = dotsi.fy('a': 'aval', 'b': 'b1':'b2a':'b3a':'b3aval','b3b':'b3bval','b2b':'b2bval' )
>>> print(data.b.b1.b2a.b3b)
b3bval
>>> 

除了dicts-within-dicts,Dotsi还支持dicts-within-lists-within-dicts。 注意:我是 Dotsi 的作者。

【讨论】:

【参考方案4】:

json.loads 有一个有趣的参数,称为 object_hook,如果所有字典值都是 JSON 可序列化的,即可以使用该参数,

import json
from types import SimpleNamespace

data = 'a': 'aval', 'b': 'b1':'b2a':'b3a':'b3aval','b3b':'b3bval','b2b':'b2bval'
data1= json.loads(
    json.dumps(data), object_hook=lambda d: SimpleNamespace(**d)
)
print(data1.b.b1.b2a.b3b)  # -> b3bval

如果 Guido 正在监听,我认为 SimpleNamespace 应该采用 recursive 参数,这样您就可以使用 data1 = SimpleNamespace(recursive=True, **data)

【讨论】:

这可能有点低效,但到目前为止它是我能找到的最简单的解决方案,不依赖于第三方包。【参考方案5】:

可以使用建立在基本对象上的简单类:

class afoo1(object):
    def __init__(self, kwargs):
        for name in kwargs:
            val = kwargs[name]
            if isinstance(val, dict):
                val = afoo1(val)
            setattr(self,name,val)

我借用了 argparse.Namespace 定义,经过调整以允许嵌套。

它会被用作

In [172]: dd='a':'aval','b':'b1':'bval'

In [173]: f=afoo1(dd)

In [174]: f
Out[174]: <__main__.afoo1 at 0xb3808ccc>

In [175]: f.a
Out[175]: 'aval'

In [176]: f.b
Out[176]: <__main__.afoo1 at 0xb380802c>

In [177]: f.b.b1
Out[177]: 'bval'

它也可以用**kwargs(连同*args)定义。 __repr__ 定义也可能很好。

与其他简单对象一样,可以添加属性,例如f.c = f(递归定义)。 vars(f) 返回一个字典,虽然它不做任何递归转换)。

【讨论】:

虽然从功能的角度来看你的类没有什么问题,但它可以有更好的风格。我不会将kwargs 用于常规字典参数,因为该参数名称通常用于关键字参数,在这种情况下根本不适用。我还会使用 for name, value in kwargs.items()(或 Python 2 中的 .iteritems()),而不是遍历键并在下一行查找值。 是的,尽管错误是由于对 Namespace 原始文件的不完整编辑造成的。【参考方案6】:

使用__setattr__方法怎么样?

>>> class AttrDict(dict):
...     def __getattr__(self, name):
...         if name in self:
...             return self[name]
... 
...     def __setattr__(self, name, value):
...         self[name] = self.from_nested_dict(value)
... 
...     def __delattr__(self, name):
...         if name in self:
...             del self[name]
... 
...     @staticmethod
...     def from_nested_dict(data):
...         """ Construct nested AttrDicts from nested dictionaries. """
...         if not isinstance(data, dict):
...             return data
...         else:
...             return AttrDict(key: AttrDict.from_nested_dict(data[key])
...                                 for key in data)
...         

>>> ad = AttrDict()
>>> ad


>>> data = 'a': 'aval', 'b': 'b1':'b2a':'b3a':'b3aval','b3b':'b3bval','b2b':'b2bval' 

>>> ad.data = data
>>> ad.data
'a': 'aval', 'b': 'b1': 'b2a': 'b3a': 'b3aval', 'b3b': 'b3bval', 'b2b': 'b2bval'

>>> print(ad.data.b.b1.b2a.b3b)
    b3bval

【讨论】:

以上是关于嵌套字典的类对象属性访问的主要内容,如果未能解决你的问题,请参考以下文章

python基础之面向对象进阶

js对象不能访问他所属类的类属性

slots与迭代器

从类对象继承属性的类对象的python dict

python中的类

__slots__(三十六)