嵌套字典的类对象属性访问
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
【讨论】:
以上是关于嵌套字典的类对象属性访问的主要内容,如果未能解决你的问题,请参考以下文章