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

Python:尝试创建包含有限MRU条目的dict,python,dictionary,mru,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

我试图创建一个
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)
现在从输出来看,我在实现
\uu getitem\uuuuuuuuu
函数方面似乎有点幼稚,因为
\uuuuuu repr\uuuuuu
对于。。。在
(我猜在这里,调用
\uuuuu iter\uuuuuuu
,然后调用
\uuuu getitem\uuuuuuu
)会导致第一个项目作为MRU移动到最后一个,但无法继续,因为迭代器没有“下一个”项目,因为它现在指向最后一个元素。但我不确定我能做些什么来解决这个问题。我应该重新实施吗

我不知道如何区分用户的调用
\uuuu getitem\uuuu
和内部调用。当然,一个解决方法是让用户使用一个
find()
方法来完成移动到结尾的操作,但是我真的希望能够使用常规语法
d[k]


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

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

实际的
\uuu iter\uuu
方法直接在内部结构上循环,即维护项目顺序的双链接列表。它永远不会直接使用
\uuu getitem\uu
,而只是从链表返回键

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

那里有一个
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()
方法的问题
orderedict
重用的
.items()
方法,该方法返回一个实例;看

你必须改变这种行为:

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()
方法执行相同的操作。

请您将问题解释得更清楚一些;您从当前测试中获得的输出与您想要/期望的结果有何不同?@jornsharpe:
\uuu getitem\uuu
修改顺序。。因此,在迭代时,第一个项目移动到最后一个项目,迭代明显中断。在内部,
orderedict
使用链式列表<代码>\uuuuuuuuuuuuuuuuuuuuuuuuuu直接遍历链表,没有调用
\uuuuuuu getitem\uuuuuuuuu
。啊,
.items()
重用
可变映射.items()
,返回一个
ItemsView()
对象,该对象在自定义类上使用
\uuuuu getitem
。替换为不使用
\uuuu getitem\uuuu
而是使用其他内部方法。现在我更新了答案,因为我有时间实际解析您的测试套件。供将来参考:请尝试将示例代码简化为能够立即演示问题的代码。您的大多数测试套件都与您的问题无关。您好,非常感谢您的详细回复,并对测试套件中的额外内容表示抱歉。现在我明白了我必须重新实现
ItemsView
以避免使用
[]
\uuu getitem\uuuu
并改为使用
.get()
,从您提供的
集合.abc
链接可以清楚地看出,我也必须重新实现
值view
,但鉴于
键视图
似乎没有使用
[]
我不需要重新实现它,对吗?好吧,我在Martijn给出的上面的代码的基础上所做的只是将一个
,ValuesView
添加到collections.abc import行的
,并在模块级添加一个
mruditvaluesview
,替换
ValuesView
方法中的
.get()
,并将
.values()
添加到
mrudit
中,它现在可以工作了。非常感谢Martijn!对了,
KeysView
确实不会触发
\uGetItem\uuuuu
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)