Accéder au contenu principal

Cache Python : Deux méthodes simples

Apprenez à utiliser des décorateurs tels que @functools.lru_cache ou @functools.cache pour mettre en cache des fonctions en Python.
Actualisé 8 sept. 2025  · 12 min de lecture

Dans cet article, nous allons découvrir la mise en cache en Python. Nous allons examiner ce que c'est et comment l'utiliser efficacement.

La mise en cache est une technique utilisée pour améliorer les performances des applications en stockant temporairement les résultats obtenus par le programme afin de les réutiliser si nécessaire ultérieurement.

Dans ce tutoriel, nous allons explorer différentes techniques de mise en cache en Python, notamment les décorateurs @lru_cache et @cache du module functools.

Pour ceux d'entre vous qui sont pressés, commençons par une implémentation très succincte de la mise en cache, puis nous poursuivrons avec plus de détails.

Réponse courte : Mise en œuvre de la mise en cache Python

Pour créer un cache en Python, nous pouvons utiliser le décorateur @cache du module functools. Dans le code ci-dessous, veuillez noter que la fonction print() n'est exécutée qu'une seule fois :

import functools

@functools.cache
def square(n):
    print(f"Calculating square of {n}")
    return n * n

# Testing the cached function
print(square(4))
print(square(4))

# Calculating square of 4
# 16
# 16

Qu'est-ce que la mise en cache en Python ?

Supposons que nous devions résoudre un problème mathématique et que nous passions une heure à trouver la bonne réponse. Si nous devions résoudre le même problème le lendemain, il serait utile de réutiliser notre travail précédent plutôt que de tout recommencer.

La mise en cache en Python suit un principe similaire : elle stocke les valeurs lorsqu'elles sont calculées dans les appels de fonction afin de les réutiliser lorsque cela est nécessaire. Ce type de mise en cache est également appelé mémorisation.

Examinons un bref exemple qui calcule deux fois la somme d'une large plage de nombres :

output = sum(range(100_000_001))
print(output)
output = sum(range(100_000_001))
print(output)

# 5000000050000000
# 5000000050000000

Le programme doit calculer la somme à chaque fois. Nous pouvons le confirmer en chronométrant les deux appels :

import timeit

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

