Python:尝试创建包含有限MRU条目的dict

时间:2014-05-17 10:53:42

标签: python dictionary mru

我正在尝试创建一个dict,其中只包含有限数量的MRU条目(用于帮助缓存我通过ctypes调用的昂贵C函数的输出)。这是代码:

from collections import OrderedDict

class MRUDict(OrderedDict):

    def __init__(self, capacity = 64):
        super().__init__()
        self.__checkAndSetCapacity(capacity)

    def capacity(self):
        return self.__capacity

    def setCapacity(self, capacity):
        self.__checkAndSetCapacity(capacity)
        for i in range(len(self) - capacity):
            self.__evict() # will execute only if len > capacity

    def __getitem__(self, key):
        value = super().__getitem__(key)
        # if above raises IndexError, next line won't execute
        print("Moving key {} to last i.e. MRU position".format(key))
        super().move_to_end(key)
        return value

    def __setitem__(self, key, value):
        if key in self:
            super().move_to_end(key)
        else: # new key
            if len(self) == self.__capacity:
                self.__evict()
        super().__setitem__(key, value)

    def __evict(self):
        key, value = self.popitem(last = False) # pop first i.e. oldest item
        print("Capacity exceeded. Evicting ({}, {})".format(key, value))

    def __checkAndSetCapacity(self, capacity):
        if not isinstance(capacity, int):
            raise TypeError("Capacity should be an int.")
        if capacity == 0:
            raise ValueError("Capacity should not be zero.")
        self.__capacity = capacity

......这是测试代码:

def printkeys(d):
    print("Current keys in order:", tuple(d)) # here d means d.keys()
    print()

from mrudict import MRUDict
print("Creating MRUDict with capacity 5.")
d = MRUDict(5)
print("Adding keys 0 to 7 with values:")
for i in range(8): d[i] = i + 0.1
printkeys(d)

print("Calling str on object:")
print(d) # test of default __repr__ (since probably __str__ is the same here)
printkeys(d)

print("Accessing existing key 4:")
print(4, d[4]) # test of __getitem__
printkeys(d)

try:
    print("Accessing non-existing key 20:")
    print(20, d[20]) # test of __getitem__
except:
    print("Caught exception: key does not exist.")
printkeys(d)

print("Updating value of existing key 6:")
d[6] = 6.6 # test of __setitem__ with existing key
printkeys(d)

print("Adding new key, value pair:")
d[10] = 10.1 # test of __setitem__ with non-existing key
printkeys(d)

print("Testing for presence of key 3:")
print(3 in d)
printkeys(d)

print("Trying to loop over the items:")
for k in d: print(k, d[k])
printkeys(d)

print("Trying to loop over the items:")
for k, v in d.items(): print(k, v)
printkeys(d)

现在从输出中看来我在实现__getitem__函数方面有点天真,因为__repr__for ... in(我在这里猜测,调用{{1}然后__iter__)导致第一个项目作为MRU移动到最后一个项目,但不能继续进行,因为迭代器没有“下一个”项目,因为它现在指向最后一个元素。但我不知道我能做些什么来解决这个问题。我应该重新实现__getitem__吗?

我不确定如何区分用户的呼叫__iter__和内部呼叫。当然,一种解决方法是让用户使用__getitem__方法来执行移动到结束的操作,但我真的希望能够使用常规语法find()

请告知如何解决这个问题。谢谢!

1 个答案:

答案 0 :(得分:1)

对于这些行为的复杂变化,研究OrderedDict source code是值得的。

实际的__iter__方法直接在内部结构上循环,内部结构是维护项目顺序的双向链接列表。它永远不会直接使用__getitem__,而只是从链表中返回密钥。

您遇到的实际问题是,直接在循环时访问项目

for k in d: print(k, d[k])

那里有一个d[k]; 访问将项目5从开始移动到结束。这会更新链接列表,因此在询问下一个项目时,curr.next引用现在是根,迭代将停止。

解决方法是不要那样做。添加专用方法来访问项目而不触发MRU更新。或者您可以重复使用dict.get(),例如:

>>> for k in d: print(k, d.get(k))
... 
5 5.1
7 7.1
4 4.1
6 6.6
10 10.1

会遇到.items()方法的问题; OrderedDict重新使用collections.abc.MutableMapping.items()方法,该方法返回collections.abc.ItemsView()个实例;请参阅collections.abc source code

您必须替换该行为:

from collections.abc import ItemsView


class MRUDictItemsView(ItemsView):
    def __contains__(self, item):
        key, value = item
        v = self._mapping.get(key, object())
        return v == value

    def __iter__(self):
        for key in self._mapping:
            yield (key, self._mapping.get(key))


class MRUDict(OrderedDict):
    # ...

    def items(self):
        return MRUDictItemsView(self)

您必须对.keys().values()方法执行相同操作。