像属性一样访问字典键?

我发现用obj.foo而不是obj['foo']来访问字典键更方便,所以我写了这个代码片段:

class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value

然而,我认为一定有一些原因,Python没有提供开箱即用的功能。以这种方式访问字典键的注意事项和缺陷是什么?

310683 次浏览
如果使用数组表示法,可以将所有合法的字符串字符作为键的一部分。 例如,obj['!#$%^&*()_']

如果你想要一个方法键,比如__eq____getattr__,怎么办?

而且你不能有一个不以字母开头的条目,所以使用0343853作为键是无效的。

如果你不想使用字符串呢?

一般情况下它不成立。不是所有有效的dict键都有可寻址的属性(“键”)。所以,你要小心。

Python对象基本上都是字典。所以我怀疑会有什么表现或其他惩罚。

不需要写你自己的作为 setattr ()和getattr()已经存在

类对象的优势可能在类定义和继承中发挥作用。

元组可以使用字典键。如何在构造中访问元组?

此外,namedtuple是一个方便的结构,可以通过属性访问提供值。

另一个SO问题中有一个很好的实现示例,它简化了现有的代码。如何:

class AttributeDict(dict):
__slots__ = ()
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__

更加简洁,并且不会为将来的__getattr____setattr__函数留下任何额外的麻烦空间。

买者自负:由于某些原因,这样的类似乎破坏了多处理包。我只是在发现这个bug之前挣扎了一段时间,所以: 在python multiprocessing中查找异常 < / p >

更新- 2020年

自从这个问题在大约十年前被提出以来,Python本身已经发生了相当大的变化。

虽然我最初回答中的方法在某些情况下仍然有效,(例如,遗留项目坚持使用较旧版本的Python,以及在某些情况下你确实需要处理具有非常动态字符串键的字典),但我认为一般来说,Python 3.7中引入的dataclasses是绝大多数AttrDict用例的明显/正确解决方案。

原来的答案

最好的方法是:

class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self

一些优点:

  • 它真的有用!
  • 没有字典类方法被遮蔽(例如.keys()工作得很好。除非-当然-你给它们赋值,见下文)
  • 属性和项总是同步的
  • 试图将不存在的key作为属性正确地访问会引发AttributeError而不是KeyError
  • 支持(选项卡)自动补全(例如在jupyter &ipython)

缺点:

  • 如果.keys()这样的方法被传入的数据覆盖,它们将工作得很好
  • 在Python <中导致内存泄漏;2.7.4 / Python3 <3.2.3
  • Pylint用E1123(unexpected-keyword-arg)E1103(maybe-no-member)疯狂
  • 对于外行来说,这似乎是纯粹的魔法。

简要解释一下它是如何工作的

  • 所有python对象都在内部将它们的属性存储在一个名为__dict__的字典中。
  • 没有要求内部字典__dict__必须“只是一个普通的dict”,所以我们可以将dict()的任何子类赋值给内部字典。
  • 在本例中,我们简单地分配了正在实例化的AttrDict()实例(就像在__init__中一样)。
  • 通过调用super()__init__()方法,我们确保它(已经)行为完全像一个字典,因为该函数调用所有字典实例化代码。

Python没有开箱即用提供此功能的原因之一

如“协约”中所述;List,它将存储键的名称空间(可能来自任意和/或不受信任的数据!)与内置dict方法属性的名称空间结合起来。例如:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
print "Never reached!"

我根据这个线程的输入创建了这个。我需要使用odect,所以我必须覆盖get和设置attr。我认为这应该适用于大多数特殊用途。

用法如下:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])


# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])


# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

类:

class OrderedAttrDict(odict.OrderedDict):
"""
Constructs an odict.OrderedDict with attribute access to data.


Setting a NEW attribute only creates it on the instance, not the dict.
Setting an attribute that is a key in the data will set the dict data but
will not create a new instance attribute
"""
def __getattr__(self, attr):
"""
Try to get the data. If attr is not a key, fall-back and get the attr
"""
if self.has_key(attr):
return super(OrderedAttrDict, self).__getitem__(attr)
else:
return super(OrderedAttrDict, self).__getattr__(attr)




