Whole file

tkem/cachetools

The author described this change as Fix #159: Pass self to @cachedmethod key function.. It counts as a record because the check below fails on the code as it stood at 0e778e441 and passes on 9dda91f99, with nothing else changed between the two runs.

Fix saved2021-12-21
Sharing licenceMIT · LICENSE
Change size+678 674

What the code was meant to do, written into the code itself as a save note

Fix #159: Pass self to @cachedmethod key function.

The change

2222 import random
2323 import time
2424
25-from .keys import hashkey
26-
27-
28-class _DefaultSize:
29-
30- __slots__ = ()
31-
32- def __getitem__(self, _):
33- return 1
34-
35- def __setitem__(self, _, value):
36- assert value == 1
37-
38- def pop(self, _):
39- return 1
40-
41-
42-class Cache(collections.abc.MutableMapping):
43- """Mutable mapping to serve as a simple cache or cache base class."""
44-
45- __marker = object()
46-
47- __size = _DefaultSize()
48-
49- def __init__(self, maxsize, getsizeof=None):
50- if getsizeof:
51- self.getsizeof = getsizeof
52- if self.getsizeof is not Cache.getsizeof:
53- self.__size = dict()
54- self.__data = dict()
55- self.__currsize = 0
56- self.__maxsize = maxsize
57-
58- def __repr__(self):
59- return "%s(%s, maxsize=%r, currsize=%r)" % (
60- self.__class__.__name__,
61- repr(self.__data),
62- self.__maxsize,
63- self.__currsize,
64- )
65-
66- def __getitem__(self, key):
67- try:
68- return self.__data[key]
69- except KeyError:
70- return self.__missing__(key)
71-
72- def __setitem__(self, key, value):
73- maxsize = self.__maxsize
74- size = self.getsizeof(value)
75- if size > maxsize:
76- raise ValueError("value too large")
77- if key not in self.__data or self.__size[key] < size:
78- while self.__currsize + size > maxsize:
79- self.popitem()
80- if key in self.__data:
81- diffsize = size - self.__size[key]
82- else:
83- diffsize = size
84- self.__data[key] = value
85- self.__size[key] = size
86- self.__currsize += diffsize
87-
88- def __delitem__(self, key):
89- size = self.__size.pop(key)
90- del self.__data[key]
91- self.__currsize -= size
92-
93- def __contains__(self, key):
94- return key in self.__data
95-
96- def __missing__(self, key):
97- raise KeyError(key)
98-
99- def __iter__(self):
100- return iter(self.__data)
101-
102- def __len__(self):
103- return len(self.__data)
104-
105- def get(self, key, default=None):
106- if key in self:
107- return self[key]
108- else:
109- return default
110-
111- def pop(self, key, default=__marker):
112- if key in self:
113- value = self[key]
114- del self[key]
115- elif default is self.__marker:
116- raise KeyError(key)
117- else:
118- value = default
119- return value
120-
121- def setdefault(self, key, default=None):
122- if key in self:
123- value = self[key]
124- else:
125- self[key] = value = default
126- return value
127-
128- @property
129- def maxsize(self):
130- """The maximum size of the cache."""
131- return self.__maxsize
132-
133- @property
134- def currsize(self):
135- """The current size of the cache."""
136- return self.__currsize
137-
138- @staticmethod
139- def getsizeof(value):
140- """Return the size of a cache element's value."""
141- return 1
142-
143-
144-class FIFOCache(Cache):
145- """First In First Out (FIFO) cache implementation."""
146-
147- def __init__(self, maxsize, getsizeof=None):
148- Cache.__init__(self, maxsize, getsizeof)
149- self.__order = collections.OrderedDict()
150-
151- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
152- cache_setitem(self, key, value)
153- try:
154- self.__order.move_to_end(key)
155- except KeyError:
156- self.__order[key] = None
157-
158- def __delitem__(self, key, cache_delitem=Cache.__delitem__):
159- cache_delitem(self, key)
160- del self.__order[key]
161-
162- def popitem(self):
163- """Remove and return the `(key, value)` pair first inserted."""
164- try:
165- key = next(iter(self.__order))
166- except StopIteration:
167- raise KeyError("%s is empty" % type(self).__name__) from None
168- else:
169- return (key, self.pop(key))
170-
171-
172-class LFUCache(Cache):
173- """Least Frequently Used (LFU) cache implementation."""
174-
175- def __init__(self, maxsize, getsizeof=None):
176- Cache.__init__(self, maxsize, getsizeof)
177- self.__counter = collections.Counter()
178-
179- def __getitem__(self, key, cache_getitem=Cache.__getitem__):
180- value = cache_getitem(self, key)
181- if key in self: # __missing__ may not store item
182- self.__counter[key] -= 1
183- return value
184-
185- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
186- cache_setitem(self, key, value)
187- self.__counter[key] -= 1
188-
189- def __delitem__(self, key, cache_delitem=Cache.__delitem__):
190- cache_delitem(self, key)
191- del self.__counter[key]
192-
193- def popitem(self):
194- """Remove and return the `(key, value)` pair least frequently used."""
195- try:
196- ((key, _),) = self.__counter.most_common(1)
197- except ValueError:
198- raise KeyError("%s is empty" % type(self).__name__) from None
199- else:
200- return (key, self.pop(key))
201-
202-
203-class LRUCache(Cache):
204- """Least Recently Used (LRU) cache implementation."""
205-
206- def __init__(self, maxsize, getsizeof=None):
207- Cache.__init__(self, maxsize, getsizeof)
208- self.__order = collections.OrderedDict()
209-
210- def __getitem__(self, key, cache_getitem=Cache.__getitem__):
211- value = cache_getitem(self, key)
212- if key in self: # __missing__ may not store item
213- self.__update(key)
214- return value
215-
216- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
217- cache_setitem(self, key, value)
218- self.__update(key)
219-
220- def __delitem__(self, key, cache_delitem=Cache.__delitem__):
221- cache_delitem(self, key)
222- del self.__order[key]
223-
224- def popitem(self):
225- """Remove and return the `(key, value)` pair least recently used."""
226- try:
227- key = next(iter(self.__order))
228- except StopIteration:
229- raise KeyError("%s is empty" % type(self).__name__) from None
230- else:
231- return (key, self.pop(key))
232-
233- def __update(self, key):
234- try:
235- self.__order.move_to_end(key)
236- except KeyError:
237- self.__order[key] = None
238-
239-
240-class MRUCache(Cache):
241- """Most Recently Used (MRU) cache implementation."""
242-
243- def __init__(self, maxsize, getsizeof=None):
244- Cache.__init__(self, maxsize, getsizeof)
245- self.__order = collections.OrderedDict()
246-
247- def __getitem__(self, key, cache_getitem=Cache.__getitem__):
248- value = cache_getitem(self, key)
249- if key in self: # __missing__ may not store item
250- self.__update(key)
251- return value
252-
253- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
254- cache_setitem(self, key, value)
255- self.__update(key)
256-
257- def __delitem__(self, key, cache_delitem=Cache.__delitem__):
258- cache_delitem(self, key)
259- del self.__order[key]
260-
261- def popitem(self):
262- """Remove and return the `(key, value)` pair most recently used."""
263- try:
264- key = next(iter(self.__order))
265- except StopIteration:
266- raise KeyError("%s is empty" % type(self).__name__) from None
267- else:
268- return (key, self.pop(key))
269-
270- def __update(self, key):
271- try:
272- self.__order.move_to_end(key, last=False)
273- except KeyError:
274- self.__order[key] = None
275-
276-
277-class RRCache(Cache):
278- """Random Replacement (RR) cache implementation."""
279-
280- def __init__(self, maxsize, choice=random.choice, getsizeof=None):
281- Cache.__init__(self, maxsize, getsizeof)
282- self.__choice = choice
283-
284- @property
285- def choice(self):
286- """The `choice` function used by the cache."""
287- return self.__choice
288-
289- def popitem(self):
290- """Remove and return a random `(key, value)` pair."""
291- try:
292- key = self.__choice(list(self))
293- except IndexError:
294- raise KeyError("%s is empty" % type(self).__name__) from None
295- else:
296- return (key, self.pop(key))
297-
298-
299-class _TimedCache(Cache):
300- """Base class for time aware cache implementations."""
301-
302- class _Timer:
303- def __init__(self, timer):
304- self.__timer = timer
305- self.__nesting = 0
306-
307- def __call__(self):
308- if self.__nesting == 0:
309- return self.__timer()
310- else:
311- return self.__time
312-
313- def __enter__(self):
314- if self.__nesting == 0:
315- self.__time = time = self.__timer()
316- else:
317- time = self.__time
318- self.__nesting += 1
319- return time
320-
321- def __exit__(self, *exc):
322- self.__nesting -= 1
323-
324- def __reduce__(self):
325- return _TimedCache._Timer, (self.__timer,)
326-
327- def __getattr__(self, name):
328- return getattr(self.__timer, name)
329-
330- def __init__(self, maxsize, timer=time.monotonic, getsizeof=None):
331- Cache.__init__(self, maxsize, getsizeof)
332- self.__timer = _TimedCache._Timer(timer)
333-
334- def __repr__(self, cache_repr=Cache.__repr__):
335- with self.__timer as time:
336- self.expire(time)
337- return cache_repr(self)
338-
339- def __len__(self, cache_len=Cache.__len__):
340- with self.__timer as time:
341- self.expire(time)
342- return cache_len(self)
343-
344- @property
345- def currsize(self):
346- with self.__timer as time:
347- self.expire(time)
348- return super().currsize
349-
350- @property
351- def timer(self):
352- """The timer function used by the cache."""
353- return self.__timer
354-
355- def clear(self):
356- with self.__timer as time:
357- self.expire(time)
358- Cache.clear(self)
359-
360- def get(self, *args, **kwargs):
361- with self.__timer:
362- return Cache.get(self, *args, **kwargs)
363-
364- def pop(self, *args, **kwargs):
365- with self.__timer:
366- return Cache.pop(self, *args, **kwargs)
367-
368- def setdefault(self, *args, **kwargs):
369- with self.__timer:
370- return Cache.setdefault(self, *args, **kwargs)
371-
372-
373-class TTLCache(_TimedCache):
374- """LRU Cache implementation with per-item time-to-live (TTL) value."""
375-
376- class _Link:
377-
378- __slots__ = ("key", "expires", "next", "prev")
379-
380- def __init__(self, key=None, expires=None):
381- self.key = key
382- self.expires = expires
383-
384- def __reduce__(self):
385- return TTLCache._Link, (self.key, self.expires)
386-
387- def unlink(self):
388- next = self.next
389- prev = self.prev
390- prev.next = next
391- next.prev = prev
392-
393- def __init__(self, maxsize, ttl, timer=time.monotonic, getsizeof=None):
394- _TimedCache.__init__(self, maxsize, timer, getsizeof)
395- self.__root = root = TTLCache._Link()
396- root.prev = root.next = root
397- self.__links = collections.OrderedDict()
398- self.__ttl = ttl
399-
400- def __contains__(self, key):
401- try:
402- link = self.__links[key] # no reordering
403- except KeyError:
404- return False
405- else:
406- return self.timer() < link.expires
407-
408- def __getitem__(self, key, cache_getitem=Cache.__getitem__):
409- try:
410- link = self.__getlink(key)
411- except KeyError:
412- expired = False
413- else:
414- expired = not (self.timer() < link.expires)
415- if expired:
416- return self.__missing__(key)
417- else:
418- return cache_getitem(self, key)
419-
420- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
421- with self.timer as time:
958 further changed lines not shown

The check that tells the two apart

failpass·tests/test_method.py::CachedMethodTest::test_unhashable

Check file tests/test_method.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.

Origin and history

The code before it0e778e4410641af906930877c14f12f592d16fe2
Broken version dated2021-12-19
Modulecachetools.__init__
Units changedcached, cachedmethod
Fingerprint7d2f314202d4573d
Checked2026-08-18 by goldset/0.1

Every field above is generated by our program. None of it is written by hand.

Other bugs found in tkem/cachetools