像属性一样访问dict键?

Izz*_*sin 273 python syntax dictionary

我发现访问dict密钥更加方便,obj.foo而不是obj['foo'],所以我写了这个代码片段:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value
Run Code Online (Sandbox Code Playgroud)

但是,我认为必须有一些原因,Python不提供开箱即用的功能.以这种方式访问​​dict密钥有什么警告和陷阱?

Kim*_*ais 283

最好的方法是:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self
Run Code Online (Sandbox Code Playgroud)

一些优点:

  • 它确实有效!
  • 没有字典类方法被遮蔽(例如.keys()工作得很好)
  • 属性和项始终保持同步
  • 尝试正确地访问不存在的键作为属性AttributeError而不是KeyError

缺点:

  • 如果被传入的数据覆盖,那么类似的方法.keys()无法正常工作
  • 在Python <2.7.4/Python3 <3.2.3中导致内存泄漏
  • Pylint用E1123(unexpected-keyword-arg)和去香蕉E1103(maybe-no-member)
  • 对于没有经验的人来说,这似乎是纯粹的魔法.

关于它如何工作的简短说明

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

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!"
Run Code Online (Sandbox Code Playgroud)

  • @viveksinghggits 仅仅因为您通过`.` 访问事物,您不能违反语言规则:) 而且我不希望`AttrDict` 自动将包含空格的字段转换为不同的内容。 (2认同)

Her*_*ery 125

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

  • @Melab问题是"以这种方式访问​​dict键会有什么警告和陷阱?"(作为属性),答案是这里显示的大多数字符都不可用. (3认同)
  • 并非JavaScript是编程语言的一个特别好的例子,但JS中的对象同时支持属性访问和数组表示法,这样可以方便常见情况*和*对于非合法属性名称的符号的通用回退. (2认同)

sla*_*acy 78

另一个问题来看,有一个很好的实现示例可以简化现有代码.怎么样:

class AttributeDict(dict): 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
Run Code Online (Sandbox Code Playgroud)

