2

我正在尝试创建一个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__and for ... in(我在这里猜测,调用__iter__then __getitem__)都会导致第一项作为 MRU 移动到最后一项,但无法继续进行,因为迭代器没有“下一个”项,因为它现在指向最后一个元素。但我不确定我能做些什么来解决这种情况。我应该重新实现__iter__吗?

我不确定如何区分用户的呼叫__getitem__和内部呼叫。当然,一种解决方法是让用户使用一种find()方法来完成移动到结束的事情,但我真的很希望能够使用常规语法d[k]

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

4

1 回答 1

2

对于此类复杂的行为变化,研究OrderedDict源代码是值得的。

实际的方法直接__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源代码

您必须替换该行为:

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()and.values()方法做同样的事情。

于 2014-05-17T11:42:45.517 回答