Fundierte Artikel über Technologien, die das Kommende formen.

Pyodide: Echtes Python im Browser dank WebAssembly

Pyodide kompiliert CPython zu WebAssembly, sodass Python mit NumPy und pandas direkt im Browser läuft. Wie es funktioniert und wann es sich lohnt.

Eine Python-Schlange, aufgerollt in einem Glasterrarium in Form eines Browserfensters, mit Wissenschaftskisten

Python im Browser ist seit den frühen 2010er-Jahren ein Traum, und jeder Versuch scheiterte am selben Problem: Python ist zu eng mit der C-Laufzeitumgebung von CPython verwoben, als dass es sich einfach für eine Web-Umgebung portieren ließe. Transcrypt, Brython und Skulpt versuchten, das Problem zu lösen, indem sie Python in JavaScript nachbauten. Das funktionierte bei einfachen Skripten, brach aber zusammen, sobald man NumPy, pandas oder eine der C-Erweiterungsbibliotheken brauchte, die Python erst richtig nützlich machen.

Pyodide geht einen anderen Weg: Es kompiliert den echten CPython-Interpreter nach WebAssembly. Keine Neuimplementierung, sondern der tatsächliche CPython-Quellcode, kompiliert mit Emscripten, damit er im Browser läuft. Das bedeutet volle Sprachkompatibilität, inklusive C-Erweiterungen. Du kannst import numpy ausführen und es funktioniert, weil das kompilierte Wasm-Binary den echten C-Code von NumPy enthält.

Wie Pyodide unter der Haube funktioniert

Der Build-Prozess von Pyodide nimmt den CPython-Quellbaum und kompiliert ihn mit Emscripten, einer Compiler-Toolchain, die statt nativem Maschinencode WebAssembly als Ziel hat. Das Ergebnis ist eine .wasm-Binärdatei, die den Python-Interpreter implementiert, dazu JavaScript-Glue-Code, der ihn mit der Browser-Umgebung verbindet.

Knifflig sind die C-Erweiterungen. NumPy zum Beispiel enthält über 100.000 Zeilen C- und Fortran-Code. Pyodide kompiliert vorab eine kuratierte Auswahl beliebter Pakete – NumPy, pandas, scikit-learn, matplotlib, SciPy – zu Wasm-Modulen, die bei Bedarf geladen werden. Wenn du import numpy aufrufst, lädt Pyodide das vorkompilierte NumPy-Wasm-Modul herunter, bindet es in den laufenden Interpreter ein und initialisiert es.

<!-- Minimal Pyodide example -->
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.0/full/pyodide.js"></script>
<script>
async function main() {
// Load the Python interpreter (~10MB download)
const pyodide = await loadPyodide();
// Run Python code directly
pyodide.runPython(`
import sys
print(f"Python {sys.version} running in the browser!")
`);
// Load packages on demand
await pyodide.loadPackage('numpy');
// Use NumPy — the real NumPy, compiled to Wasm
const result = pyodide.runPython(`
import numpy as np
arr = np.random.randn(1000)
f"Mean: {arr.mean():.4f}, Std: {arr.std():.4f}"
`);
console.log(result);  // "Mean: 0.0123, Std: 1.0045"
}
main();
</script>

Die Brücke zwischen JavaScript und Python

Eine der stärksten Funktionen von Pyodide ist die nahtlose Interoperabilität zwischen Python und JavaScript. Python-Objekte werden automatisch in JavaScript gespiegelt und umgekehrt. Du kannst aus Python JavaScript-Funktionen aufrufen, das DOM aus Python heraus manipulieren und Daten zwischen beiden Sprachen übergeben, ohne manuelle Serialisierung.

