Подробные статьи о технологиях, определяющих будущее.

Pyodide: настоящий Python в браузере через WebAssembly

Pyodide компилирует CPython в WebAssembly: Python с NumPy и pandas прямо в браузере. Как это работает и где применять.

Питон, свернувшийся в стеклянном террариуме в форме окна браузера, рядом с ящиками с научным оборудованием

Python в браузере мечтают запустить с начала 2010-х, и каждая попытка упиралась в одну и ту же проблему: Python слишком тесно связан с C-рантаймом CPython, чтобы его было легко перенести в веб-среду. Transcrypt, Brython и Skulpt пытались решить это, переписав Python на JavaScript. Для простых скриптов это работало, но ломалось, как только требовались NumPy, pandas или другие библиотеки с C-расширениями, ради которых Python вообще и нужен.

Pyodide идёт другим путём: компилирует настоящий интерпретатор CPython в WebAssembly. Это не переписанная реализация, а исходный код CPython, собранный через Emscripten для работы в браузере. Поэтому совместимость с языком полная, включая C-расширения. Можно написать import numpy, и это работает, потому что скомпилированный Wasm-бинарник содержит настоящий C-код NumPy.

Как устроен Pyodide изнутри

Сборка Pyodide берёт дерево исходников CPython и компилирует его через Emscripten, набор инструментов, который генерирует WebAssembly вместо машинного кода под конкретную платформу. На выходе получается бинарник .wasm с интерпретатором Python и JavaScript-обвязка, которая связывает его с браузером.

Самое сложное — C-расширения. В NumPy, например, больше 100 000 строк кода на C и Fortran. Pyodide заранее собирает набор популярных пакетов (NumPy, pandas, scikit-learn, matplotlib, scipy) в Wasm-модули, которые подгружаются по необходимости. Когда вы вызываете import numpy, Pyodide скачивает готовый Wasm-модуль NumPy, подключает его к работающему интерпретатору и инициализирует.

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

Мост между JavaScript и Python

Одна из самых сильных сторон Pyodide — бесшовное взаимодействие между Python и JavaScript. Объекты Python автоматически проксируются в JavaScript и обратно. Из Python можно вызывать JavaScript-функции, работать с DOM и передавать данные между языками без ручной сериализации.