Le résultat montre que les deux appels prennent approximativement le même temps (selon notre configuration, les temps d'exécution peuvent être plus rapides ou plus lents).

Cependant, nous pouvons utiliser un cache pour éviter de calculer plusieurs fois la même valeur. Nous pouvons redéfinir le nom sum à l'aide de la fonction cache() du module intégré functools:

import functools
import timeit

sum = functools.cache(sum)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

print(
    timeit.timeit(
        "sum(range(100_000_001))",
        globals=globals(),
        number=1,
    )
)

Le deuxième appel prend désormais quelques microsecondes au lieu de plus d'une seconde, car le résultat de la somme des nombres compris entre 0 et 100 000 000 a déjà été calculé et mis en cache. Le deuxième appel utilise la valeur calculée et stockée précédemment.

Ci-dessus, nous utilisons le décorateur functools.cache() pour inclure un cache à la fonction intégrée sum(). À titre d'information, un décorateur en Python est une fonction qui modifie le comportement d'une autre fonction sans changer de manière permanente son code. Vous pouvez en apprendre davantage sur les décorateurs dans ce tutoriel sur les décorateurs Python.

Le décorateur functools.cache() a été ajouté à Python dans la version 3.9, mais il est possible d'utiliser functools.lru_cache() pour les versions antérieures. Dans la section suivante, nous examinerons ces deux méthodes pour créer un cache, y compris l'utilisation de la notation décoratrice plus fréquemment utilisée, telle que @cache.

Mise en cache Python : Différentes méthodes

Le module Python functools comprend deux décorateurs permettant d'appliquer la mise en cache aux fonctions. Veuillez explorer functools.lru_cache() et functools.cache() à l'aide d'un exemple.

Écrivons une fonction sum_digits() qui prend en entrée une séquence de nombres et renvoie la somme des chiffres de ces nombres. Par exemple, si nous utilisons le tuple (23, 43, 8) comme entrée, alors :

  • La somme des chiffres de 23 est cinq.
  • La somme des chiffres de 43 est sept.
  • La somme des chiffres de 8 est huit.
  • Par conséquent, la somme totale est de 20.

Voici une manière d'écrire notre fonction d'sum_digits():

def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = 23, 43, 8

print(sum_digits(numbers))

# 20

Utilisons cette fonction pour explorer différentes façons de créer un cache.

Mise en cache manuelle Python

Commençons par créer le cache manuellement. Bien que nous puissions également automatiser cette opération facilement, la création manuelle d'un cache nous aide à mieux comprendre le processus.

Créons un dictionnaire et ajoutons des paires clé-valeur à chaque fois que nous appelons la fonction avec une nouvelle valeur afin de stocker les résultats. Si nous appelons la fonction avec une valeur déjà stockée dans ce dictionnaire, la fonction renverra la valeur stockée sans la recalculer :

import random
import timeit

def sum_digits(numbers):
    if numbers not in sum_digits.my_cache:
        sum_digits.my_cache[numbers] = sum(
            int(digit) for number in numbers for digit in str(number)
        )
    return sum_digits.my_cache[numbers]
sum_digits.my_cache = {}

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

Le deuxième appel à sum_digits(numbers) est beaucoup plus rapide que le premier, car il utilise la valeur mise en cache.

Nous allons maintenant expliquer le code ci-dessus plus en détail. Tout d'abord, veuillez noter que nous créons le dictionnaire sum_digits.my_cache après avoir défini la fonction, même si nous l'utilisons dans la définition de la fonction.

La fonction sum_digits() vérifie si l'argument transmis à la fonction figure déjà parmi les clés du dictionnaire sum_digits.my_cache. La somme de tous les chiffres n'est évaluée que si l'argument ne se trouve pas déjà dans le cache.

Étant donné que l'argument que nous utilisons lors de l'appel de la fonction sert de clé dans le dictionnaire, il doit s'agir d'un type de données hachable. Une liste n'est pas hachable, nous ne pouvons donc pas l'utiliser comme clé dans un dictionnaire. Par exemple, essayons de remplacer numbers par une liste au lieu d'un tuple. Cela provoquera une exception TypeError:

# ...

numbers = [random.randint(1, 1000) for _ in range(1_000_000)]

# ...
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'

Créer un cache manuellement est très utile à des fins d'apprentissage, mais explorons maintenant des méthodes plus rapides pour y parvenir.

Mise en cache Python avec functools.lru_cache()

Python dispose du décorateur lru_cache() depuis la version 3.2. Le sigle « lru » au début du nom de la fonction signifie « least recently used » (moins récemment utilisé). Nous pouvons considérer le cache comme une boîte destinée à stocker les éléments fréquemment utilisés. Lorsqu'il est plein, la stratégie LRU supprime l'élément que nous n'avons pas utilisé depuis le plus longtemps afin de libérer de l'espace pour de nouveaux éléments.

Veuillez décorer notre fonction sum_digits() avec @functools.lru_cache:

import functools
import random
import timeit

@functools.lru_cache
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

Grâce à la mise en cache, le deuxième appel s'exécute beaucoup plus rapidement.

Par défaut, le cache stocke les 128 premières valeurs calculées. Une fois les 128 emplacements occupés, l'algorithme supprime la valeur la moins récemment utilisée (LRU) afin de libérer de l'espace pour les nouvelles valeurs.

Nous pouvons définir une taille maximale différente pour le cache lorsque nous décorons la fonction à l'aide du paramètre 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)
    )

# ...

Dans ce cas, le cache ne stocke que cinq valeurs. Nous pouvons également définir l'argument maxsize sur None si nous ne souhaitons pas limiter la taille du cache.

Mise en cache Python avec functools.cache()

Python 3.9 inclut un décorateur de mise en cache plus simple et plus rapide : functools.cache(). Ce décorateur présente deux caractéristiques principales :

  • Il n'y a pas de taille maximale, cela revient à appeler functools.lru_cache(maxsize=None).
  • Il stocke tous les appels de fonction et leurs résultats (il n'utilise pas la stratégie LRU). Cette approche convient aux fonctions dont les sorties sont relativement modestes ou lorsque les contraintes liées à la taille du cache ne sont pas un sujet de préoccupation.

Utilisons le décorateur @functools.cache sur la fonction sum_digits() :

import functools
import random
import timeit

@functools.cache
def sum_digits(numbers):
    return sum(
        int(digit) for number in numbers for digit in str(number)
    )

numbers = tuple(random.randint(1, 1000) for _ in range(1_000_000))

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

print(
    timeit.timeit(
        "sum_digits(numbers)",
        globals=globals(),
        number=1
    )
)

Définir sum_digits() avec @functools.cache revient à assigner sum_digits à 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)

