Accessing dict keys like an attribute?
我发现用
1 2 3 4 5 | class AttributeDict(dict): def __getattr__(self, attr): return self[attr] def __setattr__(self, attr, value): self[attr] = value |
但是,我认为,一定有一些原因导致python不能提供这种开箱即用的功能。以这种方式访问dict键的注意事项和陷阱是什么?
最好的方法是:
1 2 3 4 | class AttrDict(dict): def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) self.__dict__ = self |
一些优点:
- 它确实有效!
- 没有隐藏字典类方法(如
.keys() 工作正常) - 属性和项始终保持同步
- 试图以属性的形式访问不存在的密钥会正确地引发
AttributeError ,而不是KeyError 。
欺骗:
- 像
.keys() 这样的方法,如果被输入的数据覆盖,就不能正常工作。 - 在python<2.7.4/python3<3.2.3中导致内存泄漏
- 皮林特和
E1123(unexpected-keyword-arg) 和E1103(maybe-no-member) 一起吃香蕉。 - 对于没有经验的人来说,这似乎是纯粹的魔法。
这是如何工作的简短解释
- 所有python对象内部都将其属性存储在一个名为
__dict__ 的字典中。 - 内部字典
__dict__ 不需要"只是一个普通的dict",因此我们可以将dict() 的任何子类分配给内部字典。 - 在我们的例子中,我们只需分配我们正在实例化的
AttrDict() 实例(就像我们在__init__ 中一样)。 - 通过调用
super() 的__init__() 方法,我们确保它(已经)的行为与字典完全相同,因为该函数调用所有字典实例化代码。
为什么python不提供这种开箱即用的功能
如"cons"列表中所述,这将组合存储键的命名空间(可能来自任意和/或不受信任的数据!)具有内置dict方法属性的命名空间。例如:
1 2 3 4 | d = AttrDict() d.update({'items':["jacket","necktie","trousers"]}) for k, v in d.items(): # TypeError: 'list' object is not callable print"Never reached!" |
如果使用数组表示法,则可以将所有合法的字符串字符作为键的一部分。例如,
从另一个问题来看,有一个很好的实现示例可以简化现有代码。怎么样:
1 2 3 | class AttributeDict(dict): __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__ |
更简洁,而且在将来不会给你的
在那里我回答了被问到的问题为什么python不提供开箱即用的服务?
我怀疑这与python的禅有关:"应该有一种——最好只有一种——显而易见的方法。"这将创建两种明显的方法来访问字典中的值:
这包括代码中可能缺乏清晰性和混乱性。也就是说,如果有人打算在以后维护您的代码,那么下面的内容可能会让其他人感到困惑,或者甚至让您感到困惑,如果您暂时不重新使用它的话。同样,来自禅宗:"可读性很重要!"
1 2 3 4 | >>> KEY = 'spam' >>> d[KEY] = 1 >>> # Several lines of miscellaneous code here... ... assert d.spam == 1 |
如果实例化
另外,如果您更改
1 2 3 4 5 6 7 | >>> 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键。例如,
1 2 3 | >>> d = {(2, 3): True,} >>> assert d[(2, 3)] is True >>> |
是合法的,但
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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食谱(第9章)中的配方。
其中我社论我更喜欢
1 2 3 4 5 | >>> 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} >>> |
这是一个简单的例子,但是我经常发现自己在不同的情况下使用dict,而不是使用
我相信OP早就解决了这个问题,让他满意了,但是如果他仍然想要这个功能,那么我建议他从Pypi下载一个提供它的包:
- 我更熟悉的就是邦奇。
dict 的子类,因此您拥有所有这些功能。 - AttrDict看起来也不错,但我对它不太熟悉,也没有像我这样详细地查看过信息源。
- 正如罗塔雷蒂的评论中所指出的,束已经被弃用,但有一个活跃的分叉称为芒奇。
但是,为了提高代码的可读性,我强烈建议他不要混合他的符号样式。如果他更喜欢这个符号,那么他应该简单地实例化一个动态对象,向它添加他想要的属性,并将其称为一天:
1 2 3 4 5 6 | >>> 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要求:
What if you want to go one deeper? ( referring to type(...) )
虽然我从未使用过这个用例(同样,我倾向于使用嵌套的
1 2 3 4 5 6 7 8 9 10 | >>> 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} |
注意清空:出于某些原因,类似这样的类似乎会破坏多处理包。我只是和这个bug斗争了一段时间,然后发现了这个问题:在python多处理中查找异常
如果您想要一个方法的密钥,比如
你不可能有一个不是以字母开头的条目,所以使用
如果你不想用绳子怎么办?
您可以从标准库中提取方便的容器类:
1 | from argparse import Namespace |
以避免在代码位周围进行复制。没有标准的字典访问,但如果你真的想要,很容易取回。argparse中的代码很简单,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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__ |
元组可以使用dict键。如何访问构造中的元组?
此外,NamedDuple是一种方便的结构,可以通过属性访问提供值。
一般来说,它不起作用。并非所有有效的dict键都具有可寻址属性("键")。所以,你需要小心。
python对象基本上都是字典。所以我怀疑是否有很多表现或其他惩罚。
这并不能解决最初的问题,但对于像我这样的人来说,在查找提供此功能的lib时,应该会很有用。
上瘾:这是一个伟大的自由:https://github.com/mewwts/addict它处理了前面答案中提到的许多问题。
文档示例:
1 2 3 4 5 6 7 8 9 10 11 12 | body = { 'query': { 'filtered': { 'query': { 'match': {'description': 'addictive'} }, 'filter': { 'term': {'created_by': 'Mats'} } } } } |
吸毒成瘾:
1 2 3 4 | from addict import Dict body = Dict() body.query.filtered.query.match.description = 'addictive' body.query.filtered.filter.term.created_by = 'Mats' |
下面是一个使用内置
1 2 | def record(name, d): return namedtuple(name, d.keys())(**d) |
以及用法示例:
1 2 3 4 5 6 | rec = record('Model', { 'train_op': train_op, 'loss': loss, }) print rec.loss(..) |
不需要把自己写成setAttr()和getAttr()已经存在。
类对象的优势可能在类定义和继承中发挥作用。
我是基于这个线程的输入创建的。我需要使用odict,所以我必须覆盖get和set attr。我认为这应该适用于大多数特殊用途。
用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create an ordered dict normally... >>> od = OrderedAttrDict() >>> od["a"] = 1 >>> od["b"] = 2 >>> od OrderedAttrDict([('a', 1), ('b', 2)]) # Get and set data using attribute access... >>> od.a 1 >>> od.b = 20 >>> od OrderedAttrDict([('a', 1), ('b', 20)]) # Setting a NEW attribute only creates it on the instance, not the dict... >>> od.c = 8 >>> od OrderedAttrDict([('a', 1), ('b', 20)]) >>> od.c 8 |
班级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class OrderedAttrDict(odict.OrderedDict): """ Constructs an odict.OrderedDict with attribute access to data. Setting a NEW attribute only creates it on the instance, not the dict. Setting an attribute that is a key in the data will set the dict data but will not create a new instance attribute """ def __getattr__(self, attr): """ Try to get the data. If attr is not a key, fall-back and get the attr """ if self.has_key(attr): return super(OrderedAttrDict, self).__getitem__(attr) else: return super(OrderedAttrDict, self).__getattr__(attr) def __setattr__(self, attr, value): """ Try to set the data. If attr is not a key, fall-back and set the attr """ if self.has_key(attr): super(OrderedAttrDict, self).__setitem__(attr, value) else: super(OrderedAttrDict, self).__setattr__(attr, value) |
这是一个在线程中已经提到的非常酷的模式,但是如果您只想获取一个dict并将其转换为一个在IDE中自动完成的对象,等等:
1 2 3 | class ObjectFromDict(object): def __init__(self, d): self.__dict__ = d |
显然,现在有了一个用于这个的库——https://pypi.python.org/pypi/attrdict——它实现了这个精确的功能以及递归合并和JSON加载。可能值得一看。
为了增加答案的多样性,Sci Kit Learn将其作为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 |
您所需要的只是获取
prodict怎么样,我写的小python类控制了它们:)
另外,您还可以获得自动代码完成、递归对象实例化和自动类型转换!
你可以按照你的要求做:
1 2 3 | p = Prodict() p.foo = 1 p.bar ="baz" |
示例1:类型提示
1 2 3 4 5 6 7 | class Country(Prodict): name: str population: int turkey = Country() turkey.name = 'Turkey' turkey.population = 79814871 |
1 2 3 4 5 6 7 | 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'> |
让我发布另一个实现,它基于Kinvais的答案,但是集成了http://databio.org/posts/python_attributedict.html中提出的attributedict的思想。
此版本的优点是它也适用于嵌套字典:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
你可以用我刚上的这门课来做。使用这个类,您可以像使用另一个字典(包括JSON序列化)一样使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class Map(dict): """ Example: m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) """ def __init__(self, *args, **kwargs): super(Map, self).__init__(*args, **kwargs) for arg in args: if isinstance(arg, dict): for k, v in arg.iteritems(): self[k] = v if kwargs: for k, v in kwargs.iteritems(): self[k] = v def __getattr__(self, attr): return self.get(attr) def __setattr__(self, key, value): self.__setitem__(key, value) def __setitem__(self, key, value): super(Map, self).__setitem__(key, value) self.__dict__.update({key: value}) def __delattr__(self, item): self.__delitem__(item) def __delitem__(self, key): super(Map, self).__delitem__(key) del self.__dict__[key] |
使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) # Add new key m.new_key = 'Hello world!' print m.new_key print m['new_key'] # Update values m.new_key = 'Yay!' # Or m['new_key'] = 'Yay!' # Delete key del m.new_key # Or del m['new_key'] |
1 2 3 4 5 6 7 8 9 10 11 12 | class AttrDict(dict): def __init__(self): self.__dict__ = self if __name__ == '____main__': d = AttrDict() d['ray'] = 'hope' d.sun = 'shine' >>> Now we can use this . notation print d['ray'] print d.sun |
解决方案是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | DICT_RESERVED_KEYS = vars(dict).keys() class SmartDict(dict): """ A Dict which is accessible via attribute dot notation """ def __init__(self, *args, **kwargs): """ :param args: multiple dicts ({}, {}, ..) :param kwargs: arbitrary keys='value' If ``keyerror=False`` is passed then not found attributes will always return None. """ super(SmartDict, self).__init__() self['__keyerror'] = kwargs.pop('keyerror', True) [self.update(arg) for arg in args if isinstance(arg, dict)] self.update(kwargs) def __getattr__(self, attr): if attr not in DICT_RESERVED_KEYS: if self['__keyerror']: return self[attr] else: return self.get(attr) return getattr(self, attr) def __setattr__(self, key, value): if key in DICT_RESERVED_KEYS: raise AttributeError("You cannot set a reserved name as attribute") self.__setitem__(key, value) def __copy__(self): return self.__class__(self) def copy(self): return self.__copy__() |
正如Doug所指出的,有一个包可以用来实现
新纪元
它有一个很好的特性,可以通过它的neobunchify函数将您的dict转换为一个新束对象。我经常使用mako模板,并将数据作为neobunch对象传递,使其更具可读性,因此如果您碰巧在python程序中使用了一个普通的dict,但希望在mako模板中使用点符号,则可以这样使用:
1 2 3 4 5 6 7 | from mako.template import Template from neobunch import neobunchify mako_template = Template(filename='mako.tmpl', strict_undefined=True) data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]} with open('out.txt', 'w') as out_file: out_file.write(mako_template.render(**neobunchify(data))) |
而Mako模板可能看起来像:
1 2 3 4 | % for d in tmpl_data: Column1 Column2 ${d.key1} ${d.key2} % endfor |
What would be the caveats and pitfalls of accessing dict keys in this manner?
正如@henry所建议的,点式访问不能用于dict的一个原因是它将dict键名限制为python有效变量,从而限制了所有可能的名称。
以下是点式访问一般不会有帮助的示例,给出了一个dict,
有效性
以下属性在python中无效:
1 2 3 4 5 6 | d.1_foo # enumerated names d./bar # path names d.21.7, d.12:30 # decimals, time d."" # empty strings d.john doe, d.denny's # spaces, misc punctuation d.3 * x # expressions |
风格
PEP8约定将对属性命名施加软约束:
a.保留关键字(或内置函数)名称:
1 2 3 4 5 | d.in d.False, d.True d.max, d.min d.sum d.id |
If a function argument's name clashes with a reserved keyword, it is generally better to append a single trailing underscore ...
b.方法和变量名的大小写规则:
Variable names follow the same convention as function names.
1 2 | d.Firstname d.Country |
Use the function naming rules: lowercase with words separated by underscores as necessary to improve readability.
有时,这些问题会在pandas之类的库中出现,该库允许按名称对数据帧列进行点式访问。解决命名限制的默认机制也是数组表示法——括号内的字符串。
如果这些约束不适用于您的用例,那么在点式访问数据结构上有几个选项。
这不是一个"好"的答案,但我认为这很漂亮(它不处理当前形式的嵌套式听写)。简单地用一个函数包装您的dict:
1 2 3 4 5 6 7 | def make_funcdict(d={}, **kwargs) def funcdict(d={}, **kwargs): funcdict.__dict__.update(d) funcdict.__dict__.update(kwargs) return funcdict.__dict__ funcdict(d, **kwargs) return funcdict |
现在您有了稍微不同的语法。将dict项作为
1 2 3 4 5 6 7 8 9 10 11 12 13 | d = {'name':'Henry', 'age':31} d = make_funcdict(d) >>> for key in d(): ... print key ... age name >>> print d.name ... Henry >>> print d.age ... 31 >>> d({'Height':'5-11'}, Job='Carpenter') ... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'} |
就在那里。如果有人提出这种方法的优点和缺点,我会很高兴的。