6

我正在对 OrderedDict (Cpython, 2.7.3) 进行子类化来表示数据文件。 __getitem__从数据文件中提取一个字段并将其设置在当前实例上,类似于我在下面发布的代码。现在,如果该字段在字典中或在磁盘上的文件中,我想覆盖__contains__以返回,因为它可以通过任何一种方式读取。True但是,这似乎破坏OrderedDict了检查其密钥的能力。

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

我做错了什么可怕的事情吗?

4

2 回答 2

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.__rootand self.__map

什么时候Foo.__contains__定义

    if key not in self:

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


我发现使用以下代码很有帮助(在__setitem__and中添加打印语句__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__定义。

于 2013-03-09T22:39:24.693 回答
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"时,条件应该为 False,但即使在插入任何项目之前它也是 True。因此,密钥未添加到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",因此无法在值中返回此具体键。

于 2013-03-09T22:45:14.983 回答