In questo articolo parliamo di caching in Python: cos’è, a cosa serve e come usarlo in modo efficace.
Il caching è una tecnica per migliorare le prestazioni di un’applicazione memorizzando temporaneamente i risultati prodotti dal programma, così da riutilizzarli quando servono di nuovo.
In questo tutorial vedremo diverse tecniche di caching in Python, inclusi i decorator @lru_cache e @cache del modulo functools.
Se hai fretta, partiamo da un’implementazione rapidissima e poi entriamo nei dettagli.
Risposta breve: implementazione del caching in Python
Per creare una cache in Python possiamo usare il decorator @cache del modulo functools. Nel codice qui sotto, nota che la funzione print() viene eseguita una sola volta:
import functools
@functools.cachedefsquare(n):print(f"Calculating square of {n}")return n * n
# Testing the cached functionprint(square(4))print(square(4))# Calculating square of 4# 16# 16Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
Supponiamo di dover risolvere un problema matematico e di impiegare un’ora per arrivare alla risposta giusta. Se il giorno dopo dovessimo risolvere lo stesso problema, sarebbe utile riutilizzare il lavoro già fatto invece di ricominciare da capo.
Il caching in Python segue lo stesso principio: memorizza i valori calcolati all’interno delle chiamate di funzione per riutilizzarli quando servono di nuovo. Questo tipo di caching è chiamato anche memoization.
Vediamo un breve esempio che calcola due volte la somma di un ampio intervallo di numeri:
output =sum(range(100_000_001))print(output)
output =sum(range(100_000_001))print(output)# 5000000050000000# 5000000050000000Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
L’output mostra che entrambe le chiamate impiegano più o meno lo stesso tempo (in base alla nostra configurazione, i tempi possono variare).
Possiamo però usare una cache per evitare di ricalcolare lo stesso valore più di una volta. Possiamo ridefinire il nome sum usando la funzione cache() del modulo standard functools:
La seconda chiamata ora richiede pochi microsecondi invece di oltre un secondo, perché il risultato della somma dei numeri da 0 a 100.000.000 è stato già calcolato e messo in cache: la seconda chiamata riutilizza il valore calcolato e memorizzato in precedenza.
Qui sopra usiamo il decorator functools.cache() per aggiungere una cache alla funzione built-in sum(). A margine, un decorator in Python è una funzione che modifica il comportamento di un’altra funzione senza cambiare in modo permanente il suo codice. Puoi saperne di più sui decorator in questo tutorial sui decorator in Python.
Il decorator functools.cache() è stato aggiunto in Python 3.9, ma per le versioni precedenti possiamo usare functools.lru_cache(). Nella prossima sezione esploreremo entrambi i modi per creare una cache, incluso l’uso della notazione a decorator più comune, come @cache.
Caching in Python: metodi diversi
Il modulo functools di Python offre due decorator per applicare il caching alle funzioni. Vediamo functools.lru_cache() e functools.cache() con un esempio.
Scriviamo una funzione sum_digits() che accetta una sequenza di numeri e restituisce la somma delle cifre di quei numeri. Per esempio, se usiamo la tupla (23, 43, 8) come input, allora:
La somma delle cifre di 23 è cinque.
La somma delle cifre di 43 è sette.
La somma delle cifre di 8 è otto.
Quindi la somma complessiva è 20.
Ecco un modo per scrivere la nostra funzione sum_digits():
defsum_digits(numbers):returnsum(int(digit)for number in numbers for digit instr(number))
numbers =23,43,8print(sum_digits(numbers))# 20Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
Usiamo questa funzione per esplorare diversi modi di creare una cache.
Caching manuale in Python
Iniziamo creando la cache manualmente. Anche se potremmo automatizzare facilmente il processo, costruire una cache a mano aiuta a capirne il funzionamento.
Creiamo un dizionario e aggiungiamo coppie chiave-valore ogni volta che chiamiamo la funzione con un nuovo valore, così da memorizzare i risultati. Se chiamiamo la funzione con un valore già presente nel dizionario, la funzione restituirà il valore memorizzato senza ricalcolarlo:
import random
import timeit
defsum_digits(numbers):if numbers notin sum_digits.my_cache:
sum_digits.my_cache[numbers]=sum(int(digit)for number in numbers for digit instr(number))return sum_digits.my_cache[numbers]
sum_digits.my_cache ={}
numbers =tuple(random.randint(1,1000)for _ inrange(1_000_000))print(
timeit.timeit("sum_digits(numbers)",globals=globals(),
number=1))print(
timeit.timeit("sum_digits(numbers)",globals=globals(),
number=1))Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
La seconda chiamata a sum_digits(numbers) è molto più veloce della prima, perché usa il valore in cache.
Spieghiamo ora il codice più nel dettaglio. Per prima cosa, nota che creiamo il dizionario sum_digits.my_cache dopo aver definito la funzione, anche se lo usiamo all’interno della definizione.
La funzione sum_digits() controlla se l’argomento passato è già una delle chiavi del dizionario sum_digits.my_cache. La somma di tutte le cifre viene valutata solo se l’argomento non è già in cache.
Dato che l’argomento usato quando chiamiamo la funzione funge da chiave nel dizionario, deve essere di un tipo hashable. Una lista non è hashable, quindi non possiamo usarla come chiave in un dizionario. Ad esempio, proviamo a sostituire numbers con una lista invece che con una tupla: verrà sollevato un TypeError:
# ...
numbers = [random.randint(1, 1000) for _ in range(1_000_000)]
# ...Offerto da
Creare una cache manualmente è ottimo per imparare, ma ora vediamo metodi più rapidi.
Caching in Python con functools.lru_cache()
Python include il decorator lru_cache() sin dalla versione 3.2. La sigla “lru” sta per "least recently used". Possiamo pensare alla cache come a una scatola che conserva le cose usate di frequente: quando si riempie, la strategia LRU butta via l’elemento che non usiamo da più tempo per far spazio a qualcosa di nuovo.
Applichiamo il decorator @functools.lru_cache alla nostra funzione sum_digits():
import functools
import random
import timeit
@functools.lru_cachedefsum_digits(numbers):returnsum(int(digit)for number in numbers for digit instr(number))
numbers =tuple(random.randint(1,1000)for _ inrange(1_000_000))print(
timeit.timeit("sum_digits(numbers)",globals=globals(),
number=1))print(
timeit.timeit("sum_digits(numbers)",globals=globals(),
number=1))Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
Grazie al caching, la seconda chiamata impiega sensibilmente meno tempo.
Per impostazione predefinita, la cache conserva i primi 128 valori calcolati. Quando i 128 slot sono pieni, l’algoritmo elimina il valore meno usato di recente (LRU) per far spazio ai nuovi.
Possiamo impostare una dimensione massima diversa quando applichiamo il decorator usando il parametro maxsize:
import functools
import random
import timeit
@functools.lru_cache(maxsize=5)
def sum_digits(numbers):
return sum(
int(digit) for number in numbers for digit in str(number)
)
# ...Offerto da
In questo caso, la cache conserva solo cinque valori. Possiamo anche impostare maxsize a None se non vogliamo limitare la dimensione della cache.
Caching in Python con functools.cache()
Python 3.9 include un decorator per il caching più semplice e veloce: functools.cache(). Questo decorator ha due caratteristiche principali:
Non ha una dimensione massima: è equivalente a chiamare functools.lru_cache(maxsize=None).
Memorizza tutte le chiamate di funzione e i relativi risultati (non usa la strategia LRU). È adatto a funzioni con output relativamente piccoli o quando non abbiamo vincoli sulla dimensione della cache.
Applichiamo il decorator @functools.cache alla funzione sum_digits():
import functools
import random
import timeit
@functools.cachedefsum_digits(numbers):returnsum(int(digit)for number in numbers for digit instr(number))
numbers =tuple(random.randint(1,1000)for _ inrange(1_000_000))print(
timeit.timeit("sum_digits(numbers)",globals=globals(),
number=1))print(
timeit.timeit("sum_digits(numbers)",globals=globals(),
number=1))Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
Decorare sum_digits() con @functools.cache equivale ad assegnare sum_digits a functools.cache():
# ...
def sum_digits(numbers):
return sum(
int(digit) for number in numbers for digit in str(number)
)
sum_digits = functools.cache(sum_digits)Offerto da
Nota che possiamo anche usare un diverso stile di import:
In questo modo possiamo decorare le funzioni usando semplicemente @cache.
Altre strategie di caching
Gli strumenti integrati di Python implementano la strategia LRU, in cui le voci meno usate di recente vengono eliminate per far spazio ai nuovi valori.
Vediamo ora alcune altre strategie di caching:
First-in, first-out (FIFO): quando la cache è piena, viene rimosso il primo elemento aggiunto per far spazio ai nuovi valori. La differenza tra LRU e FIFO è che LRU mantiene in cache gli elementi usati di recente, mentre FIFO scarta l’elemento più vecchio indipendentemente dall’uso.
Last-in, first-out (LIFO): quando la cache è piena, viene rimosso l’elemento aggiunto più di recente. Immagina una pila di piatti in mensa: il piatto appoggiato per ultimo (last in) è quello che si prende per primo (first out).
Most-recently used (MRU): quando serve spazio, viene scartato il valore usato più di recente.
Random replacement (RR): questa strategia scarta casualmente un elemento per far spazio a uno nuovo.
Queste strategie possono essere combinate con misure della durata di validità: indica per quanto tempo un dato in cache è considerato valido o rilevante. Immagina un articolo di news in cache: potrebbe essere consultato spesso (LRU lo terrebbe), ma dopo una settimana potrebbe essere superato.
Caching in Python: casi d’uso comuni
Finora abbiamo usato esempi semplici per imparare. Nella pratica, però, il caching ha moltissime applicazioni reali.
Nel data science eseguiamo spesso operazioni ripetute su dataset di grandi dimensioni. Usare risultati in cache riduce il tempo e i costi associati al ripetere gli stessi calcoli sugli stessi dataset.
Possiamo anche usare il caching per salvare risorse esterne come pagine web o database. Consideriamo un esempio e mettiamo in cache un articolo di DataCamp. Prima, però, dobbiamo installare il modulo di terze parti requests eseguendo la riga seguente nel terminale:
Una volta installato requests, possiamo provare il codice seguente, che tenta di recuperare due volte lo stesso articolo di DataCamp usando il decorator @lru_cache:
import requests
from functools import lru_cache
@lru_cache(maxsize=10)defget_article(url):print(f"Fetching article from {url}")
response = requests.get(url)return response.text
print(get_article("https://www.datacamp.com/tutorial/decorators-python"))print(get_article("https://www.datacamp.com/tutorial/decorators-python"))Offerto da
Editor “Impara con esempi”: premi Tab per entrare. Premi Esc, poi Tab o Maiusc+Tab per uscire.
A margine, abbiamo troncato l’output perché è molto lungo. Nota però che solo la prima chiamata a get_article() stampa la frase Fetching article from {url}.
Questo perché la pagina web viene effettivamente scaricata solo la prima volta. Il risultato viene memorizzato nella cache della funzione. Quando richiediamo la stessa pagina la seconda volta, i dati vengono restituiti dalla cache.
Il caching evita ritardi inutili quando si recuperano ripetutamente gli stessi dati. Le API esterne hanno spesso limiti di rate e costi associati alle richieste. Il caching riduce i costi delle API e la probabilità di raggiungere i limiti di rate.
Un altro caso d’uso tipico è nelle applicazioni di machine learning, dove vanno ripetute varie elaborazioni costose. Ad esempio, se dobbiamo tokenizzare e vettorializzare un testo prima di usarlo in un modello di machine learning, possiamo memorizzare l’output in cache: così non dovremo ripetere operazioni computazionalmente onerose.
Sfide comuni del caching in Python
Abbiamo visto i vantaggi del caching in Python. Ci sono però anche alcune sfide e controindicazioni da considerare quando lo si implementa:
Invalidazione della cache e consistenza: i dati possono cambiare nel tempo. Di conseguenza, i valori memorizzati in cache potrebbero dover essere aggiornati o rimossi.
Gestione della memoria: memorizzare grandi quantità di dati in cache richiede memoria e può causare problemi di prestazioni se la cache cresce senza controllo.
Complessità: aggiungere cache introduce complessità nel sistema, sia in creazione che in manutenzione. Spesso i benefici superano i costi, ma questa maggiore complessità può portare a bug difficili da individuare e correggere.
Conclusione
Possiamo usare il caching per ottimizzare le prestazioni quando operazioni computazionalmente intense vengono ripetute sugli stessi dati.
Python mette a disposizione due decorator per creare una cache nelle chiamate di funzione: @lru_cache e @cache nel modulo functools.
Dobbiamo però assicurarci di mantenere aggiornata la cache e gestire correttamente la memoria.
Se vuoi approfondire caching e Python, dai un’occhiata a questo percorso di 6 corsi Python Programming.
Ho studiato Fisica e Matematica a livello UG presso l’Università di Malta. Poi mi sono trasferito a Londra e ho conseguito il dottorato in Fisica all’Imperial College. Ho lavorato su nuove tecniche ottiche per immagini della retina umana. Ora mi concentro sullo scrivere di Python, comunicare su Python e insegnare Python.
Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
In questo articolo parliamo di caching in Python: cos’è, a cosa serve e come usarlo in modo efficace.
Il caching è una tecnica per migliorare le prestazioni di un’applicazione memorizzando temporaneamente i risultati prodotti dal programma, così da riutilizzarli quando servono di nuovo.
In questo tutorial vedremo diverse tecniche di caching in Python, inclusi i decorator
@lru_cachee@cachedel modulofunctools.Se hai fretta, partiamo da un’implementazione rapidissima e poi entriamo nei dettagli.
Risposta breve: implementazione del caching in Python
Per creare una cache in Python possiamo usare il decorator
@cachedel modulofunctools. Nel codice qui sotto, nota che la funzioneprint()viene eseguita una sola volta:Che cos’è il caching in Python?
Supponiamo di dover risolvere un problema matematico e di impiegare un’ora per arrivare alla risposta giusta. Se il giorno dopo dovessimo risolvere lo stesso problema, sarebbe utile riutilizzare il lavoro già fatto invece di ricominciare da capo.
Il caching in Python segue lo stesso principio: memorizza i valori calcolati all’interno delle chiamate di funzione per riutilizzarli quando servono di nuovo. Questo tipo di caching è chiamato anche memoization.
Vediamo un breve esempio che calcola due volte la somma di un ampio intervallo di numeri:
Il programma deve calcolare la somma ogni volta. Possiamo verificarlo misurando il tempo delle due chiamate:
L’output mostra che entrambe le chiamate impiegano più o meno lo stesso tempo (in base alla nostra configurazione, i tempi possono variare).
Possiamo però usare una cache per evitare di ricalcolare lo stesso valore più di una volta. Possiamo ridefinire il nome
sumusando la funzionecache()del modulo standardfunctools:La seconda chiamata ora richiede pochi microsecondi invece di oltre un secondo, perché il risultato della somma dei numeri da 0 a 100.000.000 è stato già calcolato e messo in cache: la seconda chiamata riutilizza il valore calcolato e memorizzato in precedenza.
Qui sopra usiamo il decorator
functools.cache()per aggiungere una cache alla funzione built-insum(). A margine, un decorator in Python è una funzione che modifica il comportamento di un’altra funzione senza cambiare in modo permanente il suo codice. Puoi saperne di più sui decorator in questo tutorial sui decorator in Python.Il decorator
functools.cache()è stato aggiunto in Python 3.9, ma per le versioni precedenti possiamo usarefunctools.lru_cache(). Nella prossima sezione esploreremo entrambi i modi per creare una cache, incluso l’uso della notazione a decorator più comune, come@cache.Caching in Python: metodi diversi
Il modulo
functoolsdi Python offre due decorator per applicare il caching alle funzioni. Vediamofunctools.lru_cache()efunctools.cache()con un esempio.Scriviamo una funzione
sum_digits()che accetta una sequenza di numeri e restituisce la somma delle cifre di quei numeri. Per esempio, se usiamo la tupla(23, 43, 8)come input, allora:23è cinque.43è sette.8è otto.Ecco un modo per scrivere la nostra funzione
sum_digits():Usiamo questa funzione per esplorare diversi modi di creare una cache.
Caching manuale in Python
Iniziamo creando la cache manualmente. Anche se potremmo automatizzare facilmente il processo, costruire una cache a mano aiuta a capirne il funzionamento.
Creiamo un dizionario e aggiungiamo coppie chiave-valore ogni volta che chiamiamo la funzione con un nuovo valore, così da memorizzare i risultati. Se chiamiamo la funzione con un valore già presente nel dizionario, la funzione restituirà il valore memorizzato senza ricalcolarlo:
La seconda chiamata a
sum_digits(numbers)è molto più veloce della prima, perché usa il valore in cache.Spieghiamo ora il codice più nel dettaglio. Per prima cosa, nota che creiamo il dizionario
sum_digits.my_cachedopo aver definito la funzione, anche se lo usiamo all’interno della definizione.La funzione
sum_digits()controlla se l’argomento passato è già una delle chiavi del dizionariosum_digits.my_cache. La somma di tutte le cifre viene valutata solo se l’argomento non è già in cache.Dato che l’argomento usato quando chiamiamo la funzione funge da chiave nel dizionario, deve essere di un tipo hashable. Una lista non è hashable, quindi non possiamo usarla come chiave in un dizionario. Ad esempio, proviamo a sostituire
numberscon una lista invece che con una tupla: verrà sollevato unTypeError:Creare una cache manualmente è ottimo per imparare, ma ora vediamo metodi più rapidi.
Caching in Python con functools.lru_cache()
Python include il decorator
lru_cache()sin dalla versione 3.2. La sigla “lru” sta per "least recently used". Possiamo pensare alla cache come a una scatola che conserva le cose usate di frequente: quando si riempie, la strategia LRU butta via l’elemento che non usiamo da più tempo per far spazio a qualcosa di nuovo.Applichiamo il decorator
@functools.lru_cachealla nostra funzionesum_digits():Grazie al caching, la seconda chiamata impiega sensibilmente meno tempo.
Per impostazione predefinita, la cache conserva i primi 128 valori calcolati. Quando i 128 slot sono pieni, l’algoritmo elimina il valore meno usato di recente (LRU) per far spazio ai nuovi.
Possiamo impostare una dimensione massima diversa quando applichiamo il decorator usando il parametro
maxsize:In questo caso, la cache conserva solo cinque valori. Possiamo anche impostare
maxsizeaNonese non vogliamo limitare la dimensione della cache.Caching in Python con functools.cache()
Python 3.9 include un decorator per il caching più semplice e veloce:
functools.cache(). Questo decorator ha due caratteristiche principali:functools.lru_cache(maxsize=None).Applichiamo il decorator
@functools.cachealla funzionesum_digits():Decorare
sum_digits()con@functools.cacheequivale ad assegnaresum_digitsafunctools.cache():Nota che possiamo anche usare un diverso stile di import:
In questo modo possiamo decorare le funzioni usando semplicemente
@cache.Altre strategie di caching
Gli strumenti integrati di Python implementano la strategia LRU, in cui le voci meno usate di recente vengono eliminate per far spazio ai nuovi valori.
Vediamo ora alcune altre strategie di caching:
Queste strategie possono essere combinate con misure della durata di validità: indica per quanto tempo un dato in cache è considerato valido o rilevante. Immagina un articolo di news in cache: potrebbe essere consultato spesso (LRU lo terrebbe), ma dopo una settimana potrebbe essere superato.
Caching in Python: casi d’uso comuni
Finora abbiamo usato esempi semplici per imparare. Nella pratica, però, il caching ha moltissime applicazioni reali.
Nel data science eseguiamo spesso operazioni ripetute su dataset di grandi dimensioni. Usare risultati in cache riduce il tempo e i costi associati al ripetere gli stessi calcoli sugli stessi dataset.
Possiamo anche usare il caching per salvare risorse esterne come pagine web o database. Consideriamo un esempio e mettiamo in cache un articolo di DataCamp. Prima, però, dobbiamo installare il modulo di terze parti
requestseseguendo la riga seguente nel terminale:Una volta installato
requests, possiamo provare il codice seguente, che tenta di recuperare due volte lo stesso articolo di DataCamp usando il decorator@lru_cache:A margine, abbiamo troncato l’output perché è molto lungo. Nota però che solo la prima chiamata a
get_article()stampa la fraseFetching article from {url}.Questo perché la pagina web viene effettivamente scaricata solo la prima volta. Il risultato viene memorizzato nella cache della funzione. Quando richiediamo la stessa pagina la seconda volta, i dati vengono restituiti dalla cache.
Il caching evita ritardi inutili quando si recuperano ripetutamente gli stessi dati. Le API esterne hanno spesso limiti di rate e costi associati alle richieste. Il caching riduce i costi delle API e la probabilità di raggiungere i limiti di rate.
Un altro caso d’uso tipico è nelle applicazioni di machine learning, dove vanno ripetute varie elaborazioni costose. Ad esempio, se dobbiamo tokenizzare e vettorializzare un testo prima di usarlo in un modello di machine learning, possiamo memorizzare l’output in cache: così non dovremo ripetere operazioni computazionalmente onerose.
Sfide comuni del caching in Python
Abbiamo visto i vantaggi del caching in Python. Ci sono però anche alcune sfide e controindicazioni da considerare quando lo si implementa:
Conclusione
Possiamo usare il caching per ottimizzare le prestazioni quando operazioni computazionalmente intense vengono ripetute sugli stessi dati.
Python mette a disposizione due decorator per creare una cache nelle chiamate di funzione:
@lru_cachee@cachenel modulofunctools.Dobbiamo però assicurarci di mantenere aggiornata la cache e gestire correttamente la memoria.
Se vuoi approfondire caching e Python, dai un’occhiata a questo percorso di 6 corsi Python Programming.