Запуск Python в браузере с помощью WebAssembly

Сообщество Python уже давно обсуждает наилучший способ сделать Python первоклассным инструментом в современном веб-браузере. Самая большая проблема заключается в том, что веб-браузеры на самом деле поддерживают только один язык программирования: JavaScript. Однако по мере развития веб-технологий мы внедряем в Сеть все больше и больше приложений, таких как игры, научная визуализация и программное обеспечение для редактирования аудио и видео. Это означает, что мы привнесли в Веб сложные вычисления - то, для чего JavaScript не был разработан. Все эти проблемы вызвали необходимость в низкоуровневом веб-языке, который мог бы обеспечить быстрое, портативное, компактное и безопасное выполнение. В результате крупные производители браузеров поработали над этой идеей и представили миру WebAssembly еще в 2017 году.

В этом руководстве мы рассмотрим, как WebAssembly может помочь вам запустить код на Python в браузере.

Чтобы было понятно, JavaScript сам по себе является мощным языком программирования. Просто он не предназначен для определенных задач. Подробнее об этом читайте в статье От ASM.JS до WebAssembly Брендана Эйха, создателя JavaScript.

Содержимое

Что мы создаем

Допустим, вы хотите преподавать курс Python. Чтобы сделать ваш курс более интересным и увлекательным, после каждого урока вы хотите включать в него упражнения для своих учеников, чтобы они могли практиковать то, чему научились.

Проблема здесь в том, что студентам необходимо подготовить среду разработки, установив определенную версию Python, создав и активировав виртуальную среду и установив все необходимые пакеты. Это может отнять много времени и усилий. Кроме того, трудно дать точные инструкции по этому поводу, поскольку все машины разные.

Хотя вы могли бы создать серверную часть для запуска отправленного кода в контейнере Docker или, возможно, в функции AWS Lambda, вы решили упростить стек и добавить в содержание курса редактор Python, который может запускать код на Python на стороне клиента, в веб-браузер и показывайте результат пользователям. Это именно то, что вы будете создавать в этом руководстве:

Final WASM App

Ознакомьтесь с демонстрацией в реальном времени здесь. Вы также можете ознакомиться с версией React по адресу wasmeditor.com.

WebAssembly

Основываясь на определении из документации Mozilla Developer Network (MDN), WebAssembly (WASM) - это:

Новый тип кода, который может быть запущен в современных веб-браузерах и обеспечивает новые функции и значительный прирост производительности. В первую очередь он не предназначен для написания от руки, скорее он предназначен для эффективной компиляции исходных текстов на таких языках, как C, C++, Rust и т.д.

Итак, давайте запустим код, написанный на разных языках (не только на JavaScript), в браузере со следующими преимуществами:

  1. Он быстрый, эффективный и переносимый.
  2. Он безопасен, поскольку код выполняется в безопасной среде выполнения sandbox.
  3. Его можно запустить на стороне клиента.

Итак, в нашем примере выше нам не нужно беспокоиться, если пользователи запускают код на нашем сервере, и нам не нужно беспокоиться, если тысячи студентов попробуют практический код, поскольку выполнение кода происходит на стороне клиента, в веб-браузере.

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 в браузере.

Давайте кратко рассмотрим приведенный выше код:

  1. Во-первых, вы можете загрузить и установить Pyodide, используя либо CDN, либо непосредственно из релизов GitHub.
  2. loadPyodide загружает и инициализирует модуль Pyodide wasm.
  3. pyodide.RunPython принимает код Python в виде строки и возвращает результат выполнения кода.

Преимущества Pyodide

В предыдущем примере вы видели, как просто установить Pyodide и начать им пользоваться. Вам просто нужно импортировать pyodide.js из CDN и инициализировать его с помощью loadPyodide. После этого вы можете использовать pyodide.runPython("Your Python Code Here") для запуска вашего кода на Python в браузере.

При первой загрузке Pyodide размер загружаемого файла будет большим, поскольку вы загружаете полный интерпретатор CPython, но ваш браузер кеширует его, и вам не нужно загружать его снова.

Существует также большое активное сообщество людей, работающих над Pyodide:

  1. Дорожная карта Pyodide
  2. Открытые проблемы на GitHub
  3. Сообщество гиттеров

Ограничения по применению пиодида

Чтобы загрузить Pyodide в первый раз, потребуется четыре или пять секунд (в зависимости от вашего подключения), так как вам нужно загрузить ~10 МБ. Кроме того, код на Pyodide выполняется примерно в 3-5 раз медленнее, чем на родном Python.

Другие опции

В общем, если вы хотите запустить Python в браузере, у вас есть два доступных подхода:

  1. Используйте транспилятор для преобразования Python в JavaScript. Brython, Transcrypt и Skulpt все используют этот подход.
  2. Преобразуйте среду выполнения Python для использования в браузере. Pyodide и PyPy.js используют этот подход.

Одно из основных различий между первым и вторым вариантами заключается в том, что упомянутые библиотеки в первом варианте не поддерживают пакеты Python. Тем не менее, их размер загружаемого файла намного меньше, чем у библиотек во втором варианте, и, следовательно, они быстрее.

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

Редактор кода на Python

В этом разделе мы создадим простой редактор Python, который может запускать код в браузере с помощью:

  1. Пиодид
  2. Кодовое зеркало
  3. Фляжка

Создайте новый проект:

$ 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 и его зависимости.

Пользовательский интерфейс состоит из трех важных компонентов:

  1. Редактор: Где пользователи могут писать код на Python. Это textarea HTML-элемент с id из code. Когда мы инициализировали codemirror, мы дали ему понять, что хотим использовать этот элемент в качестве редактора кода.
  2. Вывод: Где будут отображаться выходные данные кода. Это textarea элемент с id из output. Когда Pyodide выполнит код на Python, он выведет результат в этот элемент. Мы также вывели сообщение об ошибке в этом элементе.
  3. Кнопка запуска: Когда пользователи нажимают на эту кнопку, мы получаем значение элемента 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);
  }
}

Здесь мы:

  1. Инициализированный CodeMirror с поддержкой Python и темы Dracula.
  2. Инициализирован Pyodide.
  3. Добавлена функция под названием evaluatePython, которая запускается, когда пользователь нажимает на кнопку Run. Он передает значение элемента code в pyodide.runPython и отображает результаты в элементе output с помощью addToOutput.
  4. Добавлена функция под названием 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