Python 3.15 erschienen: Faule Importe, UTF-8 und neue Datentypen
Translate
Stefan Wintermeyer
Python 3.15.0 ist verfügbar. Die neue Hauptversion der Programmiersprache erschien am 9. Oktober 2026 und bringt Änderungen an der Sprache, am Referenzinterpreter CPython und an der Standardbibliothek. Zu den auffälligsten Neuerungen gehören explizit verzögerte Imports (Lazy Imports) sowie die eingebauten Typen frozendict und sentinel. Hinzu kommen Fortschritte beim experimentellen Just-in-time-Compiler.
Laut Release Notes von Python 3.15.0 flossen 5643 Commits von 1012 Mitwirkenden in die Version ein. Die Freigabe verzögerte sich um eine Woche. Am 2. Oktober schob Release-Manager Hugo van Kemenade einen dritten Release Candidate nach, weil kurz vor Schluss bei den Lazy Imports noch Fehler aufgetaucht waren, die eine Freigabe blockierten.
Imports erst dann ausführen, wenn sie gebraucht werden
Ein Import lädt ein Modul und führt dabei dessen Code aus. Bei umfangreichen Abhängigkeiten kann das schon die Hilfeausgabe eines Kommandozeilenprogramms verzögern.
Python 3.15 ergänzt dafür das weiche Schlüsselwort lazy. Weich heißt: Es hat nur vor einer Importanweisung eine besondere Bedeutung und bleibt sonst als Name verwendbar. Ein so gekennzeichneter Import verschiebt das Laden bis zur ersten Verwendung des importierten Namens. Ein vereinfachtes Beispiel für ein Werkzeug mit optionaler JSON-Ausgabe:
# Python 3.15
lazy import json
import sys
def main():
if "--json" in sys.argv:
print(json.dumps({"status": "ok", "version": "3.15"}))
else:
print("Status: ok")
if __name__ == "__main__":
main()
Ohne die Option --json benötigt dieses Programm das Modul json nicht. Erst beim Zugriff auf json.dumps wird der verzögerte Import aufgelöst. In einer eigenen Messung brauchte ein Skript, das 13 Module der Standardbibliothek importiert und dann nur eine Zeile ausgibt, auf einem Apple M4 mit gewöhnlichen Imports im Median 44 Millisekunden. Mit lazy waren es 11, kaum mehr als bei einem Skript ganz ohne Imports.
lazy ist nur auf Modulebene erlaubt, also auf der obersten Ebene einer Datei, nicht in Funktionen, Klassen oder try-Blöcken, und nicht für import *. Wer bestehenden Code ohne Umschreiben testen will, kann mit der Umgebungsvariablen PYTHON_LAZY_IMPORTS=all nahezu alle Imports auf Modulebene verzögern.
Das Verfahren verändert allerdings den Zeitpunkt von Seiteneffekten und Fehlern. Ein fehlendes Modul fällt erst beim ersten Zugriff auf.
Unveränderliche Wörterbücher werden eingebaut
Ein Wörterbuch (dict) ordnet Schlüsseln Werte zu. Mit frozendict gibt es davon nun eine unveränderliche Variante. Einträge lassen sich nach der Erstellung weder hinzufügen noch entfernen oder ersetzen. Das schützt etwa gemeinsam verwendete Konfigurationen vor versehentlichen Änderungen:
Die Unveränderlichkeit betrifft nur die Zuordnung. Eine enthaltene Liste bleibt veränderlich. Sind auch alle Werte hashbar, also ihrerseits unveränderlich wie Zahlen und Zeichenketten, ist auch das frozendict hashbar. Es taugt dann selbst als Schlüssel eines Wörterbuchs oder als Element einer Menge. Beim Vergleich zweier frozendict spielt die Reihenfolge der Einträge keine Rolle.
Ein alter Wunsch, für den es heute mehr Gründe gibt
Neu ist die Idee nicht, denn ein erster Vorschlag für frozendict wurde 2012 abgelehnt, unter anderem weil der Bedarf als gering galt. Die Autoren des neuen Anlaufs, Victor Stinner und Donghee Na, verweisen darauf, dass Programme heute häufiger Daten zwischen gleichzeitig laufenden Teilen austauschen: 2014 kam asyncio hinzu, 2024 eine Interpretervariante ohne Global Interpreter Lock (GIL), in der Threads mehrere Prozessorkerne gleichzeitig nutzen, 2025 das Modul concurrent.interpreters für mehrere Interpreter in einem Prozess. Ein unveränderliches Wörterbuch lässt sich zwischen Threads ohne Sperren weiterreichen. Den Bedarf belegen für sie die Pakete frozendict und frozenmap im Paketverzeichnis PyPI.
Mit frozendict fällt im Alltag ein versehentlicher Schreibzugriff auf Konfigurationen, Nachschlagetabellen oder Standardwerte von Funktionen sofort als Fehler auf, statt unbemerkt andere Programmteile zu beeinflussen. Außerdem taugt ein frozendict als Argument für Caches wie functools.lru_cache (siehe Dokumentation zu lru_cache), die Ergebnisse aufwendiger Berechnungen aufheben und dafür hashbare Argumente verlangen. Ein dict führt dort zum Fehler.
Mehr Tempo als ein dict verspricht der neue Typ nicht, er teilt den größten Teil seines Codes mit ihm.
„Nicht angegeben“ bekommt einen eigenen Wert
Der neue eingebaute Typ sentinel löst ein häufiges API-Problem: Eine Funktion soll unterscheiden können, ob ein Argument fehlt oder ausdrücklich None, Pythons Wert für „nichts“, übergeben wurde. Dafür braucht es einen Markerwert (Sentinel), der mit keinem echten Wert verwechselt werden kann. Bisher dient dazu oft ein eigens erzeugtes object(), das in Funktionssignaturen und in der Hilfe nichtssagend als <object object at 0x...> erscheint. Python 3.15 bietet mit den Sentinel Values eine benannte Alternative:
# Python 3.15
MISSING = sentinel("MISSING")
def describe(value=MISSING):
if value is MISSING:
return "Kein Wert übergeben"
if value is None:
return "Explizit None übergeben"
return f"Wert: {value!r}"
print(describe()) # Kein Wert übergeben
print(describe(None)) # Explizit None übergeben
print(describe(0)) # Wert: 0
Der Vergleich erfolgt mit is, das prüft, ob zwei Namen dasselbe Objekt bezeichnen. Jeder Aufruf von sentinel() erzeugt ein neues Objekt, auch bei identischem Namen. Gemeinsam verwendete Marker müssen deshalb einmal definiert und wiederverwendet werden. Kopien liefern dasselbe Objekt zurück, und ein Sentinel, der unter seinem Namen aus einem Modul importierbar ist, behält seine Identität auch beim Serialisieren mit pickle.
UTF-8 wird zum Standard
Die im Alltag möglicherweise folgenreichste Änderung kommt ohne neue Syntax aus. Python 3.15 aktiviert den UTF-8-Modus standardmäßig. Textoperationen ohne ausdrücklich angegebene Kodierung verwenden dadurch UTF-8, statt sich nach den Sprach- und Regionseinstellungen des Systems (Locale) zu richten. Das betrifft insbesondere bestehende Programme unter Windows, die bisher implizit eine lokale Kodierung voraussetzen.
Vorhandene Dateien werden durch die Änderung nicht umkodiert. Eine ältere Datei mit Windows-1252-Daten muss weiterhin entsprechend eingelesen werden:
with open("altbestand.csv", encoding="cp1252") as file:
text = file.read()
Wer weiterhin die Kodierung des Systems braucht, gibt encoding="locale" an. Der UTF-8-Modus lässt sich außerdem mit PYTHONUTF8=0 oder -X utf8=0 abschalten. Der zugrunde liegende Änderungsvorschlag PEP 686 (Python Enhancement Proposal) empfiehlt, implizite Kodierungsannahmen vor der Umstellung aufzuspüren: Mit -X warn_default_encoding meldet der Interpreter jedes open() ohne encoding-Argument als EncodingWarning.
Beim Upgrade gehören reale Import- und Exportdaten in die Prüfung. Tests mit ausschließlich ASCII-Zeichen können bestehen, obwohl Umlaute in einer älteren Datei Probleme verursachen.
Comprehensions können Sammlungen direkt entpacken
Comprehensions sind Pythons Kurzschreibweise, um eine Liste, eine Menge oder ein Wörterbuch in einem Ausdruck aus einer Schleife zu erzeugen. Sie und die verwandten Generatorausdrücke können jetzt entpacken: * führt mehrere Listen oder Mengen zu einer zusammen, ** mehrere Wörterbücher:
# Python 3.15
batches = [[1, 2], [3], [4, 5]]
flat = [*batch for batch in batches]
print(flat) # [1, 2, 3, 4, 5]
groups = [{"python", "linux"}, {"python", "windows"}]
all_tags = {*group for group in groups}
print(sorted(all_tags)) # ['linux', 'python', 'windows']
Bisher war für die flache Liste eine Comprehension mit zwei Schleifen nötig:
flat = [item for batch in batches for item in batch]
Auch mehrere Konfigurationsebenen lassen sich kompakt kombinieren:
Bei mehrfach vorkommenden Schlüsseln gewinnt der später verarbeitete Wert. Python 3.14 lehnt beide Schreibweisen mit einem Syntaxfehler ab.
Profiling erhält einen gemeinsamen Namensraum
Profiler messen, wo ein Programm seine Laufzeit verbringt. Python ordnet seine eingebauten Werkzeuge dafür neu. Das Paket profiling bündelt unterschiedliche Verfahren unter einem gemeinsamen Namen. profiling.tracing übernimmt das deterministische Profiling aus cProfile; profiling.sampling enthält den neuen statistischen Profiler Tachyon (Dokumentation zu profiling.sampling). cProfile bleibt als Alias verfügbar. Das Modul profile ist veraltet und soll mit Python 3.17 entfernt werden.
Tracing verfolgt Funktionsaufrufe und liefert genaue Aufrufzahlen. Sampling nimmt Stichproben des Aufrufstapels, um zeitintensive Programmstellen zu finden. Tachyon tut das standardmäßig 1000-mal pro Sekunde und kann ein Skript starten oder sich an einen laufenden Python-Prozess hängen, ohne dass dieser neu gestartet oder verändert werden muss. Weil der Profiler dazu den Speicher des anderen Prozesses liest, braucht er meist Root- oder Administratorrechte:
Ein interaktiver Flame-Graph zeigt als HTML-Datei, welchen Anteil der Laufzeit jede Funktion samt der von ihr aufgerufenen Funktionen beansprucht:
sudo python3.15 -m profiling.sampling run \
--flamegraph -o profile.html analyse.py
Sehr kurze Funktionsaufrufe können zwischen zwei Stichproben liegen und unbemerkt bleiben. Wer exakte Aufrufzahlen benötigt, bleibt beim Tracing.
JIT legt zu, bleibt experimentell
Auch der experimentelle Just-in-time-Compiler (JIT) wurde überarbeitet. Er übersetzt häufig durchlaufenen Python-Code zur Laufzeit in Maschinencode. Wo ein Build ihn enthält, ist er abgeschaltet und lässt sich mit der Umgebungsvariablen PYTHON_JIT=1 aktivieren. Die offizielle Dokumentation nennt für die Benchmark-Suite pyperformance im geometrischen Mittel einen Leistungsvorteil von 7 bis 8 Prozent gegenüber dem vollständig optimierten Standardinterpreter auf x86-64-Linux. Auf AArch64-macOS, also Macs mit Apple-Prozessor, sind es 11 bis 12 Prozent gegenüber dem Tail-Calling-Interpreter. Der setzt seit Python 3.14 jede Bytecode-Anweisung als eigene kleine C-Funktion um.
Diese Zahlen sind keine pauschale Beschleunigung gegenüber Python 3.14. Sie vergleichen JIT- und Nicht-JIT-Konfigurationen unter bestimmten Bedingungen. Die Ergebnisse einzelner Benchmarks reichen laut Dokumentation von ungefähr 15 Prozent Verlangsamung bis zu mehr als 100 Prozent Beschleunigung, den Mikrobenchmark unpack_sequence ausgenommen.
Zu den Änderungen gehören ein neues Tracing-Frontend, das tatsächlich durchlaufene Pfade aufzeichnet, statt sie zu schätzen, und eine einfache Registerallokation, die Zwischenwerte in Prozessorregistern hält statt im Speicher.
Unabhängig vom JIT profitiert Windows: Die offiziellen 64-Bit-Binaries verwenden jetzt ebenfalls den Tail-Calling-Interpreter, den erst der Compiler aus Visual Studio 2026 ermöglicht. Das Python-Team misst damit auf pyperformance 15 bis 20 Prozent mehr Tempo als mit dem bisherigen Interpreter.
Neuerungen im Text-Adventure erkunden
Wer die Neuerungen spielerisch erkunden will: Der langjährige Python-Entwickler Barry Warsaw hat mit whatsnewt ein englischsprachiges Text-Adventure fürs Terminal geschrieben (siehe Abb. 1). Jedes der 18 Rätsel verlangt ein paar Zeilen Code mit einer Neuerung aus 3.15, die das Spiel tatsächlich ausführt. Mit dem Paketmanager uv genügt zum Start ein Befehl, der Spiel und passendes Python bei Bedarf herunterlädt:
uvx --python 3.15 whatsnewt
Die Neuerungen von Python 3.15 lassen sich auch spielerisch per Text-Adventure erkunden (Abb. 1).
Tkinter-Programme können unter macOS 27 hängen
Mac-Anwender sollten vor dem Wechsel auf macOS 27.0 einen Hinweis der Release-Seite beachten: Die mitgelieferte Entwicklungsumgebung IDLE und andere Programme mit Tkinter-Oberfläche können dort beim Öffnen von Dialogen aus dem Menü hängen bleiben. Ursache ist demnach eine Verhaltensänderung des Betriebssystems, kein Fehler in Python 3.15. Betroffen sind daher alle aktuellen Python-Versionen. Laut CPython-Issue 158053 ist ein Workaround bereits in die Tk-Entwicklungszweige eingeflossen; ein Python-Update mit korrigiertem Tk steht noch aus.
Aktualisieren mit Blick auf Daten und Abhängigkeiten
Als praktische Vorgehensweise bietet sich eine separate Testumgebung an, in der neben den regulären Tests typische Start-, Import- und Exportvorgänge laufen. Bei Lazy Imports gehören Initialisierungen und Registrierungen in die Prüfung, bei in C oder Rust geschriebenen Erweiterungsmodulen die Unterstützung für die gewählte Interpretervariante, etwa die ohne GIL. Die offiziellen macOS-Binaries installieren diese Free-Threading-Variante jetzt standardmäßig mit.
Neue Syntax muss außerdem zur erklärten Mindestversion eines Projekts passen. Wer weiterhin Python 3.14 oder ältere Versionen unterstützt, kann lazy-Imports und entpackende Comprehensions nicht in gemeinsam genutzte Quelldateien übernehmen.
Für die 3.15-Reihe sieht der Veröffentlichungsplan ungefähr zweimonatliche Fehlerkorrekturversionen über zwei Jahre vor. Anschließend sollen noch rund drei Jahre Sicherheitsaktualisierungen als Quellcode folgen, bis ungefähr Oktober 2031.