You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

cache.py 4.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import pickle
  2. from os import PathLike
  3. from pathlib import Path
  4. from typing import Callable, Generic, Hashable, TypeVar, Union
  5. from .logger import print_log
  6. K = TypeVar("K")
  7. T = TypeVar("T")
  8. PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
  9. class Cache(Generic[K, T]):
  10. def __init__(
  11. self,
  12. func: Callable[[K], T],
  13. cache: bool,
  14. cache_file: Union[None, str, PathLike],
  15. key_func: Callable[[K], Hashable] = lambda x: x,
  16. max_size: int = 4096,
  17. ):
  18. """Create cache
  19. :param func: Function this cache evaluates
  20. :param cache: If true, do in memory caching.
  21. :param cache_root: If not None, cache to files at the provided path.
  22. :param key_func: Convert the key into a hashable object if needed
  23. """
  24. self.func = func
  25. self.key_func = key_func
  26. self.cache = cache
  27. if cache is True or cache_file is not None:
  28. print_log("Caching is activated", logger="current")
  29. self._init_cache(cache_file, max_size)
  30. self.first = self.get_from_dict
  31. else:
  32. self.first = self.func
  33. def __getitem__(self, item: K, *args) -> T:
  34. return self.first(item, *args)
  35. def invalidate(self):
  36. """Invalidate entire cache."""
  37. self.cache_dict.clear()
  38. if self.cache_file:
  39. for p in self.cache_root.iterdir():
  40. p.unlink()
  41. def _init_cache(self, cache_file, max_size):
  42. self.cache = True
  43. self.cache_dict = dict()
  44. self.hits, self.misses, self.maxsize = 0, 0, max_size
  45. self.full = False
  46. self.root = [] # root of the circular doubly linked list
  47. self.root[:] = [self.root, self.root, None, None]
  48. if cache_file is not None:
  49. with open(cache_file, "rb") as f:
  50. cache_dict_from_file = pickle.load(f)
  51. self.maxsize += len(cache_dict_from_file)
  52. print_log(
  53. f"Max size of the cache has been enlarged to {self.maxsize}.", logger="current"
  54. )
  55. for cache_key, result in cache_dict_from_file.items():
  56. last = self.root[PREV]
  57. link = [last, self.root, cache_key, result]
  58. last[NEXT] = self.root[PREV] = self.cache_dict[cache_key] = link
  59. def get(self, item: K, *args) -> T:
  60. return self.first(item, *args)
  61. def get_from_dict(self, item: K, *args) -> T:
  62. """Implements dict based cache."""
  63. cache_key = (self.key_func(item), *args)
  64. link = self.cache_dict.get(cache_key)
  65. if link is not None:
  66. # Move the link to the front of the circular queue
  67. link_prev, link_next, _key, result = link
  68. link_prev[NEXT] = link_next
  69. link_next[PREV] = link_prev
  70. last = self.root[PREV]
  71. last[NEXT] = self.root[PREV] = link
  72. link[PREV] = last
  73. link[NEXT] = self.root
  74. self.hits += 1
  75. return result
  76. self.misses += 1
  77. result = self.func(item, *args)
  78. if self.full:
  79. # Use the old root to store the new key and result.
  80. oldroot = self.root
  81. oldroot[KEY] = cache_key
  82. oldroot[RESULT] = result
  83. # Empty the oldest link and make it the new root.
  84. self.root = oldroot[NEXT]
  85. oldkey = self.root[KEY]
  86. oldresult = self.root[RESULT]
  87. self.root[KEY] = self.root[RESULT] = None
  88. # Now update the cache dictionary.
  89. del self.cache_dict[oldkey]
  90. self.cache_dict[cache_key] = oldroot
  91. else:
  92. # Put result in a new link at the front of the queue.
  93. last = self.root[PREV]
  94. link = [last, self.root, cache_key, result]
  95. last[NEXT] = self.root[PREV] = self.cache_dict[cache_key] = link
  96. if isinstance(self.maxsize, int):
  97. self.full = len(self.cache_dict) >= self.maxsize
  98. return result

An efficient Python toolkit for Abductive Learning (ABL), a novel paradigm that integrates machine learning and logical reasoning in a unified framework.