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

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

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


当前回答

让我发布另一个实现,它基于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

其他回答

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

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

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

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

由于以下原因,我对现有的选项不满意,于是我开发了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

如果你想要一个方法的键,比如__eq__或__getattr__呢?

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

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

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

为什么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}