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

lru_cache.py 7.5 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. try:
  2. from functools import lru_cache
  3. except ImportError:
  4. # backport of Python's 3.3 lru_cache, written by Raymond Hettinger and
  5. # licensed under MIT license, from:
  6. # <http://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/>
  7. # Should be removed when Django only supports Python 3.2 and above.
  8. from collections import namedtuple
  9. from functools import update_wrapper
  10. from threading import RLock
  11. _CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
  12. class _HashedSeq(list):
  13. __slots__ = 'hashvalue'
  14. def __init__(self, tup, hash=hash):
  15. self[:] = tup
  16. self.hashvalue = hash(tup)
  17. def __hash__(self):
  18. return self.hashvalue
  19. def _make_key(args, kwds, typed,
  20. kwd_mark = (object(),),
  21. fasttypes = {int, str, frozenset, type(None)},
  22. sorted=sorted, tuple=tuple, type=type, len=len):
  23. 'Make a cache key from optionally typed positional and keyword arguments'
  24. key = args
  25. if kwds:
  26. sorted_items = sorted(kwds.items())
  27. key += kwd_mark
  28. for item in sorted_items:
  29. key += item
  30. if typed:
  31. key += tuple(type(v) for v in args)
  32. if kwds:
  33. key += tuple(type(v) for k, v in sorted_items)
  34. elif len(key) == 1 and type(key[0]) in fasttypes:
  35. return key[0]
  36. return _HashedSeq(key)
  37. def lru_cache(maxsize=100, typed=False):
  38. """Least-recently-used cache decorator.
  39. If *maxsize* is set to None, the LRU features are disabled and the cache
  40. can grow without bound.
  41. If *typed* is True, arguments of different types will be cached separately.
  42. For example, f(3.0) and f(3) will be treated as distinct calls with
  43. distinct results.
  44. Arguments to the cached function must be hashable.
  45. View the cache statistics named tuple (hits, misses, maxsize, currsize) with
  46. f.cache_info(). Clear the cache and statistics with f.cache_clear().
  47. Access the underlying function with f.__wrapped__.
  48. See: https://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
  49. """
  50. # Users should only access the lru_cache through its public API:
  51. # cache_info, cache_clear, and f.__wrapped__
  52. # The internals of the lru_cache are encapsulated for thread safety and
  53. # to allow the implementation to change (including a possible C version).
  54. def decorating_function(user_function):
  55. cache = dict()
  56. stats = [0, 0] # make statistics updateable non-locally
  57. HITS, MISSES = 0, 1 # names for the stats fields
  58. make_key = _make_key
  59. cache_get = cache.get # bound method to lookup key or return None
  60. _len = len # localize the global len() function
  61. lock = RLock() # because linkedlist updates aren't threadsafe
  62. root = [] # root of the circular doubly linked list
  63. root[:] = [root, root, None, None] # initialize by pointing to self
  64. nonlocal_root = [root] # make updateable non-locally
  65. PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
  66. if maxsize == 0:
  67. def wrapper(*args, **kwds):
  68. # no caching, just do a statistics update after a successful call
  69. result = user_function(*args, **kwds)
  70. stats[MISSES] += 1
  71. return result
  72. elif maxsize is None:
  73. def wrapper(*args, **kwds):
  74. # simple caching without ordering or size limit
  75. key = make_key(args, kwds, typed)
  76. result = cache_get(key, root) # root used here as a unique not-found sentinel
  77. if result is not root:
  78. stats[HITS] += 1
  79. return result
  80. result = user_function(*args, **kwds)
  81. cache[key] = result
  82. stats[MISSES] += 1
  83. return result
  84. else:
  85. def wrapper(*args, **kwds):
  86. # size limited caching that tracks accesses by recency
  87. key = make_key(args, kwds, typed) if kwds or typed else args
  88. with lock:
  89. link = cache_get(key)
  90. if link is not None:
  91. # record recent use of the key by moving it to the front of the list
  92. root, = nonlocal_root
  93. link_prev, link_next, key, result = link
  94. link_prev[NEXT] = link_next
  95. link_next[PREV] = link_prev
  96. last = root[PREV]
  97. last[NEXT] = root[PREV] = link
  98. link[PREV] = last
  99. link[NEXT] = root
  100. stats[HITS] += 1
  101. return result
  102. result = user_function(*args, **kwds)
  103. with lock:
  104. root, = nonlocal_root
  105. if key in cache:
  106. # getting here means that this same key was added to the
  107. # cache while the lock was released. since the link
  108. # update is already done, we need only return the
  109. # computed result and update the count of misses.
  110. pass
  111. elif _len(cache) >= maxsize:
  112. # use the old root to store the new key and result
  113. oldroot = root
  114. oldroot[KEY] = key
  115. oldroot[RESULT] = result
  116. # empty the oldest link and make it the new root
  117. root = nonlocal_root[0] = oldroot[NEXT]
  118. oldkey = root[KEY]
  119. oldvalue = root[RESULT]
  120. root[KEY] = root[RESULT] = None
  121. # now update the cache dictionary for the new links
  122. del cache[oldkey]
  123. cache[key] = oldroot
  124. else:
  125. # put result in a new link at the front of the list
  126. last = root[PREV]
  127. link = [last, root, key, result]
  128. last[NEXT] = root[PREV] = cache[key] = link
  129. stats[MISSES] += 1
  130. return result
  131. def cache_info():
  132. """Report cache statistics"""
  133. with lock:
  134. return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache))
  135. def cache_clear():
  136. """Clear the cache and cache statistics"""
  137. with lock:
  138. cache.clear()
  139. root = nonlocal_root[0]
  140. root[:] = [root, root, None, None]
  141. stats[:] = [0, 0]
  142. wrapper.__wrapped__ = user_function
  143. wrapper.cache_info = cache_info
  144. wrapper.cache_clear = cache_clear
  145. return update_wrapper(wrapper, user_function)
  146. return decorating_function