Надоело работать во фронтенд с JavaScript? Используйте Python для браузера!

“Как так?” —думаю, примерно такой будет реакция большинства на заголовок этой статьи. Что значит “Используйте Python для браузера?”. Ведь всем известно, что в браузерах может выполняться только JavaScript.

Что ж, ниже приведён скриншот исходного кода моего личного веб-сайта. Обратите внимание на его отличия:

Всё верно, это Python!

А теперь давайте поговорим о том, как он работает, и какие другие альтернативы браузерному JS существуют. 

Знакомимся с Brython

Brython — это реализация Python 3, написанная на JavaSсript и позволяющая писать код для браузеров на Python. По сути, это JS библиотека, преобразующая код Python в JS-эквивалент и вычисляющая его при выполнении.

И поскольку “написание кода для браузера на Python” звучит круто, я решил эту тему осветить.

Создаём “Змейку” при помощи Brython

Вот ссылка на мой сайт, где вы можете попробовать два варианта игры “Змейка”: написанный на Brython и JavaScript. А здесь вы найдёте ссылку на Github, где размещён весь стоящий за этим код.

Чтобы испытать Brython я решил создать классическую игру “Змейка”.

Поскольку я ни специалист по HTML Canvas, ни разработчик игр, то решил использовать в качестве стартовой точки эту её реализацию на JavaScript. Я уже создавал собственную игру “Змейка” ранее, но в данном случае эта она намного симпатичней и компактней. 

И да, парень создал её меньше, чем за 5 минут!

Итак, поверх реализации взятой “Змейки” я расширил функциональность набором очков и записью рекордного их количества, немного улучшил интерфейс, добавил кнопки паузы и инструкций. После этого я перенёс игру в Brython.

Честно говоря, я также изменил данный код так, чтобы он выполнялся в режиме strict JS, поскольку автор игры использовал такие элементы, как глобальные переменные, которые, на мой взгляд, не отражают преимущественный облик JavaScript (я нисколько его за это не критикую, так как он писал код на скорость), а я же хотел получить наглядное сравнение кода JS с кодом Brython.

В итоге JavaScript получился такой, и я не буду размещать здесь его фрагмент, потому что основная наша цель — Brython.

Несмотря на то, что некоторые части кода Brython были “прямым переводом” JS варианта, некоторые из его частей вроде функции набора очков, я создал непосредственно в Brython, а затем реализовал в JS, чтобы понять, как это всё работает. Конечный результат получился следующим:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Brython Snake</title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/brython.min.js">
    </script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <style> /* Removed to keep the snippet short. Find the full file here: */ </style>
</head>

<body onload="brython()">

    <h1 class="text-center">Snake built with <a href="https://brython.info">Python!</a></h1>
    <canvas id="game-board" width="400" height="400"></canvas>
    <br>
    <h3 id="score" class="text-center">Score: 0</h3>
    <br>
    <h6 id="high-score" class="text-center">High Score: 0</h6>
    <br>
    <div class="text-center">
        <button id="instructions-btn" class="btn btn-info">Instructions</button>
    </div>

    <script type="text/python">
        
        from browser import document, html, window
        from javascript import Math
        
        score = 0
        high_score = 0

        px = py = 10
        gs = tc = 20
        ax = ay = 15
        xv = yv = 0
        trail = []
        tail = 5

        pre_pause = [0,0]
        paused = False
   
        def game():
            global px, py, tc, gs, ax, ay, trail, tail, score
            px += xv
            py += yv
            if px < 0:
                px = tc-1
            if px > tc-1:
                px = 0
            if py < 0:
                py = tc-1
            if py > tc-1:
                py = 0
            ctx.fillStyle = "black"
            ctx.fillRect(0, 0, canvas.width, canvas.height)
            ctx.fillStyle = "lime"
            for i in range(len(trail)):
                ctx.fillRect(trail[i][0]*gs, trail[i][1]*gs, gs-2, gs-2)
                if trail[i][0] == px and trail[i][1] == py:
                    score = score if paused else 0 
                    tail = 5
            trail.insert(0, [px, py])
            while len(trail) > tail:
                trail.pop()
        
            if ax == px and ay == py:
                tail += 1
                ax = Math.floor(Math.random()*tc)
                ay = Math.floor(Math.random()*tc)
                score += 1
            update_score(score)
            ctx.fillStyle = "red"
            ctx.fillRect(ax*gs, ay*gs, gs-2, gs-2)
        
        def update_score(new_score):
            global high_score
            document["score"].innerHTML = "Score: " + str(new_score)
            if new_score > high_score:
                document["high-score"].innerHTML = "High Score: " + str(new_score)
                high_score = new_score

        def key_push(evt):
            global xv, yv, pre_pause, paused
            key = evt.keyCode
            if key == 37 and not paused:
                xv = -1
                yv = 0
            elif key == 38 and not paused:
                xv = 0
                yv = -1
            elif key == 39 and not paused:
                xv = 1
                yv = 0
            elif key == 40 and not paused:
                xv = 0
                yv = 1
            elif key == 32:
                temp = [xv, yv]
                xv = pre_pause[0]
                yv = pre_pause[1]
                pre_pause = [*temp]
                paused = not paused
            
        def show_instructions(evt):
            window.alert("Use the arrow keys to move and press spacebar to pause the game.")
        
        canvas = document["game-board"]
        ctx = canvas.getContext("2d")
        document.addEventListener("keydown", key_push)
        game_loop = window.setInterval(game, 1000/15)
        instructions_btn = document["instructions-btn"]
        instructions_btn.addEventListener("click", show_instructions)
    
    
