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

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

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


当前回答

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

其他回答

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

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

为什么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.s spam的使用位置,那么很容易导致对正在执行的操作的混淆,因为这不是一个常用的习惯用法。我知道这可能会让我感到困惑。

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

>>> 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 Cookbook(第9章)中的食谱一样。

其中我发表评论

我更喜欢垃圾邮件的美感。eggs over 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。键符号(即,当我需要从XML文件中读取prefs时)。在其他情况下,当我想实例化一个动态类并为其添加一些属性时,我继续使用字典来保持一致性,以增强可读性。

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

邦奇是我更熟悉的人。dict的子类,所以你有所有的功能。 AttrDict看起来也很不错,但我对它不熟悉,也没有像我对Bunch那样详细地查看源代码。 上瘾是积极维护,并提供attrlike访问和更多。 正如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}

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

在下面的评论中,Elmo问道:

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

虽然我从未使用过这个用例(同样,我倾向于使用嵌套的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}

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

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

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

还有一个用法示例:

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

print rec.loss(..)

没有必要自己写 Setattr()和getattr()已经存在。

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