我发现它更方便访问字典键作为obj。foo而不是obj['foo'],所以我写了这个片段:

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

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


当前回答

这个答案摘自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

in the init we are taking the dict and making it a dictionary. when getattr is used we try to get the attribute from the dict if the dict already has that attribute. or else we are passing the argument to a class method called build. now build does the intresting thing. if the object is dict or a mapping like that, the that object is made an attr dict itself. if it's a sequence like list, it's passed to the build function we r on right now. if it's anythin else, like str or int. return the object itself.

其他回答

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

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__

更新- 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!"

您可以使用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

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

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

还有一个用法示例:

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

print rec.loss(..)

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