keys(), values() and items() return an iterator that does not hold a reference to the cache, only a raw cursor into it. If the cache is not stored anywhere else, it is freed while the iterator is still alive.
import cachebox
def make_cache():
cache = cachebox.LRUCache(100)
cache['a'] = 1
cache['b'] = 2
return cache
print('cachebox', cachebox.__version__)
print('walking a cache that nothing else holds ...', flush=True)
print('got:', [key for key in make_cache().keys()])
On main this dies with an access violation instead of printing the keys (Windows, CPython 3.12). A stored iterator does the same once the last reference to the cache goes away.
6.2.2 from PyPI prints the right keys, but only while nothing has reused the freed memory. Fill another cache before the walk and it silently returns that cache's keys instead:
import cachebox
c = cachebox.LRUCache(100)
c['a'] = 1
c['b'] = 2
it = c.keys()
del c
c2 = cachebox.LRUCache(1000)
for i in range(1000):
c2[f'x{i}'] = i
print('got:', list(it))
On 6.2.2 this prints keys of c2, different ones on every run: got: ['x3', 'x4'], got: ['x21', 'x22'] and so on.
keys(),values()anditems()return an iterator that does not hold a reference to the cache, only a raw cursor into it. If the cache is not stored anywhere else, it is freed while the iterator is still alive.On main this dies with an access violation instead of printing the keys (Windows, CPython 3.12). A stored iterator does the same once the last reference to the cache goes away.
6.2.2 from PyPI prints the right keys, but only while nothing has reused the freed memory. Fill another cache before the walk and it silently returns that cache's keys instead:
On 6.2.2 this prints keys of
c2, different ones on every run:got: ['x3', 'x4'],got: ['x21', 'x22']and so on.