def __setattr__(self, attr, value):
"""
Try to set the data. If attr is not a key, fall-back and set the attr
"""
if self.has_key(attr):
super(OrderedAttrDict, self).__setitem__(attr, value)
else:
super(OrderedAttrDict, self).__setattr__(attr, value)

这是一个非常酷的模式,已经在线程中提到了,但如果你只是想把字典转换成一个在IDE中使用自动完成的对象,等等:

class ObjectFromDict(object):
def __init__(self, d):
self.__dict__ = d

你可以从标准库中获取一个方便的容器类:

from argparse import Namespace

避免复制代码位。没有标准的字典访问,但如果你真的想要的话,很容易得到一个。argparse中的代码很简单,

class Namespace(_AttributeHolder):
"""Simple object for storing attributes.


Implements equality by attribute names and values, and provides a simple
string representation.
"""


def __init__(self, **kwargs):
for name in kwargs:
setattr(self, name, kwargs[name])


__hash__ = None


def __eq__(self, other):
return vars(self) == vars(other)


def __ne__(self, other):
return not (self == other)


def __contains__(self, key):
return key in self.__dict__

显然,现在有一个库——https://pypi.python.org/pypi/attrdict——它实现了这个确切的功能,加上递归合并和json加载。也许值得一看。

这并没有解决最初的问题,但是对于像我这样在这里寻找提供此功能的库的人来说应该是有用的。

成瘾者它是一个很好的库:https://github.com/mewwts/addict它照顾了前面的答案中提到的许多问题。

文档中的一个例子:

body = {
'query': {
'filtered': {
'query': {
'match': {'description': 'addictive'}
},
'filter': {
'term': {'created_by': 'Mats'}
}
}
}
}

成瘾者:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'

这不是一个“好”的答案,但我认为这是俏皮的(它不处理嵌套字典在当前形式)。简单地将dict包装在函数中:

def make_funcdict(d=None, **kwargs)
def funcdict(d=None, **kwargs):
if d is not None:
funcdict.__dict__.update(d)
funcdict.__dict__.update(kwargs)
return funcdict.__dict__
funcdict(d, **kwargs)
return funcdict

现在你的语法略有不同。要将字典项作为属性访问,请执行f.key。要以通常的方式访问dict项(和其他dict方法),请执行f()['key'],我们可以通过使用关键字参数和/或字典调用f来方便地更新dict

例子

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
...
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

就是这样。如果有人提出这种方法的优点和缺点,我会很高兴。

其中我回答了被问到的问题

为什么Python不开箱即用呢?

我怀疑这与Python的禅宗有关:“应该有一种——最好只有一种——明显的方法来做到这一点。”这将创建两种明显的方法来访问字典中的值:obj['key']obj.key

注意事项和陷阱

这包括代码中可能缺乏清晰性和混乱。也就是说,下面的代码可能会让以后要维护你代码的人其他的感到困惑,如果你一段时间都不回去的话,甚至会让你感到困惑。同样,来自: "可读性很重要!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

如果d被实例化, KEY被定义, d[KEY]被分配到远离d.spam被使用的地方,它很容易导致对正在做什么的混淆,因为这不是一个常用的习语。我知道这可能会让我感到困惑。

此外,如果你像下面这样改变KEY的值(但没有改变d.spam),你现在得到:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

在我看来,不值得这么努力。

其他物品

正如其他人所注意到的,您可以使用任何可哈希对象(不仅仅是字符串)作为dict键。例如,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>>

是合法的,但是

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
File "<stdin>", line 1
d.(2, 3)
^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>>

不是。这使您可以访问字典键的整个范围的可打印字符或其他可哈希对象,而在访问对象属性时则没有这些权限。这使得缓存对象元类这样的魔法成为可能,比如Python食谱(第9章)中的recipe。

