Direkt zum Inhalt

Python-Cache: Zwei einfache Methoden

Lerne, wie du Dekoratoren wie @functools.lru_cache oder @functools.cache benutzt, um Funktionen in Python zwischenzuspeichern.
Aktualisierte 8. Sept. 2025  · 12 Min. Lesezeit

In diesem Artikel schauen wir uns das Caching in Python an. Wir werden verstehen, was es ist und wie man es effektiv nutzt.

Caching ist eine Technik, die die Leistung von Anwendungen verbessert, indem sie die Ergebnisse, die das Programm liefert, vorübergehend speichert, um sie später bei Bedarf wiederzuverwenden.

In diesem Tutorial lernen wir verschiedene Techniken zum Caching in Python kennen, darunter die Dekoratoren @lru_cache und @cache aus dem Modul functools.

Für alle, die es eilig haben: Lasst uns mit einer ganz einfachen Caching-Implementierung anfangen und dann mit den Details weitermachen.

Kurze Antwort: Python-Caching-Implementierung

Um einen Cache in Python zu erstellen, können wir den Dekorator @cache aus dem Modul functools verwenden. Schau mal, im Code unten wird die Funktion print() nur einmal ausgeführt:

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

Was ist Caching in Python?

Angenommen, wir müssen ein Matheproblem lösen und brauchen eine Stunde, um die richtige Antwort zu finden. Wenn wir am nächsten Tag dasselbe Problem lösen müssten, wäre es echt praktisch, unsere alte Arbeit wiederzuverwenden, anstatt wieder von vorne anzufangen.

Das Caching in Python funktioniert ähnlich – es speichert Werte, wenn sie in Funktionsaufrufen berechnet werden, um sie bei Bedarf wieder zu verwenden. Diese Art des Cachings wird auch als Memoisierung bezeichnet.

Schauen wir uns mal ein kurzes Beispiel an, das die Summe eines großen Zahlenbereichs zweimal berechnet:

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

# 5000000050000000
# 5000000050000000

Das Programm muss jedes Mal die Summe berechnen. Wir können das überprüfen, indem wir die Dauer der beiden Anrufe messen:

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,
    )
)

Die Ausgabe zeigt, dass beide Aufrufe ungefähr gleich lange dauern (je nach unserer Konfiguration können die Ausführungszeiten schneller oder langsamer sein).

Wir können aber einen Cache nutzen, um zu vermeiden, dass wir denselben Wert mehrmals berechnen müssen. Wir können den Namen sum mit der Funktion cache() aus dem integrierten Modul functools neu definieren:

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,
    )
)

Der zweite Aufruf dauert jetzt nur noch ein paar Mikrosekunden statt über eine Sekunde, weil das Ergebnis der Summe der Zahlen von 0 bis 100.000.000 schon berechnet und zwischengespeichert wurde – der zweite Aufruf nutzt den Wert, der vorher berechnet und gespeichert wurde.

Oben benutzen wir den Dekorator functools.cache(), um einen Cache in die eingebaute Funktion sum() einzubauen. Übrigens, ein Dekorator in Python ist eine Funktion, die das Verhalten einer anderen Funktion ändert, ohne ihren Code dauerhaft zu verändern. Mehr über Dekoratoren erfährst du in diesem Python-Dekoratoren-Tutorial.

Der Dekorator functools.cache() wurde in Python in Version 3.9 hinzugefügt, aber für ältere Versionen können wir functools.lru_cache() verwenden. Im nächsten Abschnitt schauen wir uns beide Möglichkeiten zum Erstellen eines Caches an, auch mit der häufiger verwendeten Dekorator-Notation, wie zum Beispiel @cache.

Python-Caching: Verschiedene Methoden

Das Python-Modul functools hat zwei Dekoratoren, mit denen man Caching auf Funktionen anwenden kann. Schauen wir uns mal functools.lru_cache() und functools.cache() anhand eines Beispiels an.

sum_digits() Schreib mal eine Funktion namens `sumDigits`, die eine Folge von Zahlen nimmt und die Summe der Ziffern dieser Zahlen zurückgibt. Wenn wir zum Beispiel die Tupel (23, 43, 8) als Eingabe nehmen, dann:

  • Die Summe der Ziffern von 23 ist fünf.
  • Die Summe der Ziffern von 43 ist sieben.
  • Die Summe der Ziffern von 8  ist acht.
  • Also, die Gesamtsumme ist 20.

