2013-03-09 54 views
6

我subclasssing OrderedDict(Cpython,2.7.3)来表示一个数据文件。 __getitem__从数据文件中拉出一个字段,并将其设置在当前实例上,类似于我在下面发布的代码。现在我想重写__contains__以返回True,如果该字段位于字典中或磁盘上的文件中,因为它可以以任何方式读取。然而,这似乎打破了OrderedDict检查它的关键的能力。为什么覆盖__contains__打破OrderedDict.keys?

from collections import OrderedDict 

dictclass = OrderedDict 

class Foo(dictclass): 
    def __getitem__(self,key): 
     try: 
      return dictclass.__getitem__(self,key) 
     except KeyError: 
      pass 

     data = key*2 
     self[key] = data 
     return data 

    def __contains__(self,whatever): 
     return dictclass.__contains__(self,whatever) or 'bar' in whatever 

a = Foo() 
print a['bar'] 
print a.keys() 

如果你运行上面的代码,你会得到这样的输出:

barbar 
[] 

需要注意的是,如果你在上面的代码更改dictclass = dict,它似乎仍然工作(给下面的输出) 。

barbar 
['bar'] 

我在做可怕的错误吗?

+0

我正在阅读[来源](http://hg.python.org/cpython/file/2.7/Lib/collections.py),我仍然很难找出这一个...... – mgilson 2013-03-09 22:36:00

+0

我这样做,我想你的问题在哪里:看看'__setitem__'和'__iter__'。 – 2013-03-09 22:38:43

+0

@ A.Rodas - 是的,那就是我在找的地方。也许我太累了,但我很难保持所有的逻辑。 – mgilson 2013-03-09 22:43:18

回答

6

Foo.__contains__没有定义

a['bar'] 

电话Foo.__getitem__,其执行

self[key] = data 

这就要求OrderedDict.__setitem__,它的定义是这样的:

def __setitem__(self, key, value, PREV=0, NEXT=1, dict_setitem=dict.__setitem__): 
    'od.__setitem__(i, y) <==> od[i]=y' 
    # Setting a new item creates a new link at the end of the linked list, 
    # and the inherited dictionary is updated with the new key/value pair. 
    if key not in self: 
     root = self.__root 
     last = root[PREV] 
     last[NEXT] = root[PREV] = self.__map[key] = [last, root, key] 
    dict_setitem(self, key, value) 

由于Foo.__contains__未定义,

if key not in self: 

为真。因此,密钥已正确添加到self.__rootself.__map

Foo.__contains__定义

if key not in self: 

如果假。所以密钥没有正确添加到self.__rootself.__mapFoo.__contains__有效的傻瓜OrderedDict.__setitem__认为'bar'密钥已被添加。


我发现它有助于用下面的代码打(添加在__setitem__打印报表__iter__):

from collections import OrderedDict 

dictclass = OrderedDict 

class Foo(dictclass): 
    def __getitem__(self,key): 
     try: 
      return dictclass.__getitem__(self,key) 
     except KeyError: 
      pass 

     data = key*2 
     self[key] = data 
     return data 

    def __contains__(self,whatever): 
     print('contains: {}'.format(whatever)) 
     return dictclass.__contains__(self,whatever) or 'bar' in whatever 

    def __setitem__(self, key, value, PREV=0, NEXT=1, dict_setitem=dict.__setitem__): 
     'od.__setitem__(i, y) <==> od[i]=y' 
     # Setting a new item creates a new link at the end of the linked list, 
     # and the inherited dictionary is updated with the new key/value pair. 
     print('key not in self: {}'.format(key not in self)) 
     if key not in self: 
      root = self._OrderedDict__root 
      last = root[PREV] 
      last[NEXT] = root[PREV] = self._OrderedDict__map[key] = [last, root, key] 
     dict_setitem(self, key, value) 

    def __iter__(self): 
     'od.__iter__() <==> iter(od)' 
     # Traverse the linked list in order. 
     NEXT, KEY = 1, 2 

     root = self._OrderedDict__root 
     curr = root[NEXT] 
     print('curr: {}'.format(curr)) 
     print('root: {}'.format(root)) 
     print('curr is not root: {}'.format(curr is not root)) 

     while curr is not root: 
      yield curr[KEY] 
      curr = curr[NEXT] 

a = Foo() 
print a['bar'] 
# barbar 

print a.keys() 
# ['bar'] 

注意,您可以通过使Foo的一个子类避免这个问题collections.MutableMapping并将其大部分行为委托给OrderedDict属性:

import collections 
dictclass = collections.OrderedDict 

class Foo(collections.MutableMapping): 
    def __init__(self, *args, **kwargs): 
     self._data = dictclass(*args, **kwargs) 
    def __setitem__(self, key, value): 
     self._data[key] = value 
    def __delitem__(self, key): 
     del self._data[key] 
    def __iter__(self): 
     return iter(self._data) 
    def __len__(self): 
     return len(self._data) 

    def __getitem__(self,key): 
     try: 
      return self._data[key] 
     except KeyError: 
      pass 

     data = key*2 
     self[key] = data 
     return data 

    def __contains__(self,whatever): 
     return dictclass.__contains__(self,whatever) or 'bar' in whatever 

其产生

a = Foo() 
print a['bar'] 
# barbar 

print a.keys() 
# ['bar'] 
即使定义 __contains__

+0

谢谢。就这样 - 我花了太多时间专注于'self .__ root'以及它如何被初始化 - 思考 - 'self .__ root = root = []; root [:] = [root,root,None]'怎么回事?!??! :X – mgilson 2013-03-09 22:45:19

+0

我的办法是非常低调 - 它通常包含大量的打印语句。 :) – unutbu 2013-03-09 23:00:09

2

什么打破你的代码是or 'bar' in whatever。如果你删除它,它会像你提到的变化dictclass = dict一样工作。

__setitem__实施OrderedDict是这样的:

def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 
    'od.__setitem__(i, y) <==> od[i]=y' 
    # Setting a new item creates a new link at the end of the linked list, 
    # and the inherited dictionary is updated with the new key/value pair. 
    if key not in self: 
     root = self.__root 
     last = root[0] 
     last[1] = root[0] = self.__map[key] = [last, root, key] 
    return dict_setitem(self, key, value) 

因此,与self["bar"] = "barbar",病情应该是假的,但它甚至插入任何项目之前为True。因此,关键ISN”加到self.__root其在OrderedDict.__iter__使用:

def __iter__(self): 
    'od.__iter__() <==> iter(od)' 
    # Traverse the linked list in order. 
    root = self.__root 
    curr = root[1]         # start at the first node 
    while curr is not root: 
     yield curr[2]        # yield the curr[KEY] 
     curr = curr[1]        # move to next node 

由于用于检索值的代码使用该迭代和self.__root不含"bar",该具体键不能在值被返回。

+0

是的,就是这样。谢谢。 +1。当然,在我的代码中,任何'或'栏'是更复杂的东西,我不想*删除。我认为围绕OrderedDict进行正确的工作将会非常困难。我想我会继承一个常规字典并保留一个单独的'__order'列表。 – mgilson 2013-03-09 22:49:10

+0

@mgilson:也许让'Foo' *有-a *'OrderedDict',而不是* be-a *'OrderedDict'? – unutbu 2013-03-09 23:12:13

+0

@unutbu - 我希望它是一个映射类型,以便我可以解压缩它...我可以自己跟踪订单。 – mgilson 2013-03-09 23:16:08