其中我发表评论

我更喜欢spam.eggs的美学而不是spam['eggs'](我认为它看起来更干净),当我遇到namedtuple时,我真的开始渴望这个功能。但是能够做以下事情的便利性胜过它。

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

这是一个简单的例子,但我经常发现自己在不同的情况下使用字典,而不是使用obj.key符号(即,当我需要从XML文件中读取prefs时)。在其他情况下,当我想实例化一个动态类并为其添加一些属性时,我继续使用字典来保持一致性,以增强可读性。

我相信OP早就解决了这个问题,让他满意了,但如果他仍然想要这个功能,那么我建议他从pypi下载一个提供该功能的包:

  • Bunch是我比较熟悉的。dict的子类,所以你有所有的功能。
  • AttrDict也看起来也很不错,但我不熟悉它,也没有像我有Bunch
  • 瘾君子是主动维护的,并提供类似attri的访问等。
  • 正如Rotareti在评论中提到的,Bunch已经被弃用了,但是有一个名为Munch的活跃分支。

然而,为了提高代码的可读性,我强烈建议他混合他的符号风格。如果他喜欢这种表示法,那么他应该简单地实例化一个动态对象,添加他想要的属性,然后收工:

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}

<人力资源/ >

其中我更新,在评论中回答一个后续问题

在评论中(下面),埃尔莫问:

如果你想再深入一点呢?(指类型(…))

虽然我从未使用过这个用例(再次强调,我倾向于使用嵌套的dict,用于 一致性),下面的代码工作:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}

你可以用我刚做的这个类来做。在这个类中,你可以像使用另一个字典(包括json序列化)一样使用Map对象,或者使用点表示法。希望对你有所帮助:

class Map(dict):
"""
Example:
m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
"""
def __init__(self, *args, **kwargs):
super(Map, self).__init__(*args, **kwargs)
for arg in args:
if isinstance(arg, dict):
for k, v in arg.iteritems():
self[k] = v


if kwargs:
for k, v in kwargs.iteritems():
self[k] = v


def __getattr__(self, attr):
return self.get(attr)


def __setattr__(self, key, value):
self.__setitem__(key, value)


def __setitem__(self, key, value):
super(Map, self).__setitem__(key, value)
self.__dict__.update({key: value})


def __delattr__(self, item):
self.__delitem__(item)


def __delitem__(self, key):
super(Map, self).__delitem__(key)
del self.__dict__[key]

使用例子:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']

下面是一个使用内置collections.namedtuple的不可变记录的简短示例:

def record(name, d):
return namedtuple(name, d.keys())(**d)

还有一个用法示例:

rec = record('Model', {
'train_op': train_op,
'loss': loss,
})


print rec.loss(..)
class AttrDict(dict):


def __init__(self):
self.__dict__ = self


if __name__ == '____main__':


d = AttrDict()
d['ray'] = 'hope'
d.sun = 'shine'  >>> Now we can use this . notation
print d['ray']
print d.sun

解决方案是:

DICT_RESERVED_KEYS = vars(dict).keys()




class SmartDict(dict):
"""
A Dict which is accessible via attribute dot notation
"""
def __init__(self, *args, **kwargs):
"""
:param args: multiple dicts ({}, {}, ..)
:param kwargs: arbitrary keys='value'


If ``keyerror=False`` is passed then not found attributes will
always return None.
"""
super(SmartDict, self).__init__()
self['__keyerror'] = kwargs.pop('keyerror', True)
[self.update(arg) for arg in args if isinstance(arg, dict)]
self.update(kwargs)


def __getattr__(self, attr):
if attr not in DICT_RESERVED_KEYS:
if self['__keyerror']:
return self[attr]
else:
return self.get(attr)
return getattr(self, attr)


def __setattr__(self, key, value):
if key in DICT_RESERVED_KEYS:
raise AttributeError("You cannot set a reserved name as attribute")
self.__setitem__(key, value)