So können wir unsere Funktion sum_digits() schreiben:

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

Lass uns diese Funktion nutzen, um verschiedene Möglichkeiten zum Erstellen eines Caches zu erkunden.

Python-Handbuch zum Caching

Lass uns zuerst den Cache manuell erstellen. Wir könnten das zwar auch einfach automatisieren, aber wenn wir den Cache manuell erstellen, verstehen wir den Prozess besser.

Lass uns ein Wörterbuch erstellen und jedes Mal, wenn wir die Funktion mit einem neuen Wert aufrufen, Schlüssel-Wert-Paare hinzufügen, um die Ergebnisse zu speichern. Wenn wir die Funktion mit einem Wert aufrufen, der schon in diesem Wörterbuch gespeichert ist, gibt die Funktion den gespeicherten Wert zurück, ohne ihn erneut zu berechnen:

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
    )
)

Der zweite Aufruf von sum_digits(numbers) geht viel schneller als der erste, weil er den zwischengespeicherten Wert nutzt.

Lass uns jetzt den Code oben genauer erklären. Zuerst mal, schau mal, wir erstellen das Wörterbuch sum_digits.my_cache, nachdem wir die Funktion definiert haben, obwohl wir es in der Funktionsdefinition benutzen.

Die Funktion sum_digits() checkt, ob das an die Funktion übergebene Argument schon einer der Schlüssel im Wörterbuch sum_digits.my_cache ist. Die Summe aller Ziffern wird nur berechnet, wenn das Argument nicht schon im Cache ist.

Da das Argument, das wir beim Aufruf der Funktion verwenden, als Schlüssel im Wörterbuch dient, muss es ein hashfähiger Datentyp sein. Eine Liste kann nicht gehasht werden, deshalb können wir sie nicht als Schlüssel in einem Wörterbuch verwenden. Versuchen wir mal, numbers durch eine Liste statt durch ein Tupel zu ersetzen – das führt zu einem Fehler TypeError:

# ...

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

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

Das manuelle Erstellen eines Caches ist super zum Lernen, aber schauen wir uns jetzt mal schnellere Methoden an.

Python-Caching mit functools.lru_cache()

Python hat seit Version 3.2 den Dekorator lru_cache(). Das „lru” am Anfang des Funktionsnamens steht für „least recently used” (zuletzt am wenigsten benutzt). Wir können uns den Cache wie eine Box vorstellen, in der oft benutzte Sachen gespeichert werden – wenn sie voll ist, wirft die LRU-Strategie das Element weg, das wir am längsten nicht benutzt haben, um Platz für etwas Neues zu schaffen.

Lass uns unsere Funktion sum_digits() mit @functools.lru_cache verschönern:

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
    )
)

Dank Caching dauert der zweite Aufruf deutlich weniger Zeit.

Standardmäßig speichert der Cache die ersten 128 berechneten Werte. Sobald alle 128 Plätze voll sind, löscht der Algorithmus den am wenigsten kürzlich verwendeten Wert (LRU), um Platz für neue Werte zu schaffen.

Wir können eine andere maximale Cache-Größe festlegen, wenn wir die Funktion mit dem Parameter maxsize dekorieren:

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)
    )

# ...

In diesem Fall speichert der Cache nur fünf Werte. Wir können auch das Argument maxsize auf None setzen, wenn wir die Größe des Caches nicht begrenzen wollen.

Python-Caching mit functools.cache()

Python 3.9 hat jetzt einen einfacheren und schnelleren Caching-Dekorator –functools.cache(). Dieser Dekorateur hat zwei Hauptmerkmale:

  • Es gibt keine maximale Größe – es ist so ähnlich wie der Aufruf von functools.lru_cache(maxsize=None).
  • Es speichert alle Funktionsaufrufe und ihre Ergebnisse (es nutzt nicht die LRU-Strategie). Das ist gut für Funktionen mit relativ kleinen Ausgaben oder wenn wir uns keine Gedanken über Cache-Größenbeschränkungen machen müssen.

Probier mal den Dekorator @functools.cache für die Funktion sum_digits() aus:

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
    )
)

