Pyodide : Python réel dans le navigateur via WebAssembly
Pyodide compile CPython en WebAssembly : Python avec NumPy et pandas dans le navigateur. Fonctionnement et cas d'usage pertinents.

Faire tourner Python dans le navigateur est un rêve depuis le début des années 2010, et chaque tentative s'est heurtée au même problème : Python est trop profondément lié au runtime C de CPython pour être porté facilement vers le web. Transcrypt, Brython et Skulpt ont tous tenté de résoudre ça en réimplémentant Python en JavaScript. Ça fonctionnait pour des scripts simples, mais ça craquait dès qu'on avait besoin de NumPy, pandas ou de n'importe laquelle des bibliothèques à extensions C qui rendent Python vraiment utile.
Pyodide prend une autre approche : compiler le vrai interpréteur CPython en WebAssembly. Pas une réimplémentation, mais le code source réel de CPython, compilé avec Emscripten pour tourner dans le navigateur. Cela garantit une compatibilité totale avec le langage, extensions C comprises. Vous pouvez faire import numpy et ça marche, parce que le binaire Wasm compilé contient le vrai code C de NumPy.
Comment fonctionne Pyodide sous le capot
Le processus de build de Pyodide prend l'arbre source de CPython et le compile avec Emscripten, une chaîne de compilation qui cible WebAssembly plutôt que le code machine natif. Le résultat est un binaire .wasm qui implémente l'interpréteur Python, accompagné de code JavaScript qui le relie à l'environnement du navigateur.
Le point délicat, ce sont les extensions C. NumPy, par exemple, contient plus de 100 000 lignes de code C et Fortran. Pyodide pré-compile un ensemble sélectionné de paquets populaires — NumPy, pandas, scikit-learn, matplotlib, scipy — en modules Wasm chargeables à la demande. Quand vous appelez import numpy, Pyodide télécharge le module Wasm précompilé de NumPy, le lie à l'interpréteur en cours d'exécution et l'initialise.
<!-- 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>
Le pont JavaScript-Python
Une des grandes forces de Pyodide, c'est l'interopérabilité transparente entre Python et JavaScript. Les objets Python sont automatiquement encapsulés côté JavaScript, et inversement. Vous pouvez appeler des fonctions JavaScript depuis Python, manipuler le DOM depuis Python, et faire transiter des données entre les deux langages sans sérialisation manuelle.
// 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!'
`);
Le système de proxys gère la conversion de types automatiquement : les dict Python deviennent des objets JavaScript, les list Python deviennent des tableaux JavaScript, et les nombres Python deviennent des nombres JavaScript. Pour les gros transferts de données — par exemple passer un tableau NumPy d'un million d'éléments à une bibliothèque de visualisation JavaScript — Pyodide utilise des tampons en mémoire partagée pour éviter les copies, ce qui est crucial pour les performances.
Quand Pyodide a du sens
Environnements de calcul interactif. JupyterLite, construit sur Pyodide, fait tourner des notebooks Jupyter entièrement dans le navigateur. Pas besoin de serveur : le noyau Python s'exécute localement en Wasm. C'est transformateur pour l'enseignement : les étudiants peuvent lancer des notebooks Python sans rien installer, et l'enseignant n'a pas à provisionner de serveurs.
Outils d'exploration de données. Les applications web qui permettent aux utilisateurs d'importer des données et de les analyser peuvent utiliser Pyodide pour tout traiter côté client. Aucune donnée ne quitte le navigateur, ce qui règle les problèmes de confidentialité et supprime les coûts de serveur. Un importeur de CSV qui exécute des transformations pandas dans le navigateur est plus simple à déployer et plus respectueux de la vie privée qu'un outil qui envoie les données à un backend.
Documentation avec exemples interactifs. La documentation d'une bibliothèque Python peut inclure des blocs de code interactifs qui s'exécutent réellement. Au lieu d'afficher une sortie statique, le lecteur peut modifier le code et voir le résultat immédiatement. C'est bien plus efficace pour apprendre que des exemples figés.
Prototypage et expérimentation. Les data scientists qui pensent en Python peuvent prototyper transformations et visualisations directement dans le navigateur, puis partager le résultat sous forme d'URL. Pas de mise en place d'environnement, pas de gestion de dépendances, pas de problèmes du style « ça marche sur ma machine ».
La réalité des performances
Pyodide n'est pas rapide. WebAssembly a un surcoût par rapport au code natif, généralement 1,5 à 3 fois plus lent sur les traitements gourmands en calcul. En plus, Pyodide fait tourner CPython (déjà environ 100 fois plus lent que C pour du Python pur) compilé en Wasm. Pour les boucles Python pures, Pyodide est environ 2 à 5 fois plus lent que CPython natif.
Mais voilà : les charges de travail où Pyodide est utile sont typiquement dominées par des appels aux extensions C, pas par du Python pur. Quand vous appelez np.dot(a, b), le calcul réel se fait dans du code C compilé (désormais compilé en Wasm). Le surcoût Wasm sur ce code C est de 1,5 à 3 fois, ce qui, pour un usage interactif, reste tout à fait acceptable. Une multiplication de matrices NumPy qui prend 10 ms en natif en prend 20 dans Pyodide. C'est imperceptible pour un utilisateur qui clique sur « Exécuter » dans un notebook.
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.
Le coût de démarrage est le vrai problème. Charger le runtime de base de Pyodide représente un téléchargement de 10 Mo. Charger NumPy ajoute 7 Mo, pandas 10 Mo de plus. Pour une page web qui doit s'afficher rapidement, un délai d'initialisation de 5 secondes est rédhibitoire. C'est pourquoi Pyodide fonctionne au mieux dans les applications où l'utilisateur s'attend à patienter le temps que l'environnement se charge (notebooks, outils de données, tutoriels interactifs), plutôt que dans les pages web classiques.
Ce que Pyodide ne sait pas faire
Tous les paquets Python ne fonctionnent pas avec Pyodide. Ceux qui ont des dépendances système (pilotes de base de données, bibliothèques GUI, tout ce qui appelle des API propres à un OS) ne se compilent pas en Wasm. psycopg2 nécessite une bibliothèque cliente PostgreSQL, opencv a besoin des bibliothèques graphiques système. Ces dépendances n'existent pas dans l'environnement du navigateur.
Le réseau est aussi limité. Le module socket de Python ne fonctionne pas dans le navigateur : il n'y a pas d'accès aux sockets bruts depuis Wasm. Les requêtes HTTP passent par l'API fetch() du navigateur via le pont JavaScript de Pyodide, et sont donc soumises aux restrictions CORS. Vous ne pouvez pas faire tourner un serveur Flask dans Pyodide (il n'y a pas de socket où écouter), ni établir des connexions réseau arbitraires.
Le multithreading est partiellement pris en charge via les Web Workers, mais le GIL (Global Interpreter Lock) de Python s'applique toujours, et le modèle de threads diffère de celui de CPython natif. Le parallélisme lié au CPU fonctionne mieux avec multiprocessing (chaque worker dispose de sa propre instance Wasm) qu'avec les threads.
Pyodide face au Python transpilé
Il est intéressant de comparer l'approche de Pyodide (compiler CPython en Wasm) à l'alternative : transpiler Python en JavaScript. Des projets comme Transcrypt et Brython convertissent la syntaxe Python en JavaScript, produisant du code qui tourne nativement dans le moteur JS du navigateur.
La transpilation est plus rapide à l'exécution : le JavaScript généré tourne à la vitesse du JS natif, pas à celle de CPython en Wasm. Elle n'a aussi aucun coût de démarrage (il n'y a pas de runtime à télécharger). Mais elle sacrifie la compatibilité : le Python transpilé ne peut pas exécuter d'extensions C, présente des différences sémantiques subtiles avec CPython (surtout sur les types numériques, la gestion des chaînes et les cas limites), et ne couvre qu'une partie de la bibliothèque standard.
L'avantage de Pyodide, c'est qu'il exécute le vrai CPython. Si votre code Python fonctionne dans CPython, il fonctionne dans Pyodide (à l'exception des dépendances au niveau système). Pour les flux de travail en data science qui reposent sur NumPy, pandas et scikit-learn, cette compatibilité n'est pas négociable. Pour de simples scripts qui n'ont pas besoin d'extensions C, la transpilation peut être le meilleur choix : comme pour toute comparaison Wasm contre JavaScript natif, le bon outil dépend de la charge de travail.
Vers où cela se dirige
Pyodide est activement développé et s'améliore. Les versions récentes ont réduit la taille du runtime de base, amélioré le temps de démarrage grâce à la compilation en flux (le navigateur compile le Wasm pendant son téléchargement) et élargi l'ensemble des paquets précompilés. L'écosystème WebAssembly gagne aussi en maturité : WASI fournit des interfaces système standardisées, et le component model permettra une meilleure interopérabilité entre modules Wasm.
La tendance de fond, c'est que le navigateur devient un runtime universel. Entre JavaScript, Wasm et des projets comme Pyodide, on peut faire tourner directement dans le navigateur du code écrit dans presque n'importe quel langage. Cela ne remplace pas le calcul côté serveur : cela le complète en déplaçant vers le client les calculs qui n'ont pas besoin de serveur. Pour l'immense écosystème de la data science Python, l'exécution côté client ouvre des cas d'usage qui n'étaient tout simplement pas possibles auparavant.