def __copy__(self):
return self.__class__(self)


def copy(self):
return self.__copy__()

编辑: NeoBunch是废弃的,Munch(上面提到过)可以作为一个替代品。不过,我把这个解决方案留在这里,它可能对某些人有用。

正如Doug所指出的,有一个Bunch包,你可以使用它来实现obj.key功能。实际上有一个更新的版本叫做

NeoBunch Munch

它有一个很好的特性,通过neobunchify函数将dict转换为NeoBunch对象。我经常使用Mako模板,将数据作为NeoBunch对象传递使它们更具可读性,所以如果你碰巧在你的Python程序中使用了一个普通的字典,但想要在Mako模板中使用点符号,你可以这样使用:

from mako.template import Template
from neobunch import neobunchify


mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
out_file.write(mako_template.render(**neobunchify(data)))

Mako模板看起来像这样:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor

让我发布另一个实现,它基于Kinvais的答案,但集成了http://databio.org/posts/python_AttributeDict.html中提出的AttributeDict的思想。

这个版本的优点是它也适用于嵌套字典:

class AttrDict(dict):
"""
A class to convert a nested Dictionary into an object with key-values
that are accessible using attribute notation (AttrDict.attribute) instead of
key notation (Dict["key"]). This class recursively sets Dicts to objects,
allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
"""


# Inspired by:
# http://stackoverflow.com/a/14620633/1551810
# http://databio.org/posts/python_AttributeDict.html


def __init__(self, iterable, **kwargs):
super(AttrDict, self).__init__(iterable, **kwargs)
for key, value in iterable.items():
if isinstance(value, dict):
self.__dict__[key] = AttrDict(value)
else:
self.__dict__[key] = value

为了给答案增加一些变化,sci-kit学习将其实现为Bunch:

class Bunch(dict):
""" Scikit Learn's container object


Dictionary-like object that exposes its keys as attributes.
>>> b = Bunch(a=1, b=2)
>>> b['b']
2
>>> b.b
2
>>> b.c = 6
>>> b['c']
6
"""


def __init__(self, **kwargs):
super(Bunch, self).__init__(kwargs)


def __setattr__(self, key, value):
self[key] = value


def __dir__(self):
return self.keys()


def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(key)


def __setstate__(self, state):
pass

你所需要的就是获取setattrgetattr方法——getattr检查字典键,然后继续检查实际属性。setstaet是对pickle /unpickling“bunch”的修复-如果感兴趣,请检查https://github.com/scikit-learn/scikit-learn/issues/6196

Prodict怎么样,这个小Python类我写了来统治它们:)

另外,你会得到自动完成代码递归对象实例化自动类型转换!

你完全可以做到你所要求的:

p = Prodict()
p.foo = 1
p.bar = "baz"

例1:类型提示

class Country(Prodict):
name: str
population: int


turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

auto code complete

例2:自动类型转换

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])


print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>


print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>

以这种方式访问字典键的注意事项和缺陷是什么?

正如@Henry所指出的,在dict中不能使用点访问的一个原因是,它将dict键名限制为python有效变量,从而限制了所有可能的名称。

以下是给出字典d的例子,说明为什么点访问通常没有帮助:

有效性

以下属性在Python中是无效的:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation
d.3 * x                           # expressions

风格

PEP8约定将对属性命名施加软约束:

A.保留关键字(或内置函数)名称:

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

如果函数参数的名称与保留关键字冲突,通常最好在后面添加一个下划线…

B. 方法变量名的大小写规则:

变量名遵循与函数名相同的约定。

d.Firstname
d.Country

使用函数命名规则:小写字母,单词之间用下划线分隔,以提高可读性。


有时这些问题会在图书馆像熊猫中提出,它允许按名称点访问DataFrame列。解决命名限制的默认机制也是数组表示法——括号中的字符串。

如果这些约束不适用于你的用例,在点访问数据结构上有几个选项。

