Tips & Notes¶
Saving a Cache to a File¶
Cachebox does not include built-in persistence, but all cache classes support Python's
pickle module:
import cachebox, pickle
cache = cachebox.LRUCache(100, {i: i for i in range(78)})
# Save
with open("cache.pkl", "wb") as f:
pickle.dump(cache, f)
# Load
with open("cache.pkl", "rb") as f:
loaded = pickle.load(f)
assert cache == loaded
assert cache.capacity() == loaded.capacity()
Note
Don't set lambda as getsizeof for caches when you want to pickle them.
Copying a Cache¶
All cache classes support Python's copy module, both shallow-copy and deep-copy:
import cachebox
import copy
cache = cachebox.LRUCache(100, {i: i for i in range(10)})
shallow = copy.copy(cache) # shallow copy
deep = copy.deepcopy(cache) # deep copy
Pre-allocating Capacity¶
If you know roughly how many items a cache will hold, set capacity to avoid
hash table rehashing during initial population:
Thread Safety¶
All cache operations (reads, writes, eviction) are protected by internal Rust mutexes. You do not need to add external synchronisation.
TTL and Frozen Caches¶
Frozen cannot prevent TTL expiration in TTLCache or VTTLCache.
Items will still expire naturally even when the cache is frozen.
from cachebox import Frozen, TTLCache
import time
cache = TTLCache(0, ttl=1, iterable={1: "a"})
frozen = Frozen(cache)
time.sleep(1)
print(len(frozen)) # 0 — expired despite being frozen
Attached attributes to cached functions¶
When you use the @cached decorator, If cache isn't a lambda/function, these attributes will be attached to
your function:
The cache class we're using for caching results.
import cachebox
@cachebox.cached(
cachebox.LFUCache(maxsize=20),
)
def add(a: int, b: int) -> int:
return a + b
assert type(add.cache) is cachebox.LFUCache
Tip
You can use get_cached_cache function to prevent lint & IDE warnings.
By calling it, you will get a basic statistics.
import cachebox
@cachebox.cached(
cachebox.LFUCache(maxsize=20),
)
def add(a: int, b: int) -> int:
return a + b
cache_info = add.cache_info() # CacheInfo(hits=0, misses=0, maxsize=20, size=0)
Tip
You can use get_cached_cache_info function to prevent lint & IDE warnings.
Call it if you want to clear cache and reset statistics.
import cachebox
@cachebox.cached(
cachebox.LFUCache(maxsize=20),
)
def add(a: int, b: int) -> int:
return a + b
add.cache_clear()
Tip
You can use clear_cached_cache function to prevent lint & IDE warnings.
The configured callback.
import cachebox
def callback(event, key, value): ...
@cachebox.cached(
cachebox.LFUCache(maxsize=20),
callback=callback,
)
def add(a: int, b: int) -> int:
return a + b
assert add.callback is callback
Tip
You can use get_cached_callback function to prevent lint & IDE warnings.
TTLCache/VTTLCache background thread¶
By default, both TTLCache and VTTLCache use lazy expiry: stale entries are
only cleaned up when the cache is interacted with (e.g. on insert, lookup, or
iteration). A completely idle cache will hold expired entries in memory until
the next interaction.
To reclaim expired entries proactively — independent of any method calls — pass a
sweep_interval to start a background sweeper thread:
import cachebox
from datetime import timedelta
# Sweep every 30 seconds
ttl_cache = cachebox.TTLCache(maxsize=1000, global_ttl=60, sweep_interval=30)
# timedelta is also accepted
vttl_cache = cachebox.VTTLCache(maxsize=1000, sweep_interval=timedelta(seconds=30))
The thread is a daemon thread, meaning it will not prevent the Python process from exiting when the main thread finishes.
Note
sweep_interval must be ≥ 1 second. Smaller values raise a ValueError:
cache = cachebox.TTLCache(100, global_ttl=60, sweep_interval=30)
print(cache.sweep_interval) # 30.0
# Without a sweeper, sweep_interval is None
cache2 = cachebox.TTLCache(100, global_ttl=60)
print(cache2.sweep_interval) # None
Call stop_sweeper() when you want to halt background sweeping without
destroying the cache itself. This is useful when you need to pause periodic
eviction or cleanly shut down the thread before the cache goes out of scope:
cache = cachebox.TTLCache(100, global_ttl=60, sweep_interval=10)
# ... later, during shutdown ...
cache.stop_sweeper()
Note
The sweeper thread is also stopped automatically when the cache is garbage
collected (via __del__), so manual cleanup is only necessary when explicit
lifecycle control is required.
Use a sweeper when:
- The cache may be idle for long periods but memory should still be reclaimed.
- You need to bound the window in which stale data could be observed (e.g. via items() or __iter__).
- You are using VTTLCache with short, heterogeneous TTLs and want predictable cleanup.
Stick with lazy expiry when: - The cache sees regular traffic and on-access cleanup is sufficient. - You want to avoid any background thread overhead. - Memory pressure from temporarily lingering stale entries is acceptable.