更简洁,不留任何余地额外的克鲁夫特进入你__getattr__,并__setattr__在未来的功能.

  • 您必须记住,如果在运行时添加新属性,它们不会添加到dict本身,而是添加到__dict__属性.例如`d = AttributeDict(foo = 1)`.`d.bar = 1` bar属性存储在__dict__属性中,但不存储在dict本身中.打印`d`只显示foo项目. (12认同)
  • +1,因为它可以完美地工作,据我所知.@GringoSuave,@ Izkata,@ P3trus我请求任何声称它失败的人显示一个不起作用的例子`d = AttributeDict(foo = 1); d.bar = 1; print d` =>`{'foo':1 ,'bar':1}`适合我! (7认同)
  • 如果给定的属性不存在,你应该提供一个`__getattr__`方法引发`AttributeError`,否则`getattr(obj,attr,default_value)`之类的东西不起作用(即如果`不返回`default_value`` `obj`上不存在attr` (6认同)
  • @DaveAbrahams阅读_full_问题并查看Hery,Ryan和TheCommunistDuck的答案.这不是要求_how_这样做,而是关于_可能出现的问题_. (4认同)

Dou*_* R. 71

我在哪里回答被问到的问题

为什么Python没有开箱即用?

我怀疑它与Python禅宗有关:"应该有一个 - 最好只有一个 - 显而易见的方法." 这将创建两种从字典访问值的明显方法:obj['key']obj.key.

警告和陷阱

这些可能包括代码中可能缺乏清晰度和混淆.也就是说,如果您暂时不再使用它,以下内容可能会让以后维护您的代码的其他人感到困惑,甚至可能会让您感到困惑.再次,来自Zen:"可读性很重要!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Run Code Online (Sandbox Code Playgroud)

如果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'
Run Code Online (Sandbox Code Playgroud)

IMO,不值得努力.

其他项目

正如其他人所说,你可以使用任何可散列对象(不仅仅是一个字符串)作为dict键.例如,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 
Run Code Online (Sandbox Code Playgroud)

是合法的,但是

>>> 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
>>> 
Run Code Online (Sandbox Code Playgroud)

不是.这使您可以访问字典键的所有可打印字符或其他可清除对象,这是访问对象属性时没有的.这使得像缓存对象元类一样神奇,就像Python Cookbook(第9章)中的食谱一样.

我在哪里报道

我更喜欢的美学spam.eggsspam['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}
>>>
Run Code Online (Sandbox Code Playgroud)

这是一个简单的例子,但我经常发现自己在不同情况下使用dicts而不是使用obj.key符号(即,当我需要从XML文件中读取prefs时).在其他情况下,我试图实例化一个动态类并为了美观原因对其进行一些属性,我继续使用dict来保持一致性以增强可读性.

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

  • 是我比较熟悉的那个.子类dict,所以你有所有的功能.
  • AttrDict也看起来也不错,但我不熟悉它,并通过源在尽可能多的细节还没有看,因为我有一堆.
  • 如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}
Run Code Online (Sandbox Code Playgroud)


我在哪里更新,回答评论中的后续问题

在评论(下面)中,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}
Run Code Online (Sandbox Code Playgroud)

  • Python就像一把倒在大雨中的雨伞。开始时,这一切看起来都很聪明,时髦,一段时间后开始变得沉重,然后突然,您在SE上阅读了一些内置的专家信息,然后整个过程又恢复了,整个有效负载都落在了您的肩膀上。在仍然湿透的同时,您会感到更轻盈,一切都变得如此清晰和清新。 (3认同)

Rya*_*yan 21

警告:由于某些原因,这样的类似乎打破了多处理包.在找到这个SO之前,我只是挣扎了这个bug一段时间: 在python多处理中查找异常


The*_*uck 18

如果你想要一个方法的密钥怎么办,比如__eq____getattr__

并且您将无法拥有一个不以字母开头的条目,因此将0343853密钥用作密钥.

如果你不想使用字符串怎么办?


lin*_*urn 16

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

from argparse import Namespace
Run Code Online (Sandbox Code Playgroud)

避免必须复制代码位.没有标准的字典访问,但​​如果你真的想要它,很容易得到一个.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__
Run Code Online (Sandbox Code Playgroud)

  • PLUS 1用于引用标准库,该库用于解决OP的第一个注释. (2认同)
  • 在这种情况下,Python包括一个更快的类(用C实现):`types.SimpleNamespace` https://docs.python.org/dev/library/types.html#types.SimpleNamespace (2认同)

Deu*_*ina 16

我发现自己想知道 python 生态系统中“dict keys as attr”的当前状态。正如几位评论者指出的那样,这可能不是您想从头开始制作自己的东西,因为有几个陷阱和脚步,其中一些非常微妙。另外,我不建议将其Namespace用作基类,我一直在这条路上,它并不漂亮。

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

竞争者(最近提交给 master|#commits|#contribs|coverage%):

不再维护或维护不足:

  • treedict (2014-03-28 | 95 | 2 | ?%)
  • (2012-03-12 | 20 | 2 | ?%)
  • 新群

我目前推荐上瘾。他们拥有最多的提交、贡献者和发布,为每个人提供了一个健康的开源代码库。他们有最干净的 readme.md、100% 的覆盖率和漂亮的测试集。

我在这场比赛中没有狗(现在!),除了滚动我自己的 dict/attr 代码并浪费了大量时间,因为我不知道所有这些选项:)。我可能会在未来为上瘾/咀嚼做出贡献,因为我宁愿看到一个坚固的包装而不是一堆零散的包装。如果你喜欢他们,贡献!特别是,看起来 munch 可以使用 codecov 徽章,而上瘾者可以使用 python 版本徽章。

瘾君子的优点:

  • 递归初始化(foo.abc = 'bar'),类似dict的参数变成了 上瘾者.Dict

上瘾的缺点:

  • 阴影,typing.Dict如果你from addict import Dict
  • 没有钥匙检查。由于允许递归初始化,如果您拼错了一个键,您只需创建一个新属性,而不是 KeyError(感谢 AljoSt)

蒙克优点:

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

咀嚼缺点:

  • 没有递归初始化(你不能构造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']

Run Code Online (Sandbox Code Playgroud)

这为您提供了一个仍然表现得像 dict 的对象,但也允许您以更加严格的方式访问属性等键。这里的优势是我(或您的代码的不幸消费者)确切地知道哪些字段可以和不存在,并且 IDE 可以自动完成字段。子类化 vanilladict意味着 json 序列化很容易。我认为这个想法的下一个演变将是一个定制的 protobuf 生成器,它发出这些接口,一个很好的连锁反应是你几乎免费地通过 gRPC 获得跨语言数据结构和 IPC。

如果您决定使用 attr-dicts,为了您自己(和您的队友)的理智,必须记录哪些字段是预期的。

随意编辑/更新这篇文章以使其保持最新状态!

  • `addict` 的一个很大的缺点是,当你拼错一个属性时,它不会引发异常,因为它会返回一个新的 `Dict` (这是 foo.abc = 'bar' 工作所必需的)。 (2认同)

Sen*_*ran 11

元组可以用dict键.你如何访问你的构造中的元组?

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

  • 有些人会说,不可变不是一个bug而是元组的一个特征. (9认同)
  • namedtuples的缺点是它们是不可变的. (6认同)

tal*_*eth 8

它没有普遍性.并非所有有效的dict键都具有可寻址的属性("键").所以,你需要小心.

Python对象基本上都是字典.所以我怀疑有很多表现或其他惩罚.


gon*_*onz 8

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

上瘾者这是一个很好的库:https : //github.com/mewwts/accine它解决了之前答案中提到的许多问题。

文档中的一个示例:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

与瘾君子:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'
Run Code Online (Sandbox Code Playgroud)


use*_*865 7

只是为了给答案添加一些多样性,sci-kit learn将其实现为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                       
Run Code Online (Sandbox Code Playgroud)

您所需要的只是获取setattrgetattr方法 -getattr检查 dict 键并继续检查实际属性。这setstaet是对酸洗/解酸“束”的修复 - 如果有兴趣检查https://github.com/scikit-learn/scikit-learn/issues/6196


小智 7

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

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

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

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


Ram*_*lat 6

怎么样Prodict我写的用来统治它们的小Python类:)

另外,您将获得自动代码完成递归对象实例化自动类型转换

您可以按照自己的要求做:

p = Prodict()
p.foo = 1
p.bar = "baz"
Run Code Online (Sandbox Code Playgroud)

示例1:类型提示

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

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871
Run Code Online (Sandbox Code Playgroud)

自动编码完成

示例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'>
Run Code Online (Sandbox Code Playgroud)

  • 通过pip安装在python2上,但在python2上不起作用 (2认同)
  • 由于类型注释,@Ant6n 需要 python 3.6+ (2认同)

s2t*_*2t2 6

使用SimpleNamespace

from types import SimpleNamespace

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

print(obj.color) #> "blue"
print(obj.year) #> 2050
Run Code Online (Sandbox Code Playgroud)

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

from types import SimpleNamespace

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

obj = SimpleNamespace(**params)

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

Run Code Online (Sandbox Code Playgroud)


Ben*_*man 5

这是使用内置的不可变记录的简短示例collections.namedtuple

def record(name, d):
    return namedtuple(name, d.keys())(**d)
Run Code Online (Sandbox Code Playgroud)

和用法示例:

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

print rec.loss(..)
Run Code Online (Sandbox Code Playgroud)