可以使用dict_to_obj https://pypi.org/project/dict-to-obj/ 它完全符合你所要求的

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True


这就是我用的

args = {
'batch_size': 32,
'workers': 4,
'train_dir': 'train',
'val_dir': 'val',
'lr': 1e-3,
'momentum': 0.9,
'weight_decay': 1e-4
}
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)


print (args.lr)

我发现自己在想“字典键作为吸引”的当前状态是什么;在python生态系统中。正如一些评论者指出的,这可能是不是你想自己动手做的东西,因为有几个陷阱和脚枪,其中一些非常微妙。另外,我不建议使用Namespace作为基类,我一直在走那条路,它并不漂亮。

幸运的是,有几个开源包提供了这个功能,可以安装了!不幸的是,有几个包。以下是截至2019年12月的概要。

竞争者(最近提交到|#提交|#投稿|覆盖率%):

  • __abc0 (2021-01-05 | 229 | 22 | 100%)
  • __abc0 (2021-01-22 | 166 | 17 | ?%)
  • __abc0 (2021-02-28 | 54 | 7 | ?%)
  • __abc0 (2019-02-01 | 108 | 5 | 100%)
  • __abc0 (2021-03-06 | 100 | 2 | ?%)

不再保养或保养不足:

  • __abc0 (2014-03-28 | 95 | 2 | ?%)
  • __abc0 (2012-03-12 | 20 | 2 | ?%)
  • NeoBunch

我目前推荐蒙克成瘾者。它们拥有最多的提交、贡献者和发布,这意味着它们都有一个健康的开源代码库。他们有最干净的自述。Md, 100%的覆盖率,以及一组好看的测试。

我在这场比赛中没有一只狗(现在!),除了滚动我自己的dict/attr代码,浪费了大量的时间,因为我不知道所有这些选项:)。我可能会在未来贡献给addict/munch,因为我宁愿看到一个完整的包,而不是一堆碎片化的包。如果你喜欢它们,就投稿吧!特别是,看起来munch可以使用codecov徽章,addict可以使用python版本徽章。

瘾君子优点:

  • 递归初始化(foo.a.b.c = 'bar'),类字典参数成为成瘾。Dict

成瘾的缺点:

  • 如果你from addict import Dict,影子typing.Dict
  • 不检查密钥。由于允许递归init,如果你拼错了一个键,你只是创建一个新属性,而不是KeyError(感谢AljoSt)

蒙克优点:

  • 独特的命名
  • 内置的JSON和YAML的ser/de函数

