← Alle Beiträge
Werkzeuge

Python 3.15: Lazy Imports verkürzen den Start, frozendict schützt Konfigurationen

Die stabile Hauptversion lädt ausgewählte Module erst bei Bedarf, ergänzt zwei nützliche Datentypen, setzt UTF-8 als Standard und beschleunigt das experimentelle JIT.

7 Minuten Lesezeit

Python 3.15.0 ist seit dem 9. Oktober 2026 die stabile neue Hauptversion der Programmiersprache und des CPython-Interpreters. Die auffälligste Neuerung setzt direkt beim Programmstart an: Importe lassen sich ausdrücklich als „lazy“ markieren. Das Modul wird dann nicht sofort ausgeführt und geladen, sondern erst beim ersten Zugriff auf den dadurch gebundenen Namen.

Das ist mehr als eine neue Schreibweise. Anwendungen mit großen Abhängigkeitsbäumen können ihren schnellen Einstieg von selten genutzten Funktionen entkoppeln. Ein Kommandozeilenprogramm muss beispielsweise Bibliotheken für Berichtsexport, Visualisierung oder Datenanalyse nicht mehr vorsorglich laden, wenn die aufgerufene Aktion nur eine Konfigurationsdatei prüft. Ergänzt wird die Version durch frozendict, den eingebauten Typ sentinel, UTF-8 als Standardkodierung und ein beschleunigtes, weiterhin experimentelles JIT.

Ein Exportmodul wartet bis zum ersten Zugriff

PEP 810 führt lazy als Soft-Keyword für import- und from-Importanweisungen ein. Eine Anwendung kann damit einen optionalen, aber teuren Bestandteil auf Modulebene vormerken:

Technisches Detail: lazy import report_renderer; def validate(config):; return config.is_valid(); def export(data, target):.

Beim Aufruf von validate() wird report_renderer nicht benötigt und deshalb auch nicht geladen. Erst export() greift auf den gebundenen Namen zu und löst den Import aus. Dasselbe Prinzip ist für eine from-Importanweisung vorgesehen: Der aufgeschobene Import wird fällig, sobald der importierte Name erstmals verwendet wird.

Vor Python 3.15 ließ sich ein ähnlicher Effekt nur erreichen, indem Entwickler den Import etwa in eine Funktion verschoben oder eine eigene Importmechanik verwendeten. Das verteilte Importentscheidungen über den Code und machte schwerer erkennbar, welche Abhängigkeiten grundsätzlich zu einem Modul gehören. Die neue Syntax hält die Deklaration auf Modulebene und kennzeichnet die beabsichtigte Verzögerung ausdrücklich.

Wo Lazy Imports den größten Unterschied machen

Der Nutzen entsteht nicht bei jedem Import gleichermaßen. Besonders passend sind Abhängigkeiten, die teuer zu initialisieren sind, aber nur in einem Teil der Programmläufe gebraucht werden. Ein CLI-Werkzeug mit den Befehlen check, convert und plot kann das Visualisierungsmodul für plot verzögert laden. Der häufig genutzte Befehl check startet dadurch, ohne den kompletten Grafikzweig des Abhängigkeitsbaums abzuarbeiten.

Auch Anwendungen mit mehreren optionalen Ausgabeformaten profitieren. Wird ein PDF-Renderer nur bei einem tatsächlichen PDF-Export angesprochen, bleibt er für JSON- oder Textausgaben unangetastet. Ähnlich lässt sich eine selten aufgerufene Administrationsfunktion von der alltäglichen Programmnutzung trennen. Entscheidend ist jeweils nicht die Größe einer Datei, sondern die Initialisierungsarbeit des Moduls mitsamt seinen weiteren Importen.

Die Verzögerung verändert allerdings den Zeitpunkt möglicher Nebenwirkungen. Code, den ein Modul bereits beim Import ausführt, läuft nun erst beim ersten Zugriff. Auch ein Importfehler tritt entsprechend später auf. Lazy Imports eignen sich daher am besten für klar abgegrenzte, bedarfsabhängige Komponenten – nicht als pauschaler Ersatz für sämtliche normalen Importe.

Die Syntax setzt bewusst enge Leitplanken

Explizite Lazy Imports sind nur auf Modulebene erlaubt. Das bewahrt eine eindeutige Bedeutung: Die Anweisung deklariert eine Modulabhängigkeit, deren Auflösung vertagt wird. Innerhalb von Funktionen ist die neue Form nicht vorgesehen. Dort bleibt ein normaler lokaler Import weiterhin die direkte Möglichkeit, das Laden an einen Funktionsaufruf zu binden.

