Skip to content

Commit

Permalink
Fix #56: Refactor Cache base class.
Browse files Browse the repository at this point in the history
  • Loading branch information
tkem committed Oct 25, 2015
1 parent 41d521b commit 44cc6e4
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 58 deletions.
44 changes: 31 additions & 13 deletions 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)' % (
Expand All @@ -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)
Expand Down
31 changes: 15 additions & 16 deletions cachetools/lru.py
Expand Up @@ -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
Expand All @@ -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 = {}

Expand All @@ -42,32 +47,26 @@ 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__):
cache_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 # 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()
Expand All @@ -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))
66 changes: 37 additions & 29 deletions cachetools/ttl.py
Expand Up @@ -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):

Expand Down Expand Up @@ -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 = {}
Expand All @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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))

Expand Down

0 comments on commit 44cc6e4

Please sign in to comment.