Python: @cache, @lru_cache e @cached_property, quando usare cosa
Se una funzione costosa viene chiamata più volte con gli stessi argomenti, il modulo functools ti permette di ricordare il risultato. Tre strumenti, tre usi diversi.
Il codice
from functools import lru_cache, cache, cached_property
def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)
@cache
def fib_c(n): return n if n < 2 else fib_c(n-1) + fib_c(n-2)
Misurato su Python 3.12 con n = 30:
- senza cache: circa 0,15 s;
- con
@cache: circa 0,015 ms (in pratica 31 calcoli invece di oltre un milione di chiamate).
fib_c.cache_info() restituisce hits=28, misses=31: ogni valore viene calcolato una volta sola.
@cache o @lru_cache?
@cache(Python 3.9+) conserva tutto, senza limiti. Comodo, ma se gli argomenti sono molti la memoria cresce senza fine.@lru_cache(maxsize=N)tiene solo gli ultimi N risultati usati e scarta i più vecchi.
Esempio con maxsize=2 e chiamate 1, 2, 3, 1: il valore per 1 è stato scartato quando è arrivato 3, quindi l'ultima chiamata è di nuovo un calcolo (hits=0, misses=4). Scegli maxsize in base a quanti valori diversi ti aspetti davvero.
@cached_property per i metodi
Per un valore calcolato una sola volta per istanza usa cached_property:
class Rapporto:
@cached_property
def totale(self):
return sum(range(1_000_000))
Leggendo r.totale due volte, il calcolo avviene una volta sola (verificato: contatore a 1). Il risultato viene salvato nell'istanza, quindi per ricalcolarlo basta del r.totale.
Le trappole
- Argomenti non hashabili:
@cachesu una funzione che riceve una lista fallisce conTypeError: unhashable type: 'list'. Passa una tupla. - Funzioni con effetti collaterali o dati che cambiano: la cache restituirà il vecchio risultato. Usala solo per funzioni pure, oppure svuota con
fib_c.cache_clear(). - Metodi con
@cache:selfentra nella chiave e l'istanza resta in memoria finché la cache la contiene. Per i metodi preferiscicached_propertyo una cache esplicita.
In Django lo stesso schema vale per i valori calcolati sul modello: cached_property evita query ripetute durante una richiesta, vedi anche la guida alle query N+1.
Script di verifica: verifica/cache_funzioni.py, eseguito con Python 3.12.15.