Надоело использовать Javascript для web? Вместо этого используйте браузерный Python

3014
Надоело использовать Javascript для web? Вместо этого используйте браузерный Python.
Надоело использовать Javascript для web? Вместо этого используйте браузерный Python.

«Подождите, что?» — вот как, по моему мнению, большинство людей отреагирует на название этой статьи.
Что вы имеете в виду, говоря «Просто используйте браузерный Python»?
Все знают, что браузеры могут выполнять только JavaScript.
Ниже приведен скриншот исходного кода моего личного сайта, посмотрите, заметите ли вы что-то другое:

Да, это Python!

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

Представляем Brython

Brython — это реализация Python3, написанная на JavaScript, которая позволяет писать Python-код для веб.
По сути, это библиотека JavaScript, которая преобразует ваш Python-код в эквивалентный JS и обрабатывает его во время выполнения.
И поскольку написание Python для браузера звучит довольно круто, я решил попробовать.

Создаем игру Змейка с помощью Brython

Пишем змейку
Пишем змейку

Вот ссылка на мой сайт, где вы можете попробовать как JavaScript, так и Brython Snake. А вот ссылка на весь код на GitHub.

Для того чтобы попробовать Brython в действии, я решил создать классическую игру «Змейка».
Поскольку я не специалист по HTML Canvas и не разработчик игр, я решил использовать эту реализацию JavaScript в качестве отправной точки. Я уже создавал свою собственную игру-змейку на холсте, но эта намного аккуратнее и компактнее.
О да, и этот парень создал ее менее чем за 5 минут. Спасибо Крису Делеону, это очень впечатляет.
Итак, в дополнение к реализации Криса я добавил функцию подсчета очков и результатов, немного улучшил интерфейс, добавил кнопку паузы и кнопку инструкций. Затем я портировал его на Brython.
На самом деле, я также изменил его код так, чтобы он работал в строгом режиме JS, поскольку он использовал такие вещи, как неявные глобалы, которые, как мне кажется, не являются репрезентативными для большинства JavaScript (не критикую его — он занимался скоростным кодированием), и я хотел получить хорошее сравнение кода Brython и JS.
В итоге 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 и оценивать код внутри них.

API для работы с Веб

По умолчанию JavaScript предоставляет вам доступ к таким объектам, как документ и окно, которые необходимы в любом проекте JS. Таким образом, Brython должен иметь способ включить и их.
Чтобы решить эту проблему, создатели Brython могли бы просто позволить разработчикам по умолчанию сразу обращаться к document и window в коде Python, но это заставило бы отладчики Python постоянно кричать о undefined variable и ухудшило бы производительность.
Таким образом, чтобы использовать эти Web API, мы должны импортировать их так же, как мы это делаем с любым модулем Python:

from browser import document, html, window

На самом деле, вам не нужно выполнять pip install . В конце концов, мы вставляем это в наш HTML! Просто включите операторы импорта, и все в порядке — Brython будет знать, что делать.
Чтобы проверить, насколько хорошо это работает, я попробовал несколько различных методов из Web API, таких как alert , setInterval , addEventListener и т.д. Все сработало, как и ожидалось.

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

В игре Snake, после того как змея съест яблоко, нам нужно сгенерировать новое яблоко в случайном месте.
Однако, когда я начинал работать с Brython, я не знал, что могу использовать модуль случайных чисел random  Python (вы можете!). Как же мне сгенерировать случайное число (без создания собственной библиотеки)?
Оказывается, на самом деле мы можем получить доступ к большему количеству JavaScript, чем я думал. Посмотрите на это:

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

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

Концепции, специфичные для конкретного языка

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

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

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

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

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

JavaScript Snake
JavaScript Snake
Brython Snake
Brython Snake

Как и ожидалось, оценка кода Brython во время выполнения выполняется медленнее, чем JavaScript. В моем случае это было примерно в 1,7 раза медленнее.
Для более сложных проектов, я подозреваю, Brython будет в несколько раз медленнее, чем чистый JS.
Однако вы можете заранее транспонировать код Brython и включать JavaScript только на веб-странице, что должно улучшить работу.
Я пытался использовать редактор Brython Editor для преобразования кода Brython в JS и запуска реального JS на веб-странице, но он выдавал так много ошибок, что я решил пока отложить его в сторону. Впрочем, я не приложил к этому много усилий.

Заключительные размышления о Brython

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

Плюсы

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

Минусы

  • Брайтон работает значительно медленнее, чем чистый JS.
  • Для использования Brython разработчику необходимо иметь опыт работы с JavaScript.
  • Вы неизбежно столкнетесь с большим количеством ошибок
  • Документации Brython и его сайту есть куда расти в плане удобства навигации и возможностей обучения
  • У Brython отсутствует сильная экосистема и инструменты развития.

В целом, закончив свой первый проект с Brython, я могу с уверенностью сказать, что когда-нибудь попробую его снова.
Однако я считаю, что в нынешнем виде Brython больше подходит для разработчиков JavaScript, которые знают Python и устали от JavaScript, чем для разработчиков Python, которые хотят заниматься веб-разработкой без необходимости изучать JavaScript.
Чтобы хорошо работать с Brython, я думаю, вам нужно немного понимать JavaScript. И если вы решите потратить время на изучение достаточного количества JavaScript, чтобы с легкостью писать на Brython, то с тем же успехом вы можете просто использовать JavaScript.

Другие альтернативы Browser JS

Другие альтернативы Browser JS
Другие альтернативы Browser JS

Причина, по которой я выбрал Brython, заключается в том, что из всех альтернатив Python to JS, о которых я впервые узнал, это был единственный, который все еще активно разрабатывался на GitHub. Большинство транспилеров Python → JavaScript, с которыми я сталкивался, вообще не видели коммитов за последние несколько лет.

Однако существуют и другие альтернативы.

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

WebAssembly, как следует из названия, — это язык ассемблера для веба (Интернета). Подобно тому, как ассемблер в наших компьютерах может работать как посредник между языками высокого уровня и машинным кодом, WebAssembly может позволить то же самое сделать в Интернете.

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

Однако он все еще находится в зачаточном состоянии (~3 года), поэтому, вероятно, пройдет некоторое время, прежде чем мы увидим, как JavaScript будет заменен другими языками на регулярной основе.
А пока этого не происходит, вам придется прибегнуть к таким инструментам, как Brython, если вы действительно не можете работать с JavaScript.
Но, честно говоря, это неплохое начало!