Stern-Imports können ebenfalls nicht als lazy markiert werden. Bei from modul import * ist nicht im Voraus eindeutig, welche Namen später einen Import auslösen müssten. Auch future-Imports sind ausgeschlossen, weil sie die Übersetzung und Bedeutung des aktuellen Moduls beeinflussen und deshalb nicht bis zu einer späteren Nutzung warten können.

Für die Anwendung ergibt sich eine klare Auswahl: Ein normaler Import bleibt richtig, wenn das Modul unmittelbar benötigt wird oder seine Initialisierung absichtlich beim Start stattfinden soll. lazy import passt, wenn eine Abhängigkeit zwar regulär zum Modul gehört, aber nur ein optionaler Codepfad auf sie zugreift. Ein lokaler Import bleibt nützlich, wenn die Abhängigkeit ausdrücklich Teil einer einzelnen Funktion ist.

frozendict macht unveränderliche Zuordnungen zum Standardfall

Python 3.15 ergänzt mit frozendict eine eingebaute unveränderliche Abbildung. Sie lässt sich wie eine Zuordnung lesen, nach der Erzeugung aber nicht mehr verändern. Sind sämtliche enthaltenen Schlüssel und Werte hashbar, ist auch das frozendict selbst hashbar.

Das eröffnet zwei konkrete Anwendungen. Erstens können Konfigurationen oder Metadaten als unveränderlicher Wert weitergereicht werden, ohne dass eine später aufgerufene Funktion versehentlich einen Eintrag austauscht. Zweitens kann eine solche Abbildung bei geeigneten Inhalten selbst als Schlüssel in einem Cache oder als Element einer Menge dienen:

Technisches Detail: from functools import lru_cache; @lru_cache; def build_query(options: frozendict):; return compile_query(options).

Ein gewöhnliches dict kann wegen seiner Veränderlichkeit nicht direkt als Schlüssel für diesen Cache dienen. Bislang waren dafür meist Tupel aus Schlüssel-Wert-Paaren, projektspezifische Wrapper oder andere Ersatzdarstellungen nötig. frozendict drückt die Semantik unmittelbar aus: Diese Daten sind eine Zuordnung, und ihre Belegung soll stabil bleiben.

Unveränderlich bedeutet dabei nicht automatisch, dass jedes enthaltene Objekt unveränderlich ist. Für die Hashbarkeit müssen auch die Inhalte die entsprechenden Voraussetzungen erfüllen. Ein enthaltenes veränderliches Objekt lässt sich nicht allein dadurch in einen stabilen Hashwert verwandeln, dass die äußere Abbildung eingefroren ist.

Ein Sentinel unterscheidet „nicht angegeben“ von None

Der neue eingebaute Typ sentinel dient als eindeutiger Markierungswert. Solche Werte werden gebraucht, wenn None eine legitime Eingabe ist und deshalb nicht zugleich „Argument fehlt“ bedeuten kann. Das betrifft etwa Konfigurationsfunktionen, partielle Aktualisierungen und Caches.

Angenommen, eine Funktion aktualisiert ein Profil. nickname=None könnte ausdrücklich bedeuten, den Spitznamen zu löschen. Ohne separaten Marker lässt sich dieser Auftrag nicht von einem Aufruf unterscheiden, bei dem nickname gar nicht übergeben wurde. Ein Sentinel trennt die beiden Zustände: Der Marker steht für „keine Angabe“, None bleibt ein regulärer Wert.

Python-Projekte haben dafür lange eigene Objekte erzeugt. Ein eingebauter Typ gibt diesem verbreiteten Muster nun eine gemeinsame, lesbare Form. Besonders hilfreich ist das an Schnittstellen mit optionalen Parametern: Statt einen zulässigen Datenwert mit einer Steuerbedeutung zu überladen, erhält jeder Zustand eine eindeutige Repräsentation.

UTF-8 wird zur normalen Ausgangslage

Mit Python 3.15 ist der UTF-8-Modus standardmäßig aktiviert. UTF-8 wird dadurch für Dateien, Standardeingabe, Standardausgabe und Pipes zur regulären Kodierung. Programme verhalten sich bei Textzugriffen damit über Betriebssysteme und lokale Spracheinstellungen hinweg einheitlicher.