蒙克缺点:

  • 没有递归init(你不能构造foo.a.b.c = 'bar',你必须先设置foo.a,然后再设置foo.a.b,等等。

其中我发表评论

很久以前,当我在只有我自己或其他开发人员的项目中使用文本编辑器编写python时,我喜欢dict-attrs的风格,即通过声明foo.bar.spam = eggs来插入键的能力。现在我在团队中工作,所有事情都使用IDE,我已经远离了这类数据结构和动态类型,而倾向于静态分析、函数技术和类型提示。我已经开始尝试这种技术,用我自己设计的对象子类化Pstruct:

class  BasePstruct(dict):
def __getattr__(self, name):
if name in self.__slots__:
return self[name]
return self.__getattribute__(name)


def __setattr__(self, key, value):
if key in self.__slots__:
self[key] = value
return
if key in type(self).__dict__:
self[key] = value
return
raise AttributeError(
"type object '{}' has no attribute '{}'".format(type(self).__name__, key))




class FooPstruct(BasePstruct):
__slots__ = ['foo', 'bar']


这给了你一个仍然像dict一样行为的对象,但也让你以一种更严格的方式访问像属性一样的键。这里的优点是我(或者您代码的倒霉消费者)确切地知道哪些字段可以存在,哪些字段不可以存在,并且IDE可以自动补全字段。同时,继承vanilla dict的子类意味着json序列化很容易。我认为这个想法的下一个发展将是一个自定义的protobuf生成器,它会发出这些接口,一个很好的附带效果是,你可以通过gRPC获得跨语言的数据结构和几乎免费的IPC。

如果您决定使用attrt -dicts,那么为了您自己(和您的队友)的理智,有必要记录期望哪些字段。

请随意编辑/更新这篇文章,以保持它的最新!

最简单的方法是定义一个类,我们称之为Namespace。它在字典上使用对象__abc .update()。然后,字典将被视为一个对象。

class Namespace(object):
'''
helps referencing object in a dictionary as dict.key instead of dict['key']
'''
def __init__(self, adict):
self.__dict__.update(adict)






Person = Namespace({'name': 'ahmed',
'age': 30}) #--> added for edge_cls




print(Person.name)

这个答案摘自Luciano Ramalho的《流利的Python》一书。这要归功于那个家伙。

class AttrDict:
"""A read-only façade for navigating a JSON-like object
using attribute notation
"""


def __init__(self, mapping):
self._data = dict(mapping)


def __getattr__(self, name):
if hasattr(self._data, name):
return getattr(self._data, name)
else:
return AttrDict.build(self._data[name])


@classmethod
def build(cls, obj):
if isinstance(obj, Mapping):
return cls(obj)
elif isinstance(obj, MutableSequence):
return [cls.build(item) for item in obj]
else:
return obj

在init中,我们取字典并使其成为字典。当使用getattr时,我们尝试从字典中获取该属性(如果字典已经具有该属性)。或者我们将参数传递给一个名为build的类方法。构建做了一件有趣的事情。如果对象是字典或类似的映射,该对象本身就是一个attr字典。如果它是一个像list这样的序列,它将被传递给我们现在正在使用的构建函数。如果是其他类型,比如STR或int。返回对象本身。

由于以下原因,我对现有的选项不满意后,我开发了MetaDict。它的行为完全像dict,但支持点表示法和IDE自动补全,而没有其他解决方案的缺点和潜在的名称空间冲突。所有功能和使用示例都可以在GitHub上找到(见上面的链接)。

完全披露:我是MetaDict的作者。

我在尝试其他解决方案时遇到的缺点/限制:

  • 瘾君子
    • IDE中没有键自动补全
    • 嵌套键分配不能关闭
    • 新分配的dict对象不会转换为支持属性样式的键访问
    • 阴影内置类型Dict
  • 产品
    • 没有定义静态模式(类似于dataclass), IDE中没有键自动补全
    • 当嵌入到list或其他内置可迭代对象时,不能对dict对象进行递归转换
  • AttrDict
    • IDE中没有键自动补全
    • 在幕后将list对象转换为tuple
  • Munch
    • 内置方法,如items()update()等,可以用obj.items = [1, 2, 3]覆盖
    • 当嵌入到list或其他内置可迭代对象时,不能对dict对象进行递归转换
  • EasyDict
    • 只有字符串是有效的键,但dict接受所有可哈希对象作为键
    • 内置方法,如items()update()等,可以用obj.items = [1, 2, 3]覆盖
    • 内置方法的行为不像预期的那样:obj.pop('unknown_key', None)引发AttributeError

使用SimpleNamespace:

from types import SimpleNamespace


obj = SimpleNamespace(color="blue", year=2050)


print(obj.color) #> "blue"
print(obj.year) #> 2050

编辑/更新:对OP的问题的更近的答案,从字典开始:

from types import SimpleNamespace


params = {"color":"blue", "year":2020}


obj = SimpleNamespace(**params)


print(obj.color) #> "blue"
print(obj.year) #> 2050


很抱歉再添加一个,但这一个解决了subdicts和纠正AttributeError,尽管非常简单:

class DotDict(dict):
def __init__(self, d: dict = {}):
super().__init__()
for key, value in d.items():
self[key] = DotDict(value) if type(value) is dict else value
    

def __getattr__(self, key):
if key in self:
return self[key]
raise AttributeError(key) #Set proper exception, not KeyError


__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__