Запуск Python в браузере с помощью WebAssembly
Сообщество Python уже давно обсуждает наилучший способ сделать Python первоклассным инструментом в современном веб-браузере. Самая большая проблема заключается в том, что веб-браузеры на самом деле поддерживают только один язык программирования: JavaScript. Однако по мере развития веб-технологий мы внедряем в Сеть все больше и больше приложений, таких как игры, научная визуализация и программное обеспечение для редактирования аудио и видео. Это означает, что мы привнесли в Веб сложные вычисления - то, для чего JavaScript не был разработан. Все эти проблемы вызвали необходимость в низкоуровневом веб-языке, который мог бы обеспечить быстрое, портативное, компактное и безопасное выполнение. В результате крупные производители браузеров поработали над этой идеей и представили миру WebAssembly еще в 2017 году.
В этом руководстве мы рассмотрим, как WebAssembly может помочь вам запустить код на Python в браузере.
Чтобы было понятно, JavaScript сам по себе является мощным языком программирования. Просто он не предназначен для определенных задач. Подробнее об этом читайте в статье От ASM.JS до WebAssembly Брендана Эйха, создателя JavaScript.
Содержимое
Что мы создаем
Допустим, вы хотите преподавать курс Python. Чтобы сделать ваш курс более интересным и увлекательным, после каждого урока вы хотите включать в него упражнения для своих учеников, чтобы они могли практиковать то, чему научились.
Проблема здесь в том, что студентам необходимо подготовить среду разработки, установив определенную версию Python, создав и активировав виртуальную среду и установив все необходимые пакеты. Это может отнять много времени и усилий. Кроме того, трудно дать точные инструкции по этому поводу, поскольку все машины разные.
Хотя вы могли бы создать серверную часть для запуска отправленного кода в контейнере Docker или, возможно, в функции AWS Lambda, вы решили упростить стек и добавить в содержание курса редактор Python, который может запускать код на Python на стороне клиента, в веб-браузер и показывайте результат пользователям. Это именно то, что вы будете создавать в этом руководстве:
Ознакомьтесь с демонстрацией в реальном времени здесь. Вы также можете ознакомиться с версией React по адресу wasmeditor.com.
WebAssembly
Основываясь на определении из документации Mozilla Developer Network (MDN), WebAssembly (WASM) - это:
Новый тип кода, который может быть запущен в современных веб-браузерах и обеспечивает новые функции и значительный прирост производительности. В первую очередь он не предназначен для написания от руки, скорее он предназначен для эффективной компиляции исходных текстов на таких языках, как C, C++, Rust и т.д.
Итак, давайте запустим код, написанный на разных языках (не только на JavaScript), в браузере со следующими преимуществами:
- Он быстрый, эффективный и переносимый.
- Он безопасен, поскольку код выполняется в безопасной среде выполнения sandbox.
- Его можно запустить на стороне клиента.
Итак, в нашем примере выше нам не нужно беспокоиться, если пользователи запускают код на нашем сервере, и нам не нужно беспокоиться, если тысячи студентов попробуют практический код, поскольку выполнение кода происходит на стороне клиента, в веб-браузере.
WebAssembly не был разработан для уничтожения JavaScript. Он дополняет JavaScript. Его можно использовать, когда JavaScript не подходит, например, для игр, распознавания изображений и редактирования изображений / видео, и это лишь некоторые из них.
Смотрите Примеры использования из WebAssembly.org для получения дополнительной информации о том, когда вы можете захотеть использовать WebAssembly.
Pyodide
В этом руководстве используется библиотека Pyodide для запуска кода на Python, который компилирует интерпретатор CPython в WebAssembly и запускает двоичный файл в среде JavaScript браузера. Он поставляется с рядом предустановленных пакетов Python . Вы также можете использовать Micropip, чтобы использовать еще больше пакетов, которые не поставляются по умолчанию.
Привет, мир
Создайте новый HTML-файл со следующим кодом:
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
<script>
async function main() {
let pyodide = await loadPyodide({
indexURL : "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/"
});
console.log(pyodide.runPython("print('Hello, world from the browser!')"));
};
main();
</script>
</head>
Откройте файл в вашем браузере. Затем в консоли инструментов разработчика вашего браузера вам следует выполнить что-то вроде:
Loading distutils
Loading distutils from https://cdn.jsdelivr.net/pyodide/v0.20.0/full/distutils.js
Loaded distutils
Python initialization complete
Hello, world from the browser!
Как вы можете видеть, последняя строка является результатом выполнения кода на Python в браузере.
Давайте кратко рассмотрим приведенный выше код:
- Во-первых, вы можете загрузить и установить Pyodide, используя либо CDN, либо непосредственно из релизов GitHub.
- loadPyodide загружает и инициализирует модуль Pyodide wasm.
- pyodide.RunPython принимает код Python в виде строки и возвращает результат выполнения кода.
Преимущества Pyodide
В предыдущем примере вы видели, как просто установить Pyodide и начать им пользоваться. Вам просто нужно импортировать pyodide.js из CDN и инициализировать его с помощью loadPyodide
. После этого вы можете использовать pyodide.runPython("Your Python Code Here")
для запуска вашего кода на Python в браузере.
При первой загрузке Pyodide размер загружаемого файла будет большим, поскольку вы загружаете полный интерпретатор CPython, но ваш браузер кеширует его, и вам не нужно загружать его снова.
Существует также большое активное сообщество людей, работающих над Pyodide:
Ограничения по применению пиодида
Чтобы загрузить Pyodide в первый раз, потребуется четыре или пять секунд (в зависимости от вашего подключения), так как вам нужно загрузить ~10 МБ. Кроме того, код на Pyodide выполняется примерно в 3-5 раз медленнее, чем на родном Python.
Другие опции
В общем, если вы хотите запустить Python в браузере, у вас есть два доступных подхода:
- Используйте транспилятор для преобразования Python в JavaScript. Brython, Transcrypt и Skulpt все используют этот подход.
- Преобразуйте среду выполнения Python для использования в браузере. Pyodide и PyPy.js используют этот подход.
Одно из основных различий между первым и вторым вариантами заключается в том, что упомянутые библиотеки в первом варианте не поддерживают пакеты Python. Тем не менее, их размер загружаемого файла намного меньше, чем у библиотек во втором варианте, и, следовательно, они быстрее.
В этом руководстве мы использовали Pyodide, потому что у него более простой синтаксис и он поддерживает пакеты Python. Если вас интересуют другие варианты, не стесняйтесь ознакомиться с их документацией.
Редактор кода на Python
В этом разделе мы создадим простой редактор Python, который может запускать код в браузере с помощью:
Создайте новый проект:
$ mkdir python_editor_wasm
$ cd python_editor_wasm
Создайте и активируйте виртуальную среду:
$ python3.10 -m venv env
$ source env/bin/activate
(env)$
Установите Flask:
(env)$ pip install Flask
В корне проекта создайте файл с именем app.py и добавьте следующий код:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Создайте папку "шаблоны" в корне нашего проекта и добавьте в нее index.html файл.
templates/index.html:
<!doctype html>
<html class="h-full bg-slate-900">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- install tailwindcss from cdn, don't do this for production application -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- install pyodide version 0.20.0 -->
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
<!-- import codemirror stylings -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/codemirror.min.css" />
<!-- install codemirror.js version /5.63.3 from cdn -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.js"
integrity="sha512-XMlgZzPyVXf1I/wbGnofk1Hfdx+zAWyZjh6c21yGo/k1zNC4Ve6xcQnTDTCHrjFGsOrVicJsBURLYktVEu/8vQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- install codemirror python language support -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/mode/python/python.min.js"
integrity="sha512-/mavDpedrvPG/0Grj2Ughxte/fsm42ZmZWWpHz1jCbzd5ECv8CB7PomGtw0NAnhHmE/lkDFkRMupjoohbKNA1Q=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- import codemirror dracula theme styles from cdn -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/theme/dracula.css"/>
<style>
/* set codemirror ide height to 100% of the textarea */
.CodeMirror {
height: 100%;
}
</style>
</head>
<body class="h-full overflow-hidden max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-8">
<p class="text-slate-200 text-3xl my-4 font-extrabold mx-2 pt-8">Run Python in your browser</p>
<div class="h-3/4 flex flex-row">
<div class="grid w-2/3 border-dashed border-2 border-slate-500 mx-2">
<!-- our code editor, where codemirror renders it's editor -->
<textarea id="code" name="code" class="h-full"></textarea>
</div>
<div class="grid w-1/3 border-dashed border-2 border-slate-500 mx-2">
<!-- output section where we show the stdout of the python code execution -->
<textarea readonly class="p-8 text-slate-200 bg-slate-900" id="output" name="output"></textarea>
</div>
</div>
<!-- run button to pass the code to pyodide.runPython() -->
<button onclick="evaluatePython()" type="button" class="mx-2 my-4 h-12 px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm bg-green-700 hover:bg-green-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-700 text-slate-300">Run</button>
<!-- clean the output section -->
<button onclick="clearHistory()" type="button" class="mx-2 my-4 h-12 px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm bg-red-700 hover:bg-red-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-700 text-slate-300">Clear History</button>
<script src="/static/js/main.js"></script>
</body>
</html>
В заголовке файла index.html мы импортировали Tailwind CSS для стилизации, Pyodide.js версию 0.20.0
, а также CodeMirror и его зависимости.
Пользовательский интерфейс состоит из трех важных компонентов:
- Редактор: Где пользователи могут писать код на Python. Это
textarea
HTML-элемент сid
изcode
. Когда мы инициализировалиcodemirror
, мы дали ему понять, что хотим использовать этот элемент в качестве редактора кода. - Вывод: Где будут отображаться выходные данные кода. Это
textarea
элемент сid
изoutput
. Когда Pyodide выполнит код на Python, он выведет результат в этот элемент. Мы также вывели сообщение об ошибке в этом элементе. - Кнопка запуска: Когда пользователи нажимают на эту кнопку, мы получаем значение элемента editor и передаем его в виде строки в
pyodide.runPython
. Когдаpyodide.runPython
возвращает результат, мы отображаем его в элементе output.
Теперь в корне проекта создайте папки "static/js". Затем в папке "js" создайте новый файл с именем main.js.
static/js/main.js:
// find the output element
const output = document.getElementById("output");
// initialize codemirror and pass configuration to support Python and the dracula theme
const editor = CodeMirror.fromTextArea(
document.getElementById("code"), {
mode: {
name: "python",
version: 3,
singleLineStringErrors: false,
},
theme: "dracula",
lineNumbers: true,
indentUnit: 4,
matchBrackets: true,
}
);
// set the initial value of the editor
editor.setValue("print('Hello world')");
output.value = "Initializing...\n";
// add pyodide returned value to the output
function addToOutput(stdout) {
output.value += ">>> " + "\n" + stdout + "\n";
}
// clean the output section
function clearHistory() {
output.value = "";
}
// init pyodide and show sys.version when it's loaded successfully
async function main() {
let pyodide = await loadPyodide({
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/",
});
output.value = pyodide.runPython(`
import sys
sys.version
`);
output.value += "\n" + "Python Ready !" + "\n";
return pyodide;
}
// run the main function
let pyodideReadyPromise = main();
// pass the editor value to the pyodide.runPython function and show the result in the output section
async function evaluatePython() {
let pyodide = await pyodideReadyPromise;
try {
pyodide.runPython(`
import io
sys.stdout = io.StringIO()
`);
let result = pyodide.runPython(editor.getValue());
let stdout = pyodide.runPython("sys.stdout.getvalue()");
addToOutput(stdout);
} catch (err) {
addToOutput(err);
}
}
Здесь мы:
- Инициализированный CodeMirror с поддержкой Python и темы Dracula.
- Инициализирован Pyodide.
- Добавлена функция под названием
evaluatePython
, которая запускается, когда пользователь нажимает на кнопкуRun
. Он передает значение элементаcode
вpyodide.runPython
и отображает результаты в элементеoutput
с помощьюaddToOutput
. - Добавлена функция под названием
clearHistory
, которая очищает элементoutput
, когда пользователь нажимает на кнопкуClear History
.
Чтобы запустить сервер разработки Flask локально, выполните:
(env)$ flask run
Теперь сервер должен работать на порту 5000. Перейдите к http://127.0.0.1:5000 в вашем браузере, чтобы протестировать редактор кода.
Заключение
В этом руководстве мы едва коснулись верхушки айсберга как с помощью Pyodide, так и с помощью WebAssembly. Мы видели, как мы можем использовать WebAssembly для запуска кода на Python в браузере, но WebAssembly, в целом, охватывает более широкие варианты использования.
Наши платформы развертывания разнообразнее, чем когда-либо, и мы просто не можем позволить себе тратить время и деньги на постоянное переписывание программного обеспечения для нескольких платформ. WebAssembly может повлиять на мир клиентской веб-разработки, серверной разработки, игр, образования, облачных вычислений, мобильных платформ, Интернета вещей, бессерверной работы и многого другого.
Целью WebAssembly является создание быстрого, безопасного, портативного и компактного программного обеспечения.
Вы можете найти репозиторий кода здесь.
Back to Top