我发现它更方便访问字典键作为obj。foo而不是obj['foo'],所以我写了这个片段:
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
然而,我认为一定有一些原因,Python没有提供开箱即用的功能。以这种方式访问字典键的注意事项和缺陷是什么?
product怎么样,我写了一个小小的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
例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'>
很抱歉再添加一个,但这一个解决了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__
让我发布另一个实现,它基于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
由于以下原因,我对现有的选项不满意,于是我开发了MetaDict。它的行为完全类似于dict,但支持点表示法和IDE自动补全,而没有其他解决方案的缺点和潜在的名称空间冲突。所有功能和使用示例都可以在GitHub上找到(见上面的链接)。
完全披露:我是MetaDict的作者。
我在尝试其他解决方案时遇到的缺点/限制:
Addict
No key autocompletion in IDE
Nested key assignment cannot be turned off
Newly assigned dict objects are not converted to support attribute-style key access
Shadows inbuilt type Dict
Prodict
No key autocompletion in IDE without defining a static schema (similar to dataclass)
No recursive conversion of dict objects when embedded in list or other inbuilt iterables
AttrDict
No key autocompletion in IDE
Converts list objects to tuple behind the scenes
Munch
Inbuilt methods like items(), update(), etc. can be overwritten with obj.items = [1, 2, 3]
No recursive conversion of dict objects when embedded in list or other inbuilt iterables
EasyDict
Only strings are valid keys, but dict accepts all hashable objects as keys
Inbuilt methods like items(), update(), etc. can be overwritten with obj.items = [1, 2, 3]
Inbuilt methods don't behave as expected: obj.pop('unknown_key', None) raises an AttributeError
更新- 2020年
自从这个问题在大约十年前被提出以来,Python本身已经发生了相当大的变化。
虽然我最初回答中的方法在某些情况下仍然有效,(例如,遗留项目坚持使用旧版本的Python,以及在某些情况下,您确实需要处理具有非常动态字符串键的字典),但我认为一般来说,Python 3.7中引入的数据类是AttrDict绝大多数用例的明显/正确的解决方案。
原来的答案
最好的方法是:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
一些优点:
它真的有用!
没有字典类方法被遮蔽(例如.keys()工作得很好。除非-当然-你给它们赋值,见下文)
属性和项总是同步的
试图将不存在的key作为属性访问会正确地引发AttributeError而不是KeyError
支持[Tab]自动补全(例如在jupyter和ipython中)
缺点:
如果.keys()等方法被传入的数据覆盖,它们就不能正常工作
在Python < 2.7.4 / Python3 < 3.2.3中导致内存泄漏
Pylint因为E1123(意外关键字参数)和E1103(可能没有成员)而抓狂
对于外行来说,这似乎是纯粹的魔法。
简要解释一下它是如何工作的
All python objects internally store their attributes in a dictionary that is named __dict__.
There is no requirement that the internal dictionary __dict__ would need to be "just a plain dict", so we can assign any subclass of dict() to the internal dictionary.
In our case we simply assign the AttrDict() instance we are instantiating (as we are in __init__).
By calling super()'s __init__() method we made sure that it (already) behaves exactly like a dictionary, since that function calls all the dictionary instantiation code.
Python没有开箱即用提供此功能的原因之一
正如“cons”列表中所指出的,这将存储键的名称空间(可能来自任意和/或不受信任的数据!)与内置dict方法属性的名称空间结合在一起。例如:
d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items(): # TypeError: 'list' object is not callable
print "Never reached!"