Die Änderung beseitigt eine häufige Quelle schwer reproduzierbarer Fehler: Ein Programm konnte auf dem Rechner des Autors funktionieren und auf einem System mit anderer lokaler Kodierung an denselben Textdaten scheitern. Wer Textdateien kontrolliert austauscht, sollte die Kodierung weiterhin ausdrücklich angeben; der neue Standard ersetzt keine saubere Formatdefinition. Er sorgt aber dafür, dass ein weggelassenes encoding nicht mehr so leicht zu plattformabhängigem Verhalten führt.

Relevant ist die Umstellung für Programme, die absichtlich auf eine lokale Nicht-UTF-8-Kodierung vertraut haben. Dort muss die gewünschte Kodierung nun explizit angegeben oder der UTF-8-Modus deaktiviert werden. PEP 686 nennt dafür PYTHONUTF8=0 beziehungsweise die Startoption -X utf8=0. Diese Schalter sind vor allem eine Kompatibilitätshilfe; für neue, portable Textformate ist eine explizite UTF-8-Angabe die klarere Wahl.

7x86-64-Linux,Untergrenze8x86-64-Linux,Obergrenze11AArch64-macOS,Untergrenze12AArch64-macOS,Obergrenze
Mittlere JIT-Beschleunigung in den offiziellen Benchmarks

Das experimentelle JIT wird schneller, aber nicht gleichmäßig

Das Just-in-time-Compilerprojekt von CPython bleibt in Python 3.15 ausdrücklich experimentell. In den offiziellen Benchmarks erreicht es im geometrischen Mittel 7 bis 8 Prozent mehr Leistung unter x86-64-Linux und 11 bis 12 Prozent unter AArch64-macOS. Diese Mittelwerte beschreiben die gesamte Benchmark-Sammlung, nicht den garantierten Gewinn eines einzelnen Programms.

Die Streuung ist erheblich: Laut Versionsdokumentation reichen einzelne Ergebnisse von ungefähr 15 Prozent Verlangsamung bis zu mehr als 100 Prozent Beschleunigung. Rechenmuster, Plattform und konkreter Code entscheiden somit stärker als der Durchschnittswert. Für eine lang laufende, rechenintensive Anwendung kann das JIT interessant sein; für ein kurzes CLI-Programm kann der schnellere Start durch Lazy Imports praktisch wichtiger sein.

Eine JIT-fähige CPython-Version wird über --enable-experimental-jit gebaut. Die Build-Konfiguration erlaubt, das JIT zu aktivieren, es einzubauen und standardmäßig ausgeschaltet zu lassen oder einen speziellen JIT-Interpreter zu konfigurieren. Zur Laufzeit steuert PYTHON_JIT die Aktivierung. Für den Bau wird Python 3.11 oder neuer vorausgesetzt. Die experimentelle Kennzeichnung ist entscheidend: Das JIT ist eine gezielt wählbare Laufzeitoption, noch kein allgemeines Leistungsversprechen für jede Installation.

Drei Neuerungen, drei unterschiedliche Entscheidungen

Lazy Imports, frozendict und das JIT lösen keine Varianten desselben Problems. Lazy Imports reduzieren unnötige Arbeit vor der tatsächlich gewählten Funktion. frozendict schützt die Bedeutung stabiler Zuordnungen im laufenden Programm. Das JIT versucht, häufig ausgeführten Code während der Laufzeit schneller abzuarbeiten.

Für ein datenreiches Kommandozeilenwerkzeug bietet sich daher eine Kombination an: Optionale Renderer werden lazy importiert, unveränderliche Optionen als frozendict an gecachte Funktionen übergeben, und das JIT wird nur für länger laufende Rechenpfade anhand des eigenen Programms bewertet. Der neue UTF-8-Standard vereinfacht parallel den Austausch von Textdaten, während Sentinels optionale Eingaben eindeutig halten.

Die stärkste Veränderung von Python 3.15 ist damit nicht eine einzelne Rekordzahl. Die Version gibt Entwicklern präzisere Sprachmittel für drei alltägliche Fragen: Wann muss eine Abhängigkeit wirklich geladen werden, welche Daten dürfen sich nicht mehr verändern und wie lässt sich „nicht angegeben“ sauber von einem gültigen Wert unterscheiden. Genau diese Präzision kann Startzeit, Schnittstellen und Wartbarkeit gleichzeitig verbessern.

Verwandt
Zur Bibliothek