Veuillez noter que nous pouvons également utiliser un autre style d'importation :

from functools import cache

De cette manière, nous pouvons personnaliser nos fonctions en utilisant uniquement @cache.

Autres stratégies de mise en cache

Les outils propres à Python mettent en œuvre la stratégie de mise en cache LRU, dans laquelle les entrées les moins récemment utilisées sont supprimées afin de libérer de l'espace pour les nouvelles valeurs.

Examinons quelques autres stratégies de mise en cache :

  • Premier entré, premier sorti (FIFO): Lorsque le cache est plein, l'élément ajouté en premier est supprimé afin de libérer de l'espace pour les nouvelles valeurs. La différence entre LRU et FIFO réside dans le fait que LRU conserve les éléments récemment utilisés dans le cache, tandis que FIFO supprime l'élément le plus ancien, quelle que soit son utilisation.
  • Dernier entré, premier sorti (LIFO): L'élément ajouté le plus récemment est supprimé lorsque le cache est plein. Veuillez imaginer une pile d'assiettes dans une cafétéria. La plaque que nous avons placée en dernier sur la pile (last in) est celle que nous retirerons en premier (first out).
  • Dernière utilisation (MRU): La valeur la plus récemment utilisée est supprimée lorsque de l'espace est nécessaire dans le cache.
  • Remplacement aléatoire (RR): Cette stratégie consiste à supprimer un élément de manière aléatoire afin de faire de la place pour un nouvel élément.

Ces stratégies peuvent également être associées à des mesures de durée de vie valide, qui désigne la durée pendant laquelle une donnée stockée dans le cache est considérée comme valide ou pertinente. Veuillez imaginer un article de presse dans un cache. Il peut être fréquemment consulté (LRU le conserverait), mais après une semaine, l'information pourrait être obsolète.

Mise en cache Python : Cas d'utilisation courants

Jusqu'à présent, nous avons utilisé des exemples simplifiés à des fins pédagogiques. Cependant, la mise en cache présente de nombreuses applications concrètes.

En science des données, nous effectuons fréquemment des opérations répétitives sur de grands ensembles de données. L'utilisation de résultats mis en cache réduit le temps et les coûts associés à l'exécution répétée des mêmes calculs sur les mêmes ensembles de données.

Nous pouvons également utiliser la mise en cache pour enregistrer des ressources externes telles que des pages Web ou des bases de données. Prenons un exemple et mettons en cache un article DataCamp. Cependant, nous devons d'abord installer le module tiers requests en exécutant la ligne suivante dans le terminal :

$ python -m pip install requests

Une fois requests installé, nous pouvons tester le code suivant, qui tente de récupérer deux fois le même article DataCamp tout en utilisant le décorateur @lru_cache:

import requests
from functools import lru_cache

@lru_cache(maxsize=10)
def get_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"))

À titre d'information, nous avons tronqué la sortie car elle est très longue. Veuillez noter, cependant, que seul le premier appel à get_article() affiche la phrase Fetching article from {url}.

En effet, la page Web n'est accessible que lors du premier appel. Le résultat est enregistré dans le cache de la fonction. Lorsque nous demandons la même page Web une deuxième fois, les données stockées dans le cache sont renvoyées à la place.

La mise en cache garantit qu'il n'y a pas de retards inutiles lors de la récupération répétée des mêmes données. Les API externes ont souvent des limites de débit et des coûts associés à la récupération des données. La mise en cache réduit les coûts liés aux API et le risque d'atteindre les limites de débit.

Un autre cas d'utilisation courant concerne les applications d'apprentissage automatique, où plusieurs calculs complexes doivent être répétés. Par exemple, si nous devons tokeniser et vectoriser un texte avant de l'utiliser dans un modèle d'apprentissage automatique, nous pouvons stocker le résultat dans un cache. De cette manière, nous n'aurons pas besoin de répéter les opérations qui nécessitent une grande puissance de calcul.