</script>

</body>

</html>

Теперь, исходя из этого фрагмента, давайте проясним некоторые ключевые принципы Brython:

Включение brython.js

Brython не требует установки. Достаточно импортировать скрипт внутрь <head>:

<script type=”text/javascript” src=”https://cdn.jsdelivr.net/npm/[email protected]/brython.min.js">

Выполнение Brython

Для того, чтобы Brython преобразовал и вычислил код Python как обычный JS, нужно вызвать brython прямо при загрузке тела документа так:

<body onload=”brython()”>

Эта команда произведёт поиск тегов скриптов типа text/python и вычислит внутри них код.

Web API

По умолчанию JavaScript позволяет доступ к таким элементам, как объекты document и window, являющимся основополагающими в любом проекте JS. В связи с этим Brython также нужен способ включить их в процесс. 

Для решения этой задачи его создатели могли просто позволить разработчикам по умолчанию мгновенно обращаться к document и window в своём коде Python, но тогда бы упала производительность, а отладчики Python постоянно бы выдавали undefined variable.

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

from browser import document, html, window

Теперь уже не нужно выполнять для них pip install. В конце концов мы пишем это в нашем HTML. Просто включите инструкции импорта и Brython будет знать, что делать. 

Чтобы на деле увидеть, насколько хорошо это работает, я решил попробовать несколько разных методов из Web API (alert , setInterval, addEventListener и т.д.). Всё сработало, как я и ожидал. 

Нативные объекты и методы JavaScript 

Когда в создаваемой нами игре змея съедает яблоко, нам нужно в случайном месте поля сгенерировать новое.

Как бы то ни было, я не могу использовать модуль Python random*. Как же тогда мне сгенерировать случайное число, не создавая собственную библиотеку?

На деле выяснилось, что мы можем обращаться к большей части JS, чем я думал. Смотрите:

from javascript import Mathrandom_num = Math.floor(Math.random()*10)

Если использовать модуль javascript, то в случае наличия объекта, к которому можно обратиться через JS, я могу обратиться к нему через Brython.

Если я импортирую, к примеру, JS библиотеку (jQuery, Bootstrap) и захочу использовать её методы, то смогу сделать это при помощи from javascript import <library>. И, естественно, я также смогу использовать встроенные объекты JS тем же способом, что и Date или String

*Очевидно, что Brython предоставляет некоторые стандартные библиотеки Python, реализованные непосредственно в JavaScript, и если у такой библиотеки нет JS версии, то у вас по-прежнему должна быть возможность импортировать модуль. В этом случае Brython извлечёт чистую версию Python и выполнит её наряду со своим кодом. Тем не менее random в моём случае не сработал, но я могу понять почему. 

Отличительные принципы языка

В Python, если я хочу распаковать список, то могу выполнить list2 = [*list]. Кроме того, если я захочу определить значение переменной, основываясь на состоянии, то использую foo = 10 if condition else 20.

