diff --git a/cachetools/cache.py b/cachetools/cache.py index f0f8d5c..95bf664 100644 --- a/cachetools/cache.py +++ b/cachetools/cache.py @@ -1,17 +1,34 @@ import collections +class _DefaultSize(object): + def __getitem__(self, _): + return 1 + + def __setitem__(self, _, value): + assert value == 1 + + def __delitem__(self, _): + pass + + def pop(self, _): + return 1 + + class Cache(collections.MutableMapping): """Mutable mapping to serve as a simple cache or cache base class.""" + __size = _DefaultSize() + def __init__(self, maxsize, missing=None, getsizeof=None): - self.__data = dict() - self.__currsize = 0 - self.__maxsize = maxsize if missing: self.__missing = missing if getsizeof: self.__getsizeof = getsizeof + self.__size = dict() + self.__data = dict() + self.__currsize = 0 + self.__maxsize = maxsize def __repr__(self): return '%s(%r, maxsize=%d, currsize=%d)' % ( @@ -23,37 +40,38 @@ def __repr__(self): def __getitem__(self, key): try: - return self.__data[key][0] + return self.__data[key] except KeyError: return self.__missing__(key) def __setitem__(self, key, value): - data = self.__data maxsize = self.__maxsize size = self.getsizeof(value) if size > maxsize: raise ValueError('value too large') - if key not in data or data[key][1] < size: + if key not in self.__data or self.__size[key] < size: while self.__currsize + size > maxsize: self.popitem() - if key in data: - diffsize = size - data[key][1] + if key in self.__data: + diffsize = size - self.__size[key] else: diffsize = size - data[key] = (value, size) + self.__data[key] = value + self.__size[key] = size self.__currsize += diffsize def __delitem__(self, key): - _, size = self.__data.pop(key) + size = self.__size.pop(key) + del self.__data[key] self.__currsize -= size def __contains__(self, key): return key in self.__data def __missing__(self, key): - self.__setitem__(key, self.__missing(key)) - # return value as stored in data - return self.__data[key][0] + value = self.__missing(key) + self.__setitem__(key, value) + return value def __iter__(self): return iter(self.__data) diff --git a/cachetools/lru.py b/cachetools/lru.py index 1daf6d4..f18f0b2 100644 --- a/cachetools/lru.py +++ b/cachetools/lru.py @@ -14,6 +14,11 @@ def __getstate__(self): def __setstate__(self, state): self.key, = state + def insert(self, next): + self.next = next + self.prev = prev = next.prev + prev.next = next.prev = self + def unlink(self): next = self.next prev = self.prev @@ -26,7 +31,7 @@ class LRUCache(Cache): def __init__(self, maxsize, missing=None, getsizeof=None): Cache.__init__(self, maxsize, missing, getsizeof) - root = self.__root = _Link() + self.__root = root = _Link() root.prev = root.next = root self.__links = {} @@ -42,13 +47,8 @@ def __repr__(self, cache_getitem=Cache.__getitem__): def __getitem__(self, key, cache_getitem=Cache.__getitem__): value = cache_getitem(self, key) link = self.__links[key] - next = link.next - prev = link.prev - prev.next = next - next.prev = prev - link.next = root = self.__root - link.prev = tail = root.prev - tail.next = root.prev = link + link.unlink() + link.insert(self.__root) return value def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): @@ -56,18 +56,17 @@ def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): try: link = self.__links[key] except KeyError: - link = self.__links[key] = _Link() # TODO: exception safety? + link = self.__links[key] = _Link() else: link.unlink() - link.key = key # always update - link.next = root = self.__root - link.prev = tail = root.prev - tail.next = root.prev = link + link.key = key + link.insert(self.__root) def __delitem__(self, key, cache_delitem=Cache.__delitem__): cache_delitem(self, key) - self.__links[key].unlink() - del self.__links[key] + links = self.__links + links[key].unlink() + del links[key] def __getstate__(self): state = self.__dict__.copy() @@ -92,6 +91,6 @@ def popitem(self): root = self.__root link = root.next if link is root: - raise KeyError('cache is empty: %r' % self.__links) + raise KeyError('%s is empty' % self.__class__.__name__) key = link.key return (key, self.pop(key)) diff --git a/cachetools/ttl.py b/cachetools/ttl.py index 1fe1141..57c8f9c 100644 --- a/cachetools/ttl.py +++ b/cachetools/ttl.py @@ -21,17 +21,36 @@ def __getstate__(self): def __setstate__(self, state): self.key, self.expire, self.size = state - def unlink(self): - ttl_next = self.ttl_next - ttl_prev = self.ttl_prev - ttl_prev.ttl_next = ttl_next - ttl_next.ttl_prev = ttl_prev + def insert_lru(self, next): + self.lru_next = next + self.lru_prev = prev = next.lru_prev + prev.lru_next = next.lru_prev = self + + def insert_ttl(self, next): + self.ttl_next = next + self.ttl_prev = prev = next.ttl_prev + prev.ttl_next = next.ttl_prev = self + def insert(self, next): + self.insert_lru(next) + self.insert_ttl(next) + + def unlink_lru(self): lru_next = self.lru_next lru_prev = self.lru_prev lru_prev.lru_next = lru_next lru_next.lru_prev = lru_prev + def unlink_ttl(self): + ttl_next = self.ttl_next + ttl_prev = self.ttl_prev + ttl_prev.ttl_next = ttl_next + ttl_next.ttl_prev = ttl_prev + + def unlink(self): + self.unlink_lru() + self.unlink_ttl() + class _NestedTimer(object): @@ -70,7 +89,7 @@ class TTLCache(Cache): def __init__(self, maxsize, ttl, timer=time.time, missing=None, getsizeof=None): Cache.__init__(self, maxsize, missing, getsizeof) - root = self.__root = _Link() + self.__root = root = _Link() root.ttl_prev = root.ttl_next = root root.lru_prev = root.lru_next = root self.__links = {} @@ -94,13 +113,8 @@ def __getitem__(self, key, link = self.__links[key] if link.expire < time: return cache_missing(self, key) - next = link.lru_next - prev = link.lru_prev - prev.lru_next = next - next.lru_prev = prev - link.lru_next = root = self.__root - link.lru_prev = tail = root.lru_prev - tail.lru_next = root.lru_prev = link + link.unlink_lru() + link.insert_lru(self.__root) return value def __setitem__(self, key, value, @@ -112,28 +126,21 @@ def __setitem__(self, key, value, try: link = self.__links[key] except KeyError: - link = self.__links[key] = _Link() # TODO: exception safety? + link = self.__links[key] = _Link() else: link.unlink() link.key = key link.expire = time + self.__ttl link.size = cache_getsizeof(self, value) - link.ttl_next = root = self.__root - link.ttl_prev = tail = root.ttl_prev - tail.ttl_next = root.ttl_prev = link - link.lru_next = root - link.lru_prev = tail = root.lru_prev - tail.lru_next = root.lru_prev = link - - def __delitem__(self, key, - cache_contains=Cache.__contains__, - cache_getitem=Cache.__getitem__, - cache_delitem=Cache.__delitem__): + link.insert(self.__root) + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): with self.__timer as time: self.expire(time) cache_delitem(self, key) - self.__links[key].unlink() - del self.__links[key] + links = self.__links + links[key].unlink() + del links[key] def __contains__(self, key): with self.__timer as time: @@ -210,10 +217,11 @@ def expire(self, time=None): time = self.__timer() root = self.__root head = root.ttl_next + links = self.__links cache_delitem = Cache.__delitem__ while head is not root and head.expire < time: cache_delitem(self, head.key) - del self.__links[head.key] + del links[head.key] next = head.ttl_next head.unlink() head = next @@ -228,7 +236,7 @@ def popitem(self): root = self.__root link = root.lru_next if link is root: - raise KeyError('cache is empty: %r' % self.__links) + raise KeyError('%s is empty' % self.__class__.__name__) key = link.key return (key, self.pop(key))