lrudict.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import collections, time
  2. # http://code.activestate.com/recipes/580644-lru-dictionary/
  3. class LRUDict(collections.OrderedDict):
  4. '''An dict that can discard least-recently-used items, either by maximum capacity
  5. or by time to live.
  6. An item's ttl is refreshed (aka the item is considered "used") by direct access
  7. via [] or get() only, not via iterating over the whole collection with items()
  8. for example.
  9. Expired entries only get purged after insertions or changes. Either call purge()
  10. manually or check an item's ttl with ttl() if that's unacceptable.
  11. '''
  12. def __init__(self, *args, maxduration=None, maxsize=128, **kwargs):
  13. '''Same arguments as OrderedDict with these 2 additions:
  14. maxduration: number of seconds entries are kept. 0 or None means no timelimit.
  15. maxsize: maximum number of entries being kept.'''
  16. super().__init__(*args, **kwargs)
  17. self.maxduration = maxduration
  18. self.maxsize = maxsize
  19. self.purge()
  20. def purge(self):
  21. """Removes expired or overflowing entries."""
  22. if self.maxsize:
  23. # pop until maximum capacity is reached
  24. overflowing = max(0, len(self) - self.maxsize)
  25. for _ in range(overflowing):
  26. self.popitem(last=False)
  27. if self.maxduration:
  28. # expiration limit
  29. limit = time.time() - self.maxduration
  30. # as long as there are still items in the dictionary
  31. while self:
  32. # look at the oldest (front)
  33. _, lru = next(iter(super().values()))
  34. # if it is within the timelimit, we're fine
  35. if lru > limit:
  36. break
  37. # otherwise continue to pop the front
  38. self.popitem(last=False)
  39. def __getitem__(self, key):
  40. # retrieve item
  41. value = super().__getitem__(key)[0]
  42. # update lru time
  43. super().__setitem__(key, (value, time.time()))
  44. self.move_to_end(key)
  45. return value
  46. def get(self, key, default=None):
  47. try:
  48. return self[key]
  49. except KeyError:
  50. return default
  51. def ttl(self, key):
  52. '''Returns the number of seconds this item will live.
  53. The item might still be deleted if maxsize is reached.
  54. The time to live can be negative, as for expired items
  55. that have not been purged yet.'''
  56. if self.maxduration:
  57. lru = super().__getitem__(key)[1]
  58. return self.maxduration - (time.time() - lru)
  59. def __setitem__(self, key, value):
  60. super().__setitem__(key, (value, time.time()))
  61. self.purge()
  62. def items(self):
  63. # remove ttl from values
  64. return ((k, v) for k, (v, _) in super().items())
  65. def values(self):
  66. # remove ttl from values
  67. return (v for v, _ in super().values())