У этих инструкций есть эквивалент в JavaScript: оператор распространения ([...arr]) и троичный оператор ( let foo = condition ? 10 : 20 ).

Но поддерживает ли их Brython?

Я их испробовал, и сработали они прекрасно. Вы увидите, что распаковка списков и условное присваивание Python включены в мой итоговый исходный код. 

Отладка

Честно говоря, я думал, что отладка Brython будет ужасной. На деле же оказалось всё не так плохо. Я, конечно, создал маленький и весьма простой проект, но ошибки, выброшенные Brython, были точны и достаточно описательны. По меньшей мере это касается синтаксиса. Импортирование же модулей Python представляет собой совсем другую историю. 

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

JS “Змейка” вверху и Brython “Змейка” внизу

Как и ожидалось, скорость вычисления кода Brython при выполнении ниже, чем у JavaScript. Конкретно в моём случае отличие в 1,7 раза.

Предполагаю, что для более сложных проектов Brython будет в несколько раз медленнее чистого JS.

Тем не менее вы можете заранее транспилировать код Brython и добавить JavaScript только на ту страницу, от которой требуется повышенная производительность. 

Я пробовал использовать Brython Editor для преобразования кода Brython в JS и выполнить итоговый JS на странице, но при этом выбрасывалось огромное количество ошибок, и я решил пока эту затею отложить. 

Выводы по использованию Brython

Несмотря ни на что, Brython меня весьма впечатлил. Ниже я привёл плюсы и минусы, обнаруженные на личном опыте использования этого языка:

Плюсы👍

  • Мне удалось создать “Змейку” без лишних хлопот и при этом я получил позитивный опыт отладки. 
  • В моём простом проекте Brython легко взаимодействовал с нативными объектами JS доступными на странице.
  • Я ценю тот факт, что мой код выглядит чище, чем в Python, а также, что я могу использовать полезный синтаксис Python в написании кода для браузера.
  • Что касается “Змейки, то несмотря на её более долгую загрузку по сравнению с чистым JS, для пользователя эта разница заметна не будет. 
  • Меня порадовало видеть Python в исходном коде моего сайта 🙂

Минусы👎

  • При выполнении Brython намного медленнее чистого JS.
  • Разработчику для удобства работы с Brython необходимо в некоторой степени знать JavaScript.
  • Неизбежно большее число ошибок.
  • Документация Brython и его веб-сайт неудобен в навигации и обучения.
  • Brython недостаёт развитой экосистемы и инструментов для разработки.

Закончив в итоге свой первый проект Brython, я могу с уверенностью сказать, что однажды вернусь к этому языку ещё. 

Тем не менее я считаю, что в своём текущем состоянии Brython больше подходит JS разработчикам, знающим Python и уставшим от JavaScript, нежели Python разработчикам, желающим заниматься веб-разработкой, не изучая при этом JavaScript.

На мой взгляд, для успешной работы в Brython вам нужно понимать некоторые аспекты JS. Если же вы решите затратить на их изучение столько времени, чтобы в итоге писать на Brython легко, то можете с тем же успехом просто использовать сам JavaScript.

Другие альтернативы браузерному JS

Я выбрал Brython по причине того, что среди всех альтернатив JS на основе Python, с которыми я познакомился, только эта по-прежнему активно дорабатывается на GitHub. Большинство известных мне транспиляторов Python -> JavaScript не видели коммитов уже на протяжении нескольких лет. Тем не менее другие альтернативы существуют.

Достаточно интересно выглядит в этом плане Pyodide. Он компилирует Python (вместе с его библиотеками для обработки данных) в WebAssembly, который позволяет выполнять код Python в браузере.

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

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

Это амбициозный и многообещающий проект, который скорее всего приведёт к росту объёмов веб-разработки без применения JavaScript.

Тем не менее он по-прежнему пребывает на начальной стадии (около 3 лет) развития, поэтому пройдёт ещё, вероятно, немало времени, прежде чем JavaScript будет повсеместно замещён другим языком. 

И пока этот день не настанет, разработчики будут вынуждены прибегать к использованию инструментов вроде Brython, если уж работать с JavaScript очень надоест.

Но лично я считаю, что это неплохое начало!

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Yakko Majuri: Sick of Using Javascript for the Web? Use Browser Python Instead

Предыдущая статьяОсновы науки о данных
Следующая статьяОсновы работы с Git