Das Dekorieren von sum_digits() mit @functools.cache ist dasselbe wie das Zuweisen von sum_digits zu 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)

Beachte, dass wir auch einen anderen Importstil verwenden können:

from functools import cache

So können wir unsere Funktionen einfach mit @cache dekorieren.

Andere Caching-Strategien

Die Tools von Python nutzen die LRU-Caching-Strategie, bei der die am wenigsten genutzten Einträge gelöscht werden, um Platz für neue Werte zu schaffen.

Schauen wir uns ein paar andere Caching-Strategien an:

  • Wer zuerst kommt, mahlt zuerst (FIFO): Wenn der Cache voll ist, wird das zuerst hinzugefügte Element gelöscht, um Platz für neue Werte zu schaffen. Der Unterschied zwischen LRU und FIFO ist, dass LRU die zuletzt verwendeten Elemente im Cache behält, während FIFO das älteste Element unabhängig von seiner Verwendung löscht.
  • Letzte rein, erste raus (LIFO): Wenn der Cache voll ist, wird der zuletzt hinzugefügte Eintrag gelöscht. Stell dir einen Stapel Teller in einer Cafeteria vor. Der Teller, den wir zuletzt auf den Stapel gelegt haben (Last in), ist der, den wir als erstes wieder wegnehmen (First out).
  • Zuletzt benutzt (MRU): Der zuletzt verwendete Wert wird gelöscht, wenn Platz im Cache gebraucht wird.
  • Zufälliger Austausch (RR): Diese Strategie wirft einfach irgendwas weg, um Platz für was Neues zu schaffen.

Diese Strategien können auch mit Maßnahmen zur gültigen Lebensdauer kombiniert werden – das bedeutet, wie lange ein Datenelement im Cache als gültig oder relevant angesehen wird. Stell dir einen Nachrichtenartikel in einem Cache vor. Es könnte oft aufgerufen werden (LRU würde es behalten), aber nach einer Woche könnten die Nachrichten schon veraltet sein.

Python-Caching: Häufige Anwendungsfälle

Bisher haben wir einfache Beispiele zum Lernen benutzt. Caching hat aber echt viele praktische Anwendungen.

In der Datenwissenschaft machen wir oft immer wieder die gleichen Sachen mit großen Datensätzen. Durch die Verwendung zwischengespeicherter Ergebnisse sparst du Zeit und Kosten, die entstehen, wenn du dieselben Berechnungen immer wieder mit denselben Datensätzen machst.

Wir können Caching auch zum Speichern externer Ressourcen wie Webseiten oder Datenbanken nutzen. Schauen wir uns mal ein Beispiel an und speichern einen DataCamp-Artikel im Cache. Aber zuerst müssen wir das Modul requests von Drittanbietern installieren, indem wir die folgende Zeile im Terminal ausführen:

$ python -m pip install requests

Sobald requests installiert ist, können wir den folgenden Code ausprobieren, der versucht, denselben DataCamp-Artikel zweimal abzurufen, während der Dekorator @lru_cache verwendet wird:

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"))

Übrigens haben wir die Ausgabe gekürzt, weil sie echt lang ist. Beachte aber, dass nur der erste Aufruf von get_article() den Ausdruck Fetching article from {url} ausgibt.

Das liegt daran, dass die Webseite nur beim ersten Anruf aufgerufen wird. Das Ergebnis wird im Cache der Funktion gespeichert. Wenn wir dieselbe Webseite ein zweites Mal aufrufen, werden stattdessen die im Cache gespeicherten Daten zurückgegeben.

Durch Caching wird sichergestellt, dass es keine unnötigen Verzögerungen gibt, wenn dieselben Daten immer wieder abgerufen werden. Externe APIs haben oft auch Nutzungsbeschränkungen und Kosten, die beim Abrufen von Daten anfallen. Caching spart API-Kosten und verringert die Wahrscheinlichkeit, dass man an die Ratenbegrenzungen stößt.

Ein weiterer häufiger Anwendungsfall sind Machine-Learning-Anwendungen, bei denen mehrere aufwendige Berechnungen wiederholt werden müssen. Wenn wir zum Beispiel einen Text tokenisieren und vektorisieren müssen, bevor wir ihn in einem Machine-Learning-Modell verwenden, können wir das Ergebnis in einem Cache speichern. So müssen wir die rechenintensiven Vorgänge nicht wiederholen.

