访问OrderedDict键,如Python中的属性

时间:2014-09-04 08:09:55

标签: python dictionary

我想写一个容器类

  1. 允许索引(在字典样式中),例如, data['a']和类似属性的访问,例如data.a;这是here
  2. 解决的问题
  3. 保留添加条目的顺序,例如通过继承collections.OrderedDict;这是here
  4. 解决的问题

    我将1.的解决方案改为子类collections.OrderedDict而不是dict,但它不起作用;见下文。

    from collections import OrderedDict
    
    class mydict(OrderedDict):
        def __init__(self, *args, **kwargs):
            super(mydict, self).__init__(*args, **kwargs)
            self.__dict__ = self
    
    D = mydict(a=1, b=2)
    
    #D['c'] = 3 # fails
    D.d    = 4
    
    #print D    # fails
    

    带有失败注释的两行会导致以下错误:

        print D
      File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 176, in __repr__
        return '%s(%r)' % (self.__class__.__name__, self.items())
      File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 113, in items
        return [(key, self[key]) for key in self]
      File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 76, in __iter__
        root = self.__root
    AttributeError: 'struct' object has no attribute '_OrderedDict__root'
    

        D['c'] = 3
      File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 58, in __setitem__
        root = self.__root
    AttributeError: 'mydict' object has no attribute '_OrderedDict__root'
    

    有解决方案吗?可以修补__init__功能吗?

1 个答案:

答案 0 :(得分:5)

首先,如果你只是想让这个工作,但不明白你的尝试有什么问题,有多个" AttrDict"在PyPI,ActiveState等上实现。搜索一个并查看其代码 - 或者只是安装并使用它。


self.__dict__ = self不是你想要的。我意识到人们推荐它,但它有一些基本的主要问题。其中一个问题正是你所看到的问题。

特别是,这意味着您现在已经丢失了所有现有属性,包括OrderedDict本身使用的内部属性(例如__root您遇到的错误)和附加到所有对象的特殊属性(如__class__)。

当你刚刚使用dict时,你没有注意到这个问题 - 至少在CPython中 - 因为dict是在C中实现的,而且它需要的特殊成员操作存储在C结构中,而不是__dict__中,因此您不能丢失它们。但是对于在Python中实现的任何需要任何特殊成员的类,问题就变得明显了。


Python有customizing attribute access的特殊方法。你想要做的是实现这些方法:

class AttrDict(OrderedDict):
    def __getattr__(self, name):
        return self[name]
    def __setattr__(self, name, value):
        self[name] = value
    def __delattr__(self, name):
        del self[name]

然而,在这种情况下,即使这样做也不会适用于所有内容,因为当OrderedDict中的代码尝试使用__root时,您仍然会调用覆盖进行调用!要解决此问题,您只需将这些方法中的一些调用转发到您的dict,而不是所有

那个"一些"很复杂,很难做对。一个更简单的解决方案是封装和委托给OrderedDict而不是继承:

class AttrDict(object):
    def __init__(self, *args, **kwargs):
        self._od = OrderedDict(*args, **kwargs)
    def __getattr__(self, name):
        return self._od[name]
    def __setattr__(self, name, value):
        if name == '_od':
            self.__dict__['_od'] = value
        else:
            self._od[name] = value
    def __delattr__(self, name):
        del self._od[name]

或者,更简单地说,只需使用OrderedDict作为__dict__ - 这是元类的范例,但如果您想快速和肮脏,可以在__init__中执行此操作或__new__代替。


后面的解决方案只会让你成为一个普通的对象,其中的属性恰好是有序的,而不是映射。但是转发映射方法也很容易。实施__getitem____setitem____delitem____iter____len__转发至self._od,并继承collections.MutableMapping以填写在API的其余部分。

现在,如果你回去看看" AttrDict"在PyPI上点击实施,你会发现这正是他们所做的一切:他们将__getattr____getitem__转发给OrderedDict的__getitem__,等等。