// 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!'
`);

Система проксирования сама преобразует типы: словари Python становятся объектами JavaScript, списки — массивами, а числа — числами JavaScript. При передаче больших объёмов данных, например миллиона элементов NumPy-массива в библиотеку визуализации на JavaScript, Pyodide использует общие буферы памяти без копирования. Для производительности это критически важно.

Где Pyodide действительно полезен

Интерактивные вычислительные среды. JupyterLite, построенный на Pyodide, запускает Jupyter-блокноты целиком в браузере. Сервер не нужен: ядро Python работает локально в Wasm. Для образования это большой шаг вперёд: студенты запускают Python-блокноты без установки чего-либо, а преподавателю не нужно поднимать серверы.

Инструменты для исследования данных. Веб-приложения, где пользователь загружает данные и запускает анализ, могут обрабатывать их на стороне клиента через Pyodide. Данные не покидают браузер, это снимает вопросы приватности и избавляет от расходов на серверы. Загрузчик CSV, который выполняет трансформации pandas прямо в браузере, проще в развёртывании и приватнее, чем тот, что отправляет данные на бэкенд.

Документация с живыми примерами. В документации к Python-библиотекам можно вставлять интерактивные блоки кода, которые реально выполняются. Вместо статичного вывода читатель может изменить код и сразу увидеть результат. Для обучения это намного эффективнее обычных примеров.

Прототипирование и эксперименты. Дата-сайентисты, которые думают на Python, могут прототипировать трансформации данных и визуализации прямо в браузере, а затем поделиться результатом ссылкой. Никакой настройки окружения, никакого управления зависимостями и никаких проблем вида «у меня же работает».

Реальная производительность

Pyodide не быстрый. WebAssembly уступает нативному коду: для задач, требовательных к вычислениям, обычно в 1,5–3 раза медленнее. Поверх этого Pyodide запускает CPython, который и так в 100 раз медленнее C на чистом Python, скомпилированный в Wasm. Для чистых Python-циклов Pyodide примерно в 2–5 раз медленнее обычного CPython.

Но важно вот что: там, где Pyodide полезен, время обычно уходит на вызовы C-расширений, а не на чистый Python. Когда вы вызываете np.dot(a, b), вычисления идут в скомпилированном C-коде (теперь уже в Wasm). Накладные расходы Wasm на этот код составляют 1,5–3x, и для интерактивных сценариев это вполне приемлемо. Умножение матриц в NumPy, которое нативно занимает 10 мс, в Pyodide займёт 20 мс. Для пользователя, который нажимает «Run» в блокноте, эта разница незаметна.

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.

Гораздо серьёзнее стоимость запуска. Базовый рантайм Pyodide весит 10 МБ, NumPy добавляет ещё 7 МБ, pandas — ещё 10 МБ. Для веб-страницы, которой нужно быстро отрисоваться, задержка инициализации в 5 секунд — это красная черта. Поэтому Pyodide лучше всего подходит для приложений, где пользователь и так ждёт загрузки среды: блокноты, инструменты для данных, интерактивные туториалы, а не обычные веб-страницы.

Чего Pyodide не умеет

Не каждый пакет Python работает в Pyodide. Пакеты с системными зависимостями (драйверы баз данных, GUI-библиотеки, всё, что вызывает API конкретной ОС) не скомпилируются в Wasm. psycopg2 требует клиентскую библиотеку PostgreSQL, а opencv — системные графические библиотеки. В браузере этих зависимостей просто нет.

С сетью тоже есть ограничения. Модуль socket в браузере не работает: из Wasm нет доступа к сырым сокетам. HTTP-запросы идут через браузерный API fetch() через JavaScript-мост Pyodide, поэтому на них действуют ограничения CORS. Запустить Flask-сервер в Pyodide нельзя (негде слушать порт), и произвольные сетевые соединения тоже не открыть.

Многопоточность поддерживается частично через Web Workers, но GIL (Global Interpreter Lock) по-прежнему действует, а модель потоков отличается от нативного CPython. Для CPU-bound параллелизма лучше работает multiprocessing (каждый воркер получает собственный экземпляр Wasm), чем потоки.

Pyodide и транспиляция Python

Стоит сравнить подход Pyodide (компиляция CPython в Wasm) с альтернативой — транспиляцией Python в JavaScript. Проекты вроде Transcrypt и Brython переводят синтаксис Python в JavaScript, и полученный код работает прямо в JS-движке браузера.

Транспиляция быстрее во время выполнения: сгенерированный JavaScript работает на скорости JS, а не CPython поверх Wasm. У неё также нулевая стоимость запуска, ведь рантайм не нужно скачивать. Но за это приходится платить совместимостью: транспилированный Python не запускает C-расширения, имеет тонкие семантические отличия от CPython (особенно в числовых типах, работе со строками и пограничных случаях) и поддерживает только часть стандартной библиотеки.

Главное преимущество Pyodide в том, что внутри настоящий CPython. Если ваш код работает в CPython, он заработает и в Pyodide (за исключением системных зависимостей). Для рабочих процессов с data science, которые опираются на NumPy, pandas и scikit-learn, эта совместимость необходима. Для простых скриптов без C-расширений транспиляция может оказаться лучшим выбором — как и в любом сравнении Wasm и нативного JS, всё зависит от нагрузки.

Куда всё движется

Pyodide активно развивается. Последние версии уменьшили размер базового рантайма, ускорили запуск за счёт потоковой компиляции (браузер компилирует Wasm во время загрузки) и расширили набор предсобранных пакетов. Экосистема WebAssembly тоже взрослеет: WASI даёт стандартизированные системные интерфейсы, а component model обеспечит более удобное взаимодействие между Wasm-модулями.

Общая тенденция в том, что браузер становится универсальным рантаймом. Благодаря JavaScript, Wasm и проектам вроде Pyodide в браузере можно запускать код почти на любом языке. Это не заменяет серверные вычисления, а дополняет их: вычисления, которым не нужен сервер, переезжают на клиент. Для огромной экосистемы data science в Python запуск на стороне клиента открывает сценарии, которые раньше были попросту невозможны.