// JavaScript calling Python
const pyodide = await loadPyodide();
// Define a Python function
pyodide.runPython(`
def analyze(data):
import statistics
return {
'mean': statistics.mean(data),
'median': statistics.median(data),
'stdev': statistics.stdev(data)
}
`);
// Call it from JavaScript with JS data
const analyze = pyodide.globals.get('analyze');
const result = analyze([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
console.log(result.toJs());  // {mean: 5.5, median: 5.5, stdev: 3.03}
// Python accessing the DOM
pyodide.runPython(`
from js import document
element = document.getElementById('output')
element.textContent = 'Updated from Python!'
`);

Das Proxy-System übernimmt die Typkonvertierung automatisch: Python-Dicts werden zu JavaScript-Objekten, Python-Listen zu JavaScript-Arrays und Python-Zahlen zu JavaScript-Zahlen. Bei großen Datenmengen – etwa wenn ein NumPy-Array mit einer Million Elementen an eine JavaScript-Visualisierungsbibliothek übergeben wird – nutzt Pyodide gemeinsam genutzte Speicherpuffer, um Kopien zu vermeiden. Für die Performance ist das entscheidend.

Wo Pyodide sinnvoll ist

Interaktive Rechenumgebungen. JupyterLite, das auf Pyodide aufsetzt, führt Jupyter-Notebooks komplett im Browser aus. Kein Server nötig, der Python-Kernel läuft lokal in Wasm. Das verändert die Lehre grundlegend: Studierende können Python-Notebooks ohne jede Installation nutzen, und Dozenten müssen keine Server bereitstellen.

Tools zur Datenexploration. Webanwendungen, mit denen Nutzer Daten hochladen und analysieren können, lassen sich mit Pyodide komplett clientseitig betreiben. Keine Daten verlassen den Browser, das löst Datenschutzbedenken und spart Serverkosten. Ein CSV-Uploader, der pandas-Transformationen im Browser ausführt, ist einfacher zu deployen und privater als einer, der Daten an ein Backend schickt.

Dokumentation mit Live-Beispielen. Dokumentationen von Python-Bibliotheken können interaktive Codeblöcke enthalten, die tatsächlich ausgeführt werden. Statt statischer Ausgaben können Leser den Code ändern und sofort die Ergebnisse sehen. Das ist für das Lernen deutlich wirksamer als statische Beispiele.

Prototyping und Experimente. Data Scientists, die in Python denken, können Datentransformationen und Visualisierungen direkt im Browser prototypisieren und das Ergebnis dann als URL teilen. Kein Environment-Setup, kein Dependency-Management, keine Probleme nach dem Muster „funktioniert bei mir“.

Die Performance-Realität

Pyodide ist nicht schnell. WebAssembly hat gegenüber nativem Code einen Overhead – bei rechenintensiven Aufgaben typischerweise 1,5- bis 3-mal langsamer. Hinzu kommt, dass Pyodide CPython ausführt (das bei reinem Python-Code bereits etwa 100-mal langsamer als C ist), kompiliert nach Wasm. Bei reinen Python-Schleifen ist Pyodide grob 2- bis 5-mal langsamer als nativer CPython.

Der Punkt ist aber: Die Workloads, für die Pyodide nützlich ist, werden meist von C-Erweiterungsaufrufen dominiert, nicht von reinem Python. Wenn du np.dot(a, b) aufrufst, passiert die eigentliche Berechnung im kompilierten C-Code (der jetzt in Wasm kompiliert ist). Der Wasm-Overhead auf diesem C-Code liegt bei 1,5- bis 3-fach, was für interaktive Anwendungsfälle völlig in Ordnung ist. Eine NumPy-Matrixmultiplikation, die nativ 10 ms dauert, braucht in Pyodide 20 ms. Für jemanden, der in einem Notebook auf „Run“ klickt, ist das nicht spürbar.

Performance comparison (approximate):
Native CPython    Pyodide (Wasm)
Pure Python loop:     1x                3-5x slower
NumPy operations:     1x                1.5-3x slower
Pandas groupby:       1x                2-3x slower
Startup time:         50ms              2-5 seconds
Package loading:      instant (pip)     2-10s (download + init)
Startup is the real cost. Once loaded, interactive
performance is adequate for most use cases.

Das größere Problem sind die Startkosten. Der Kern-Runtime von Pyodide ist ein 10-MB-Download. NumPy kommt mit weiteren 7 MB dazu, pandas mit 10 MB. Für eine Webseite, die schnell rendern muss, ist eine Initialisierungszeit von fünf Sekunden ein Ausschlusskriterium. Deshalb funktioniert Pyodide am besten dort, wo Nutzer ohnehin auf das Laden einer Umgebung warten – Notebooks, Datentools, interaktive Tutorials – und weniger auf klassischen Webseiten.

Was Pyodide nicht kann

Nicht jedes Python-Paket läuft in Pyodide. Pakete mit Systemabhängigkeiten – Datenbanktreiber, GUI-Bibliotheken, alles, was betriebssystemspezifische APIs aufruft – lassen sich nicht nach Wasm kompilieren. psycopg2 benötigt eine PostgreSQL-Client-Bibliothek, opencv braucht systemweite Grafikbibliotheken. Diese Abhängigkeiten gibt es in der Browser-Umgebung schlicht nicht.

Auch Netzwerkfunktionen sind eingeschränkt. Das socket-Modul von Python funktioniert im Browser nicht, aus Wasm gibt es keinen direkten Socket-Zugriff. HTTP-Anfragen laufen über die fetch()-API des Browsers via JavaScript-Brücke von Pyodide und unterliegen damit den CORS-Regeln. Einen Flask-Server kannst du in Pyodide nicht betreiben (es gibt keinen Socket, auf dem er lauschen könnte), und beliebige Netzwerkverbindungen sind ebenfalls nicht möglich.

Threading wird über Web Workers teilweise unterstützt, aber Pythons GIL (Global Interpreter Lock) gilt weiterhin, und das Threading-Modell unterscheidet sich vom nativen CPython. Für CPU-gebundene Parallelität funktioniert Multiprocessing besser als Threads, da jeder Worker seine eigene Wasm-Instanz bekommt.

Pyodide im Vergleich zu transpiliertem Python

Es lohnt sich, Pyodides Ansatz (CPython nach Wasm kompilieren) mit der Alternative zu vergleichen, Python nach JavaScript zu transpilieren. Projekte wie Transcrypt und Brython wandeln Python-Syntax in JavaScript um, sodass der Code nativ in der JS-Engine des Browsers läuft.

Transpilation ist zur Laufzeit schneller – der erzeugte JavaScript-Code läuft mit nativer JS-Geschwindigkeit, nicht mit der von CPython in Wasm. Außerdem fallen keinerlei Startkosten an, denn es muss keine Laufzeitumgebung heruntergeladen werden. Dafür geht Kompatibilität verloren: Transpiliertes Python kann keine C-Erweiterungen ausführen, weicht in der Semantik subtil von CPython ab (besonders bei Zahlentypen, Stringverarbeitung und Randfällen) und unterstützt nur einen Teil der Standardbibliothek.

Der Vorteil von Pyodide: Es führt das echte CPython aus. Wenn dein Python-Code in CPython läuft, läuft er auch in Pyodide (abgesehen von systemseitigen Abhängigkeiten). Für Data-Science-Workflows, die auf NumPy, pandas und scikit-learn setzen, ist diese Kompatibilität unverzichtbar. Für einfache Skripte ohne C-Erweiterungen kann Transpilation die bessere Wahl sein – wie bei jedem Vergleich von Wasm und nativem JS hängt die richtige Wahl von der Arbeitslast ab.

Wohin die Reise geht

Pyodide wird aktiv weiterentwickelt und verbessert sich stetig. Neuere Versionen haben die Größe des Kern-Runtimes reduziert, die Startzeit durch Streaming-Kompilierung verbessert (der Browser kompiliert Wasm bereits während des Downloads) und die Auswahl vorkompilierter Pakete erweitert. Auch das WebAssembly-Ökosystem reift: WASI liefert standardisierte Systemschnittstellen, und das Component Model wird eine bessere Interoperabilität zwischen Wasm-Modulen ermöglichen.

Der übergreifende Trend ist, dass der Browser zu einer universellen Laufzeitumgebung wird. Dank JavaScript, Wasm und Projekten wie Pyodide lässt sich Code aus nahezu jeder Sprache direkt im Browser ausführen. Das ersetzt serverseitiges Rechnen nicht, sondern ergänzt es, indem Berechnungen, die keinen Server brauchen, auf den Client verlagert werden. Für Pythons riesiges Data-Science-Ökosystem eröffnet clientseitige Ausführung im Browser Anwendungsfälle, die vorher schlicht nicht möglich waren.