像一个属性访问字典键?

我觉得更方便的访问字典键为obj.foo而不是obj['foo'] ,所以我写了这个片段:

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

不过,我认为Python必须有一些原因不能提供这种功能。 以这种方式访问​​字典键的警告和缺陷是什么?

最好的办法是:

 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)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!" 

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

从这个其他SO问题有一个很好的实现例子,简化了你现有的代码。 怎么样:

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

更简洁,不会留下任何空间来进一步cruft进入你的__getattr____setattr__函数在未来。

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

为什么Python不提供它的框?

我怀疑这与Python的禅宗有关 :“应该有一个 – 最好只有一个 – 明显的做法。” 这将创建两种明显的方法来访问字典中的值: obj['key']obj.key

警告和陷阱

这些包括在代码中可能不明确和混淆。 也就是说,以下内容可能会让其他将要在以后维护你的代码的人感到困惑,甚至会让你感到困惑,如果你不回头一段时间。 再次,从禅 :“可读性计数!”

 >>> KEY = 'foo' >>> d[KEY] = 1 >>> if d.foo == 1: ... ... ... 

如果d被实例化 KEY被定义, 或者 d[KEY]被分配到离d.foo被使用的地方很远的地方,那么很容易导致混淆所做的事情,因为这不是一个常用的习惯用法。 我知道这可能会让我迷惑。

其他项目

正如其他人所指出的,你可以使用任何可哈希对象(不只是一个字符串)作为一个字典的关键。 例如,

 >>> d = {(2, 3): True,} >>> assert d[(2, 3)] is True >>> 

是合法的,但是

 >>> C = type('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章)的配方一样。

其中我编辑

我更喜欢spam.eggsspam['eggs']的美学(我认为它看起来更干净),当我遇到namedtuple时,我真的开始渴望这个功能。 但能够做到以下的便利胜过它。

 >>> KEYS = 'foo bar baz' >>> VALS = [1, 2, 3] >>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)} >>> assert d == {'foo': 1, 'bar': 2, 'baz': 3} >>> 

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

我确信OP已经解决了这个问题,但是如果他仍然想要这个功能的话,那么我建议他从pypi下载一个包来提供它:

  • 是我更熟悉的一个。 dict子类,所以你有所有的功能。
  • AttrDict也看起来也很不错,但是我并不熟悉它,也没有像Bunch那样详细地查看源代码。
  • 正如Rotareti在评论中指出的那样,Bunch已经被弃用,但是有一个叫做Munch的活跃分叉。

但是,为了提高代码的可读性,我强烈建议他不要混合他的符号风格。 如果他喜欢这个符号,那么他应该简单地实例化一个动态的对象,添加他所需的属性,并称之为一天:

 >>> C = type('type_C', (object,), {}) >>> d = C() >>> d.foo = 1 >>> d.bar = 2 >>> d.baz = 3 >>> assert d.__dict__ == {'foo': 1, 'bar': 2, 'baz': 3} >>> 

如果你想要一个方法的键,如__eq____getattr__怎么办?

而且你不能有一个不以字母开头的条目,所以使用0343853作为关键字。

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

警告:有些类似这样的类看起来会破坏多处理程序包。 在找到这个SO之前,我只是苦苦寻找这个bug: 在python多处理中寻找异常

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

 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__ 

元组可以使用字典键。 你将如何访问元组中的元组?

另外, namedtuple是一个可以通过属性访问提供值的方便结构。

它不工作的一般性。 不是所有有效的字典键都可以设置可寻址的属性(“键”)。 所以,你需要小心。

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

不需要编写自己的setattr()和getattr()已经存在。

类对象的优点可能在类定义和继承中起作用。

我基于这个线程的输入创建了这个。 我需要使用odict,所以我必须重写get和set attr。 我认为这应该适用于大多数特殊用途。

用法如下所示:

 # 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 

班上:

 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) 

这是一个非常酷的模式已经在线程中提到,但如果你只是想要一个字典,并将其转换为一个IDE的自动完成的对象,等:

 class ObjectFromDict(object): def __init__(self, d): self.__dict__ = d 

显然现在有一个这样的库 – https://pypi.python.org/pypi/attrdict – 它实现了这个确切的功能加上递归合并和JSON加载。 值得一看。

这不能解决原来的问题,但是对于像我这样的人来说,在寻找一个提供这种功能的库的时候最终会变得有用。

冰火这是一个很好的解决方案: https : //github.com/mewwts/addict它照顾了以前答案中提到的许多问题。

来自文档的示例:

 body = { 'query': { 'filtered': { 'query': { 'match': {'description': 'addictive'} }, 'filter': { 'term': {'created_by': 'Mats'} } } } } 

吸毒者:

 from addict import Dict body = Dict() body.query.filtered.query.match.description = 'addictive' body.query.filtered.filter.term.created_by = 'Mats' 

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

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

和一个用法示例:

 rec = record('Model', { 'train_op': train_op, 'loss': loss, }) print rec.loss(..) 
 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 

正如Doug指出的那样,有一个Bunch包可以用来实现obj.key功能。 其实有一个更新的版本叫

NeoBunch

它有一个很棒的功能,通过neobunchify函数将你的字典转换成一个NeoBunch对象。 我使用Mako模板很多,并且将数据作为NeoBunch对象传递,使得它们更具可读性,所以如果您碰巧在Python程序中使用正常的字典,但希望在Mako模板中使用点符号,则可以这样使用它:

 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模板可能如下所示:

 % for d in tmpl_data: Column1 Column2 ${d.key1} ${d.key2} % endfor 

这不是一个“好”的答案,但我认为这很漂亮(它不处理当前形式的嵌套字典)。 简单地把你的字典包装在一个函数中:

 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 

现在你有一些不同的语法。 为了访问字典项目作为属性做f.key 。 要以通常的方式访问字典项目(和其他字典方法)做f()['key'] ,我们可以通过调用关键字参数和/或字典f来方便地更新字典

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

在那里。 如果有人提出这种方法的优点和缺点,我会很高兴。

你可以用我刚刚做的这个课程来做到这一点。 使用这个类,你可以像使用另一个字典(包括json序列化)或点符号一样使用Map对象。 我希望能帮助你:

 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] 

用法示例:

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

解决方案是:

 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 TypeError("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__() 

让我发表另一个实现,它建立在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 

为了给答案增加一些变化, sci-kit学习了这个实现为一个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 >>> bb 2 >>> bc = 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 

所有你需要的是获得setattrgetattr方法 – getattr检查代词键和移动到检查实际属性。 setstaet是一个修复酸洗/ unpickling“束”修复 – 如果感兴趣检查https://github.com/scikit-learn/scikit-learn/issues/6196