Difficultés courantes lors de la mise en cache en Python

Nous avons découvert les avantages de la mise en cache dans Python. Il existe également certains défis et inconvénients à prendre en compte lors de la mise en œuvre d'un cache :

  • Invalidation et cohérence du cache: Les données peuvent évoluer au fil du temps. Par conséquent, les valeurs stockées dans un cache peuvent également devoir être mises à jour ou supprimées.
  • Gestion de la mémoire: Le stockage de grandes quantités de données dans un cache nécessite de la mémoire, ce qui peut entraîner des problèmes de performances si le cache augmente indéfiniment.
  • Complexité: L'ajout de caches introduit une certaine complexité dans le système lors de la création et de la maintenance du cache. Souvent, les avantages l'emportent sur ces coûts, mais cette complexité accrue peut entraîner des erreurs difficiles à détecter et à corriger.

Conclusion

Nous pouvons utiliser la mise en cache pour optimiser les performances lorsque des opérations nécessitant une puissance de calcul importante sont répétées sur les mêmes données.

Python dispose de deux décorateurs permettant de créer un cache lors de l'appel de fonctions : @lru_cache et @cache dans le module functools.

Il est toutefois nécessaire de veiller à ce que le cache soit régulièrement mis à jour et que la mémoire soit gérée de manière adéquate.

Si vous souhaitez approfondir vos connaissances en matière de mise en cache et de Python, nous vous invitons à consulter ce cursus de formation en programmation Python composé de six cours.


Stephen Gruppetta's photo
Author
Stephen Gruppetta
LinkedIn
Twitter

J'ai étudié la physique et les mathématiques au niveau UG à l'université de Malte. J'ai ensuite déménagé à Londres et obtenu un doctorat en physique à l'Imperial College. J'ai travaillé sur de nouvelles techniques optiques pour obtenir des images de la rétine humaine. Aujourd'hui, je me concentre sur l'écriture, la communication et l'enseignement de Python.

Sujets

Apprenez Python pour la science des données.

Cours

Boîte à outils Python

4 h
304.8K
Renforcez vos compétences en science des données avec Python : maîtrisez les itérateurs et les compréhensions de listes.
Afficher les détailsRight Arrow
Commencer le cours
Voir plusRight Arrow
Apparenté

Didacticiel

30 astuces Python pour améliorer votre code, accompagnées d'exemples

Nous avons sélectionné 30 astuces Python intéressantes que vous pouvez utiliser pour améliorer votre code et développer vos compétences en Python.
Kurtis Pykes 's photo

Kurtis Pykes

Didacticiel

Tutoriel sur les méthodes .append() et .extend() de Python

Apprenez à utiliser les méthodes .append() et .extend() pour ajouter des éléments à une liste.
DataCamp Team's photo

DataCamp Team

Didacticiel

Séquence de Fibonacci en Python : Apprenez et explorez les techniques de codage

Veuillez découvrir le fonctionnement de la suite de Fibonacci. Veuillez explorer ses propriétés mathématiques et ses applications concrètes.
Laiba Siddiqui's photo

Laiba Siddiqui

Didacticiel

Comment tronquer une chaîne en Python : Trois méthodes distinctes

Apprenez les principes fondamentaux de la suppression des caractères de début et de fin d'une chaîne en Python.
Adel Nehme's photo

Adel Nehme

Didacticiel

Comment diviser des listes en Python : Exemples de base et méthodes avancées

Apprenez à diviser des listes Python à l'aide de techniques telles que le découpage, les compréhensions de listes et itertools. Veuillez découvrir quand utiliser chaque méthode pour une gestion optimale des données.
Allan Ouko's photo

Allan Ouko

Didacticiel

Comment élever un nombre au carré en Python : Exemples de base et méthodes avancées

La fonction carrée en Python est simple : Veuillez utiliser l'opérateur intégré ** ou envisager NumPy, pow(), math.pow(), les opérateurs bit à bit et d'autres fonctions pour des solutions plus polyvalentes.
Allan Ouko's photo

Allan Ouko

Voir plusVoir plus