Changed FiniteCache which had a bug where calling items() would make it recurse forever with a new LRUCache implementation.
This commit is contained in:
parent
2e3cfcbd6d
commit
e7cd4012bc
@ -3,29 +3,9 @@ from ..GraphicsScene import GraphicsScene
|
||||
from ..Point import Point
|
||||
from .. import functions as fn
|
||||
import weakref
|
||||
from ..pgcollections import OrderedDict
|
||||
import operator, sys
|
||||
import operator
|
||||
from pyqtgraph.lru_cache import LRUCache
|
||||
|
||||
class FiniteCache(OrderedDict):
|
||||
"""Caches a finite number of objects, removing
|
||||
least-frequently used items."""
|
||||
def __init__(self, length):
|
||||
self._length = length
|
||||
OrderedDict.__init__(self)
|
||||
|
||||
def __setitem__(self, item, val):
|
||||
self.pop(item, None) # make sure item is added to end
|
||||
OrderedDict.__setitem__(self, item, val)
|
||||
while len(self) > self._length:
|
||||
del self[list(self.keys())[0]]
|
||||
|
||||
def __getitem__(self, item):
|
||||
val = OrderedDict.__getitem__(self, item)
|
||||
del self[item]
|
||||
self[item] = val ## promote this key
|
||||
return val
|
||||
|
||||
|
||||
|
||||
class GraphicsItem(object):
|
||||
"""
|
||||
@ -38,7 +18,7 @@ class GraphicsItem(object):
|
||||
|
||||
The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task.
|
||||
"""
|
||||
_pixelVectorGlobalCache = FiniteCache(100)
|
||||
_pixelVectorGlobalCache = LRUCache(100, 70)
|
||||
|
||||
def __init__(self, register=True):
|
||||
if not hasattr(self, '_qtBaseClass'):
|
||||
|
116
pyqtgraph/lru_cache.py
Normal file
116
pyqtgraph/lru_cache.py
Normal file
@ -0,0 +1,116 @@
|
||||
import operator
|
||||
import sys
|
||||
import itertools
|
||||
|
||||
|
||||
_IS_PY3 = sys.version_info[0] == 3
|
||||
|
||||
class LRUCache(object):
|
||||
'''
|
||||
This LRU cache should be reasonable for short collections (until around 100 items), as it does a
|
||||
sort on the items if the collection would become too big (so, it is very fast for getting and
|
||||
setting but when its size would become higher than the max size it does one sort based on the
|
||||
internal time to decide which items should be removed -- which should be Ok if the resize_to
|
||||
isn't too close to the max_size so that it becomes an operation that doesn't happen all the
|
||||
time).
|
||||
'''
|
||||
|
||||
def __init__(self, max_size=100, resize_to=70):
|
||||
'''
|
||||
:param int max_size:
|
||||
This is the maximum size of the cache. When some item is added and the cache would become
|
||||
bigger than this, it's resized to the value passed on resize_to.
|
||||
|
||||
:param int resize_to:
|
||||
When a resize operation happens, this is the size of the final cache.
|
||||
'''
|
||||
assert resize_to < max_size
|
||||
self.max_size = max_size
|
||||
self.resize_to = resize_to
|
||||
self._counter = 0
|
||||
self._dict = {}
|
||||
if _IS_PY3:
|
||||
self._next_time = itertools.count(0).__next__
|
||||
else:
|
||||
self._next_time = itertools.count(0).next
|
||||
|
||||
def __getitem__(self, key):
|
||||
item = self._dict[key]
|
||||
item[2] = self._next_time()
|
||||
return item[1]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dict)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
item = self._dict.get(key)
|
||||
if item is None:
|
||||
if len(self._dict) + 1 > self.max_size:
|
||||
self._resize_to()
|
||||
|
||||
item = [key, value, self._next_time()]
|
||||
self._dict[key] = item
|
||||
else:
|
||||
item[1] = value
|
||||
item[2] = self._next_time()
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._dict[key]
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def clear(self):
|
||||
self._dict.clear()
|
||||
|
||||
if _IS_PY3:
|
||||
def values(self):
|
||||
return [i[1] for i in self._dict.values()]
|
||||
|
||||
def keys(self):
|
||||
return [x[0] for x in self._dict.values()]
|
||||
|
||||
def _resize_to(self):
|
||||
ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resize_to]
|
||||
for i in ordered:
|
||||
del self._dict[i[0]]
|
||||
|
||||
def iteritems(self, access_time=False):
|
||||
'''
|
||||
:param bool access_time:
|
||||
If True sorts the returned items by the internal access time.
|
||||
'''
|
||||
if access_time:
|
||||
for x in sorted(self._dict.values(), key=operator.itemgetter(2)):
|
||||
yield x[0], x[1]
|
||||
else:
|
||||
for x in self._dict.items():
|
||||
yield x[0], x[1]
|
||||
|
||||
else:
|
||||
def values(self):
|
||||
return [i[1] for i in self._dict.itervalues()]
|
||||
|
||||
def keys(self):
|
||||
return [x[0] for x in self._dict.itervalues()]
|
||||
|
||||
|
||||
def _resize_to(self):
|
||||
ordered = sorted(self._dict.itervalues(), key=operator.itemgetter(2))[:self.resize_to]
|
||||
for i in ordered:
|
||||
del self._dict[i[0]]
|
||||
|
||||
def iteritems(self, access_time=False):
|
||||
'''
|
||||
:param bool access_time:
|
||||
If True sorts the returned items by the internal access time.
|
||||
'''
|
||||
if access_time:
|
||||
for x in sorted(self._dict.itervalues(), key=operator.itemgetter(2)):
|
||||
yield x[0], x[1]
|
||||
else:
|
||||
for x in self._dict.iteritems():
|
||||
yield x[0], x[1]
|
@ -5,4 +5,54 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')
|
||||
|
||||
## all tests should be defined with this class so we have the option to tweak it later.
|
||||
class TestCase(unittest.TestCase):
|
||||
pass
|
||||
|
||||
def testLRU(self):
|
||||
from pyqtgraph.lru_cache import LRUCache
|
||||
lru = LRUCache(2, 1)
|
||||
|
||||
def CheckLru():
|
||||
lru[1] = 1
|
||||
lru[2] = 2
|
||||
lru[3] = 3
|
||||
|
||||
self.assertEqual(2, len(lru))
|
||||
self.assertSetEqual(set([2, 3]), set(lru.keys()))
|
||||
self.assertSetEqual(set([2, 3]), set(lru.values()))
|
||||
|
||||
lru[2] = 2
|
||||
self.assertSetEqual(set([2, 3]), set(lru.values()))
|
||||
|
||||
lru[1] = 1
|
||||
self.assertSetEqual(set([2, 1]), set(lru.values()))
|
||||
|
||||
#Iterates from the used in the last access to others based on access time.
|
||||
self.assertEqual([(2, 2), (1, 1)], list(lru.iteritems(access_time=True)))
|
||||
lru[2] = 2
|
||||
self.assertEqual([(1, 1), (2, 2)], list(lru.iteritems(access_time=True)))
|
||||
|
||||
del lru[2]
|
||||
self.assertEqual([(1, 1), ], list(lru.iteritems(access_time=True)))
|
||||
|
||||
lru[2] = 2
|
||||
self.assertEqual([(1, 1), (2, 2)], list(lru.iteritems(access_time=True)))
|
||||
|
||||
_a = lru[1]
|
||||
self.assertEqual([(2, 2), (1, 1)], list(lru.iteritems(access_time=True)))
|
||||
|
||||
_a = lru[2]
|
||||
self.assertEqual([(1, 1), (2, 2)], list(lru.iteritems(access_time=True)))
|
||||
|
||||
self.assertEqual(lru.get(2), 2)
|
||||
self.assertEqual(lru.get(3), None)
|
||||
self.assertEqual([(1, 1), (2, 2)], list(lru.iteritems(access_time=True)))
|
||||
|
||||
lru.clear()
|
||||
self.assertEqual([], list(lru.iteritems()))
|
||||
|
||||
CheckLru()
|
||||
|
||||
# Check it twice...
|
||||
CheckLru()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user