1"""Extensible memoizing decorator helpers."""
2
3
4def _cached_locked_info(func, cache, key, lock, info):
5 hits = misses = 0
6
7 def wrapper(*args, **kwargs):
8 nonlocal hits, misses
9 k = key(*args, **kwargs)
10 with lock:
11 try:
12 result = cache[k]
13 hits += 1
14 return result
15 except KeyError:
16 misses += 1
17 v = func(*args, **kwargs)
18 with lock:
19 try:
20 # in case of a race, prefer the item already in the cache
21 return cache.setdefault(k, v)
22 except ValueError:
23 return v # value too large
24
25 def cache_clear():
26 nonlocal hits, misses
27 with lock:
28 cache.clear()
29 hits = misses = 0
30
31 def cache_info():
32 with lock:
33 return info(hits, misses)
34
35 wrapper.cache_clear = cache_clear
36 wrapper.cache_info = cache_info
37 return wrapper
38
39
40def _cached_unlocked_info(func, cache, key, info):
41 hits = misses = 0
42
43 def wrapper(*args, **kwargs):
44 nonlocal hits, misses
45 k = key(*args, **kwargs)
46 try:
47 result = cache[k]
48 hits += 1
49 return result
50 except KeyError:
51 misses += 1
52 v = func(*args, **kwargs)
53 try:
54 cache[k] = v
55 except ValueError:
56 pass # value too large
57 return v
58
59 def cache_clear():
60 nonlocal hits, misses
61 cache.clear()
62 hits = misses = 0
63
64 wrapper.cache_clear = cache_clear
65 wrapper.cache_info = lambda: info(hits, misses)
66 return wrapper
67
68
69def _uncached_info(func, info):
70 misses = 0
71
72 def wrapper(*args, **kwargs):
73 nonlocal misses
74 misses += 1
75 return func(*args, **kwargs)
76
77 def cache_clear():
78 nonlocal misses
79 misses = 0
80
81 wrapper.cache_clear = cache_clear
82 wrapper.cache_info = lambda: info(0, misses)
83 return wrapper
84
85
86def _cached_locked(func, cache, key, lock):
87 def wrapper(*args, **kwargs):
88 k = key(*args, **kwargs)
89 with lock:
90 try:
91 return cache[k]
92 except KeyError:
93 pass # key not found
94 v = func(*args, **kwargs)
95 with lock:
96 try:
97 # in case of a race, prefer the item already in the cache
98 return cache.setdefault(k, v)
99 except ValueError:
100 return v # value too large
101
102 def cache_clear():
103 with lock:
104 cache.clear()
105
106 wrapper.cache_clear = cache_clear
107 return wrapper
108
109
110def _cached_unlocked(func, cache, key):
111 def wrapper(*args, **kwargs):
112 k = key(*args, **kwargs)
113 try:
114 return cache[k]
115 except KeyError:
116 pass # key not found
117 v = func(*args, **kwargs)
118 try:
119 cache[k] = v
120 except ValueError:
121 pass # value too large
122 return v
123
124 wrapper.cache_clear = lambda: cache.clear()
125 return wrapper
126
127
128def _uncached(func):
129 def wrapper(*args, **kwargs):
130 return func(*args, **kwargs)
131
132 wrapper.cache_clear = lambda: None
133 return wrapper
134
135
136def _cached_wrapper(func, cache, key, lock, info):
137 if info is not None:
138 if cache is None:
139 wrapper = _uncached_info(func, info)
140 elif lock is None:
141 wrapper = _cached_unlocked_info(func, cache, key, info)
142 else:
143 wrapper = _cached_locked_info(func, cache, key, lock, info)
144 else:
145 if cache is None:
146 wrapper = _uncached(func)
147 elif lock is None:
148 wrapper = _cached_unlocked(func, cache, key)
149 else:
150 wrapper = _cached_locked(func, cache, key, lock)
151 wrapper.cache_info = None
152 return wrapper