我正在尝试创建一个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()
。
请告知如何解决这个问题。谢谢!
答案 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()
方法执行相同操作。