Häufige Probleme beim Caching in Python

Wir haben die Vorteile des Cachings in Python kennengelernt. Bei der Implementierung eines Caches gibt's auch ein paar Herausforderungen und Nachteile, die man im Auge behalten sollte:

  • Cache-Invalidierung und Konsistenz: Daten können sich mit der Zeit ändern. Deshalb müssen die Werte, die im Cache gespeichert sind, vielleicht auch mal aktualisiert oder gelöscht werden.
  • Speicherverwaltung: Das Speichern großer Datenmengen im Cache braucht Speicherplatz, und das kann zu Performance-Problemen führen, wenn der Cache immer weiter wächst.
  • Komplexität: Das Hinzufügen von Caches macht das System beim Erstellen und Verwalten des Caches komplizierter. Oft sind die Vorteile größer als die Kosten, aber diese zusätzliche Komplexität kann zu Fehlern führen, die schwer zu finden und zu beheben sind.

Fazit

Wir können Caching nutzen, um die Leistung zu verbessern, wenn rechenintensive Vorgänge mit denselben Daten wiederholt werden.

Python hat zwei Dekoratoren, um beim Aufruf von Funktionen einen Cache zu erstellen: @lru_cache und @cache im Modul functools.

Wir müssen aber sicherstellen, dass wir den Cache immer auf dem neuesten Stand halten und den Speicher richtig verwalten.

Wenn du mehr über Caching und Python lernen willst, schau dir diesen sechsteiligen Lernpfad zur Python-Programmierung an.


Stephen Gruppetta's photo
Author
Stephen Gruppetta
LinkedIn
Twitter

Ich habe Physik und Mathematik auf UG-Ebene an der Universität Malta studiert. Dann zog ich nach London und machte meinen Doktor in Physik am Imperial College. Ich habe an neuartigen optischen Techniken zur Abbildung der menschlichen Netzhaut gearbeitet. Jetzt konzentriere ich mich darauf, über Python zu schreiben, über Python zu kommunizieren und Python zu unterrichten.

Themen

Lerne Python für die Datenwissenschaft!

Kurs

Python Toolbox

4 Std.
304.8K
Erweitere deine Data-Science-Fähigkeiten und lerne, wie Iteratoren funktionieren und wie neue Listen aus vorhandenen Listen erstellt werden.
Siehe DetailsRight Arrow
Kurs starten
Mehr anzeigenRight Arrow
Verwandt

Lernprogramm

Fibonacci-Folge in Python: Lerne und entdecke Programmiertechniken

Finde raus, wie die Fibonacci-Folge funktioniert. Schau dir die mathematischen Eigenschaften und die Anwendungen in der echten Welt an.
Laiba Siddiqui's photo

Laiba Siddiqui

Lernprogramm

Wie man Listen in Python aufteilt: Einfache Beispiele und fortgeschrittene Methoden

Lerne, wie du Python-Listen mit Techniken wie Slicing, List Comprehensions und itertools aufteilen kannst. Finde heraus, wann du welche Methode für die beste Datenverarbeitung nutzen solltest.
Allan Ouko's photo

Allan Ouko

Lernprogramm

30 coole Python-Tricks für besseren Code mit Beispielen

Wir haben 30 coole Python-Tricks zusammengestellt, mit denen du deinen Code verbessern und deine Python-Kenntnisse ausbauen kannst.
Kurtis Pykes 's photo

Kurtis Pykes

Lernprogramm

Python-Tutorial zum Verknüpfen von Zeichenfolgen

Lerne verschiedene Methoden zum Verknüpfen von Zeichenfolgen in Python kennen, mit Beispielen, die jede Technik zeigen.
DataCamp Team's photo

DataCamp Team

Lernprogramm

So kürzt man eine Zeichenfolge in Python: Drei verschiedene Methoden

Lerne die Grundlagen zum Entfernen von führenden und nachfolgenden Zeichen aus einer Zeichenfolge in Python.
Adel Nehme's photo

Adel Nehme

Lernprogramm

Python range()-Funktion Tutorial

Lerne anhand von Beispielen die Python-Funktion range() und ihre Möglichkeiten kennen.
Aditya Sharma's photo

Aditya Sharma

Mehr anzeigenMehr anzeigen