Если вы веб‑разработчик, который предпочитает Python, а не JavaScript, то Brython (реализация Python, работающая в браузере) может быть привлекательным вариантом.

JavaScript де‑факто является языком интерфейса веб‑разработки. Сложные механизмы JavaScript являются неотъемлемой частью всех современных интернет‑браузеров и, естественно, побуждают разработчиков кодировать интерфейсные веб‑приложения на JavaScript. Brython предлагает лучшее из обоих миров, делая Python равноправным гражданином в браузере, который имеет доступ ко всем существующим библиотекам JavaScript и API, доступным в браузере.

В этом уроке вы узнаете, как:

  • установить Brython в вашей локальной среде
  • использовать Python в браузере
  • написать код Python, который взаимодействует с JavaScript
  • развернуть Python с вашим веб‑приложением
  • создать расширения браузера с помощью Python
  • сравните Brython с другими реализациями Python для веб‑приложений

Как разработчик Python среднего уровня, знакомый с веб‑разработкой, вы получите максимальную отдачу от этого урока, если у вас также есть некоторые знания HTML и JavaScript. Чтобы освежить в памяти JavaScript, посмотрите Python против JavaScript для Pythonistas.

Содержание

Запуск Python в браузере: преимущества    ↑

Хотя JavaScript является повсеместным языком интерфейсной веб‑разработки, к вам могут относиться следующие моменты:

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

Какова бы ни была причина, многие разработчики предпочли бы альтернативу JavaScript на основе Python для использования возможностей браузера. Запуск Python в браузере дает несколько преимуществ, что позволяет:

  • Выполнять один и тот же код Python на сервере и в браузере
  • Работать с различными API-интерфейсами браузера с использованием Python
  • Управлять объектной моделью документа (DOM) с помощью Python
  • Использовать Python для взаимодействия с существующими библиотеками JavaScript, такими как Vue.js и jQuery.
  • Обучать языку Python студентов Python с помощью редактора Brython
  • Получать удовольствия от программирования на Python

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

Реализация изоморфной веб‑разработки    ↑

Изоморфный JavaScript или Universal JavaScript (универсальный JavaScript), подчеркивает, что приложения JavaScript должны работать как на клиенте, так и на сервере. Предполагается, что серверная часть основана на JavaScript, а именно на сервере Node. Разработчики Python, использующие Flask или Django, также могут применять принципы изоморфизма к Python,при условии, что они могут запускать Python в браузере.

Brython позволяет создавать интерфейс на Python и обмениваться модулями между клиентом и сервером. Например, вы можете поделиться функциями проверки, такими как следующий код, который нормализует и проверяет номера телефонов в США:

import re

def normalize_us_phone(phone: str) -> str:
    """Извлекать номера и цифры из заданного номера телефонаr"""
    return re.sub(r"[^\da-zA-z]", "", phone)

def is_valid_us_phone(phone: str) -> bool:
    """Подтвердить 10-значный номер телефона"""
    normalized_number = normalize_us_phone(phone)
    return re.match(r"^\d{10}$", normalized_number) is not None

normalize_us_phone() удаляет любые не буквенно-цифровые символы, тогда как is_valid_us_phone() возвращает True, если входная строка содержит ровно десять цифр и не содержит буквенных символов. Один и тот же код может использоваться совместно процессами, запущенными на сервере Python, и клиентом, созданным с помощью Brython.

Доступ к веб‑API    ↑

Интернет-браузеры предоставляют стандартизированные Веб‑API для JavaScript. Эти стандарты являются частью стандарта жизни HTML. Вот некоторые примеры веб‑API:

Brython позволяет не только использовать веб‑API, но и взаимодействовать с JavaScript. С некоторыми веб‑API мы будем работать в следующем разделе.

Библиотеки прототипов и JavaScript    ↑

Python часто используется для создания прототипов фрагментов кода, языковых конструкций или более масштабных идей. С Brython эта распространенная практика кодирования становится доступной непосредственно в вашем браузере. Например, вы можете использовать консоль Brython или интерактивный редактор для экспериментов с фрагментом кода.

Откройте онлайн-редактор и введите следующий код:

from browser import ajax

def on_complete(req):
    print(req.text)

language = "fr"

ajax.get(f"https://fourtonfish.com/hellosalut/?lang={language}",
         blocking=True,
         oncomplete=on_complete)

Вот как работает этот код:

  • Строка 1 импортирует модуль ajax.
  • Строка 3 определяет on_complete(), функцию обратного вызова, которая вызывается после получения ответа от ajax.get().
  • Строка 6 вызывает ajax.get() для получения перевода «привет» на французский язык с помощью HelloSalut API.

Обратите внимание, что при выполнении этого кода в редакторе Brython блокировка может иметь значение True или False. Он должен иметь значение True, если вы выполняете тот же код в консоли Brython.
Щелкните Выполнить над панелью вывода, чтобы увидеть следующий результат:

{"code":"fr","hello":"Salut"}

Попробуйте изменить язык с fr на es и посмотрите на результат. Коды языков, поддерживаемые этим API, перечислены в документации HelloSalut.

Примечание. HelloSalut — один из общедоступных API-интерфейсов, доступных в Интернете и перечисленных в проекте GitHub Public APIs.

Вы можете изменить фрагмент кода в онлайн-редакторе, чтобы использовать другой общедоступный API. Например, попробуйте получить случайный общедоступный API из проекта Public APIs:

from browser import ajax

def on_complete(req):
    print(req.text)

ajax.get("https://api.publicapis.org/random",
         blocking=True,
         oncomplete=on_complete)

Скопируйте приведенный выше код в онлайн-редактор Brython и нажмите «Выполнить», чтобы отобразить результат. Вот пример в формате JSON:

{
  "count": 1,
  "entries": [
    {
      "API": "Open Government, USA",
      "Description": "United States Government Open Data",
      "Auth": "",
      "HTTPS": true,
      "Cors": "unknown",
      "Link": "https://www.data.gov/",
      "Category": "Government"
    }
  ]
}

Поскольку конечная точка получает случайный проект, вы, вероятно, получите другой результат. Дополнительные сведения о формате JSON см. В статье Working With JSON Data in Python (Работа с данными JSON в Python).

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

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

Обучение студентов Python    ↑

Brython — это и компилятор Python, и интерпретатор, написанный на JavaScript. В результате вы можете компилировать и запускать код Python в браузере. Хороший пример этой функции демонстрирует онлайн-редактор, доступный на веб‑сайте Brython.

В онлайн-редакторе Python работает в браузере. Нет необходимости устанавливать Python на машину, нет необходимости отправлять код на сервер для выполнения. Пользователь получает немедленную обратную связь, и такой подход не подвергает серверную часть вредоносным скриптам. Студенты могут экспериментировать с Python на любом устройстве с работающим браузером, например на телефонах или Chromebook, даже при нестабильном подключении к Интернету.

Учет эффективности    ↑

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

Однако не позволяйте воспринимаемой производительности отвлекать вас от использования Brython. Например, импорт модулей Python может привести к загрузке соответствующего модуля с сервера. Чтобы проиллюстрировать эту ситуацию,откройте консоль Brython и выполните следующий код:

>>> import uuid

Заметна задержка до появления подсказки (390 мс на тестовой машине). Это связано с тем, что Brython необходимо загрузить uuid и его зависимости, а затем скомпилировать загруженные ресурсы. Однако с этого момента нет задержки при выполнении функций, доступных в uuid. Например, вы можете сгенерировать случайный универсальный уникальный идентификатор, UUID версии 4, с помощью следующего кода:

>>> uuid.uuid4()
UUID('291930f9-0c79-4c24-85fd-f76f2ada0b2a')

Вызов uuid.uuid4() генерирует объект UUID, строковое представление которого печатается в консоли. Вызов uuid.uuid4() немедленно возвращается и выполняется намного быстрее, чем первоначальный импорт модуля uuid.

Развлечение    ↑

Вы, если ещё читаете сей труд, вероятно, заинтересованы в написании кода Python в браузере. Наблюдение за выполнением кода Python в браузере «щекочет» большинство питонистов, пробуждая веселье от бесконечных возможностей.

Автор Brython, Пьер Кентель и все участники проекта, взяв на себя огромную задачу по обеспечению совместимости этого языка с веб‑браузером, как и вы, помнили об удовольствии от Python.

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

import this

Подобно Python на вашем локальном компьютере, Brython компилирует и выполняет инструкции на лету и печатает The Zen of Python. Это происходит в браузере, и выполнение кода Python не требует какого-либо взаимодействия с внутренним сервером:

Можете попробовать еще одно классическое пасхальное яйцо Python в той же среде браузера со следующим кодом:

import antigravity

В Brython присутствует тот же юмор, который вы найдете в эталонной реализации Python.

Теперь, когда вы знакомы с основами работы с Brython, познакомитесь с более продвинутыми функциями в следующих разделах.

Установка Brython

Поэкспериментировать с онлайн-консолью Brython — хорошее начало, но оно не позволит вам развернуть код Python. Есть несколько различных вариантов установки Brython в локальной среде:

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

Установка CDN    ↑

Content Delivery Network (CDN) — это сеть серверов, которая позволяет повысить производительность и скорость загрузки онлайн-контента. Вы можете установить библиотеки Brython из нескольких разных CDN:

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

Чтобы проиллюстрировать использование Brython с CDN, вы воспользуетесь CDNJS. Создайте файл со следующим HTML‑кодом:

<!doctype html>
<html>
    <head>
        <script>
          src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.js">
        </script>
    </head>
    <body onload="brython()">
        <script type="text/python">
            import browser
            browser.alert("Привет от автора сайта Python — курс молодого бойца!")
        </script>
    </body>
</html>

Вот ключевые элементы этой HTML‑страницы:

  • Строка 5 загружает brython.js из CDNJS.
  • Строка 8 выполняет brython() после завершения загрузки документа. brython() читает код Python в текущей области видимости и компилирует его в JavaScript. См. Раздел «Понимание того, как работает Brython» для получения более подробной информации.
  • Строка 9 устанавливает тип сценария text/python, что указывает Brython, какой код необходимо скомпилировать и выполнить.
  • Строка 10 импортирует браузер, модуль Brython, который предоставляет объекты и функции, позволяющие взаимодействовать с браузером.
  • Строка 11 вызывает функцию alert (), которая отображает окно сообщения с текстом «Hello Real Python!»

Сохраните файл как index.html, затем дважды щелкните файл, чтобы открыть его в интернет‑браузере по умолчанию. Браузер отображает окно сообщения с надписью «Hello Real Python!» Нажмите ОК, чтобы закрыть окно сообщения:

Чтобы уменьшить размер загружаемого файла, особенно в производственной среде, рассмотрите возможность использования минимизированной версии brython.js:

<script
  src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js">
</script>

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

Установка GitHub    ↑

Установка GitHub очень похожа на установку CDN, но позволяет реализовать приложения Brython с последней версией разработки. Вы можете скопировать предыдущий пример и изменить URL-адрес в элементе head, чтобы получить следующий index.html:

<!doctype html>
<html>
  <head>
    <script
      src="https://raw.githack.com/brython-dev/brython/master/www/src/brython.js">
    </script>
  </head>
  <body onload="brython()">
    <script type="text/python">
      import browser
      browser.alert("Привет от автора сайта Python — курс молодого бойца!")
    </script>
  </body>
</html>

После сохранения этого файла в локальном каталоге дважды щелкните index.html, чтобы отобразить в браузере ту же страницу, которую вы получили при установке CDN.

Установка PyPI    ↑

Пока что вам не нужно было ничего устанавливать в локальной среде. Вместо этого вы указали в HTML‑файле, где браузер может найти пакет Brython. Когда браузер открывает страницу, он загружает файл JavaScript Brython из соответствующей среды, либо из CDN, либо из GitHub. Brython также доступен для локальной установки на PyPI. Установка PyPI для вас, если:

  • нужен максимальный контроль и максимум настроек среды Brython, помимо того, что доступно при указании на файл CDN.
  • У вас есть богатый опыт работы с Python и вы знакомы с pip.
  • Для минимизации задержек в сети во время разработки нужна локальная установка.
  • Хочется максимально точно управлять развитием своего проекта и его результатами.

При установке Brython из PyPI устанавливается brython‑cli, где используется командная строка, который можно автоматизировать такие функций, как создание шаблона проекта или упаковка и объединение модулей для упрощения развертывания проекта Brython.

Для получения дополнительных сведений обратитесь к документации по локальной установке, чтобы узнать о возможностях brython‑cli, доступных в вашей среде после установки. brython‑cli доступен только при этом типе установки. Он недоступен, если вы устанавливаете из CDN или с помощью npm. brython‑cli в действии вы увидите позже в этом руководстве.

Перед установкой Brython вы хотите создать виртуальную среду Python для этого проекта.

В Linux или macOS выполните следующие команды:

$ python3 -m venv .venv --prompt brython
$ source .venv/bin/activate
(brython) $ python -m pip install --upgrade pip
Collecting pip
  Downloading pip-20.2.4-py2.py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 1.3 MB/s
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 20.2.3
    Uninstalling pip-20.2.3:
      Successfully uninstalled pip-20.2.3

В Windows вы можете поступить следующим образом:

> python3 -m venv .venv --prompt brython
> .venv\Scripts\activate
(brython) > python -m pip install --upgrade pip
Collecting pip
  Downloading pip-20.2.4-py2.py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 1.3 MB/s
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 20.2.3
    Uninstalling pip-20.2.3:
      Successfully uninstalled pip-20.2.3

Вы только что создали специальную среду Python для своего проекта и обновили pip до последней версии.

На следующих шагах вы установите Brython и создадите проект по умолчанию. Команды одинаковы в Linux, macOS и Windows:

(brython) $ python -m pip install brython
Collecting brython
  Downloading brython-3.9.0.tar.gz (1.2 MB)
     |████████████████████████████████| 1.2 MB 1.4 MB/s
Using legacy 'setup.py install' for brython, since package 'wheel'
is not installed.
Installing collected packages: brython
    Running setup.py install for brython ... done
(brython) $ mkdir web
(brython) $ cd web
(brython) $ brython-cli --install
Installing Brython 3.9.0
done

Вы установили Brython из PyPI, создали пустую папку с именем web и сгенерировали скелет проекта по умолчанию, выполнив brython‑cli, скопированный в вашу виртуальную среду во время установки.

В веб‑папке команда brython-cli --install создала шаблон проекта и сгенерировала следующие файлы:

Файл Описание
README.txt Документация о том, как запустить HTTP-сервер Python и открыть demo.html
brython.js Базовый движок Brython (компилятор, среда выполнения и интерфейс браузера)
brython_stdlib.js Стандартная библиотека Brython
demo.html Исходный код HTML демо-страницы Brython
index.html Базовый пример, который вы можете использовать в качестве стартовой страницы для проекта
unicode.txt База данных символов Юникода (UCD), используемая unicodedata

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

(brython) $ python -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Когда вы выполняете python -m http.server, Python запускает веб‑сервер на порту 8000. Ожидаемая страница по умолчанию — index.html. В браузере укажите http://localhost: 8000 и должна отобразиться страница с текстом Hello:

Для более полного примера вы можете изменить URL-адрес в адресной строке браузера на http://localhost:8000/demo.html. Вы должны увидеть страницу, похожую на демонстрационную страницу Brython:

При таком подходе файлы JavaScript Brython загружаются непосредственно из вашей локальной среды. Обратите внимание на атрибут src в элементе head index.html:

<!doctype html>
<html>
  <head>
   <meta charset="utf-8">
   <script type="text/javascript" src="brython.js"></script>
   <script type="text/javascript" src="brython_stdlib.js"></script>
  </head>
  <body onload="brython(1)">
    <script type="text/python">
      from browser import document
      document <= "Hello"
    </script>
  </body>
</html>

Для удобства чтения в этом уроке приведенный выше HTML‑код имеет отступ. Однако, команда brython_cli --install отступы для исходного HTML‑шаблона не генерирует.

В HTML‑файле продемонстрированы несколько новых функций Brython:

  • Строка 6 загружает brython_stdlib.js, стандартную библиотеку Python, скомпилированную в JavaScript.
  • Строка 8 вызывает brython() с аргументом 1 для вывода сообщений об ошибках в консоль браузера.
  • Строка 10 импортирует модуль документа из браузера. Функции для доступа к DOM доступны в документе.
  • В строке 11 показан новый символ (<=), добавленный в Python в качестве синтаксического сахара. В этом примере document <= "Hello" заменяет document.body.appendChild(document.createTextNode ("Hello")). Подробнее об этих функциях DOM см. В Document.createTextNode.

Оператор <= используется для добавления дочернего узла к элементу DOM. Вы увидите более подробную информацию об использовании операторов, специфичных для Brython, в DOM API в Brython.

Установка npm    ↑

Если вы хорошо разбираетесь в экосистеме JavaScript, то установка npm может вам понравиться. Перед установкой требуются Node.js и npm.

Установка с помощью npm сделает модули JavaScript Brython доступными в вашем проекте, как и любые другие модули JavaScript. После этого вы сможете воспользоваться преимуществами своего любимого инструментария JavaScript для тестирования, упаковки и развертывания интерпретатора и библиотек Brython. Эта установка идеальна, если у вас уже есть существующие библиотеки JavaScript, установленные с npm.

Примечание. Если в вашей системе не установлены Node.js и npm, прочтите оставшуюся часть этого раздела только для информации, так как вы можете спокойно пропустить саму установку. Дальнейшая часть руководства не зависит от метода установки npm для любого из примеров.

Предполагая, что в вашей системе установлен npm, создайте файл package.json по умолчанию, вызвав npm init --yes в пустом каталоге:

$ npm init --yes
Wrote to /Users/john/projects/brython/npm_install/package.json:

{
  "name": "npm_install",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Чтобы интегрировать Brython в свой проект, выполните следующую команду:

$ npm install brython
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN npm_install@1.0.0 No description
npm WARN npm_install@1.0.0 No repository field.

+ brython@3.9.0
added 1 package from 1 contributor and audited 1 package in 1.778s
found 0 vulnerabilities

Вы можете игнорировать предупреждения и отметить, что Brython был добавлен в ваш проект. Для подтверждения откройте package.json и убедитесь, что у вас есть свойство dependencies, указывающее на объект, содержащий запись brython:

{
  "name": "npm_install",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "brython": "^3.9.0"
  }
}

Что касается предыдущих примеров, вы можете создать следующий index.html и открыть его в своем браузере. веб‑сервер для этого примера не нужен, потому что браузер может загружать файл JavaScript node_modules/brython/brython.js локально:

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<script type="text/javascript"
		src="node_modules/brython/brython.js" defer>
		</script>
	</head>
	<body onload="brython()">
		<script type="text/python">
		from browser import document
		document <= "Hello"
		</script>
	</body>
</html>

Браузер отображает index.html и загружает brython.js из URL-адреса скрипта в index.html. В этом примере вы увидели другой способ установки Brython, который использует преимущества экосистемы JavaScript. В оставшейся части руководства вы напишете код, основанный на установке CDN или установке PyPI.

Обзор вариантов установки Brython    ↑

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

В следующей таблице приведены некоторые рекомендации:

Вариант
установки
Контекст
CDN Вы хотите развернуть статический веб‑сайт и добавить динамическое поведение на свои страницы с минимальными накладными расходами. Вы можете рассматривать этот вариант как замену jQuery, за исключением использования Python, а не JavaScript.
GitHub Это похоже на установку CDN, но вы хотите поэкспериментировать с новейшей версией Brython.
PyPI У вас есть опыт кодирования на Python. Вы знакомы с pip и знакомы с тем, как создавать виртуальные среды Python. Вашему проекту могут потребоваться некоторые настройки, которые вы хотите сохранить в локальной среде или в репозитории исходного кода. Вы хотите иметь больший контроль над распространяемым пакетом. Вы хотите выполнить развертывание в закрытой среде без доступа к Интернету.
npm У вас есть опыт кодирования на JavaScript. Вы знакомы с инструментами JavaScript, в частности с Node.js и npm. Вашему проекту могут потребоваться некоторые настройки, которые вы хотите сохранить в локальной среде или в репозитории исходного кода. Вы хотите иметь больший контроль над распространяемыми пакетами. Вы хотите выполнить развертывание в закрытой среде без доступа к Интернету.

В таблице перечислены различные доступные варианты установки. В следующем разделе вы узнаете больше о том, как работает Brython.

Общие сведения о том, как работает Brython    ↑

Тур по различным способам установки Brython дал некоторые общие сведения о том, как работает реализация. Вот краткое изложение некоторых характеристик, которые описаны в этом уроке:

  • Brython есть реализация Python на JavaScript.
  • Brython есть переводчик Python в JavaScript и среда выполнения, выполняемая в браузере.
  • Он предоставляет две основные библиотеки, доступные в виде файлов JavaScript:
    1. brython.js — это ядро ​​языка Brython, как подробно описано в разделе «Основные компоненты Brython».
    2. brython_stdlib.js — это стандартная библиотека Brython.
  • Он вызывает brython(), который компилирует код Python, содержащийся в тегах скрипта, с типом text/python.

В следующих разделах вы подробнее узнаете, как работает Brython.

Основные компоненты Brython    ↑

Ядро Brython содержится в brython.js или brython.min.js, минимизированной версии движка Brython. Оба включают следующие ключевые компоненты:

  • brython() — основная функция JavaScript, представленная в глобальном пространстве имен JavaScript. Вы не можете выполнить какой-либо код Python без вызова этой функции. Это единственная функция JavaScript, которую необходимо вызывать явно.
  • __BRYTHON__ — это глобальный объект JavaScript, содержащий все внутренние объекты, необходимые для запуска скриптов Python. Этот объект не используется напрямую при написании приложений Brython. Если вы посмотрите на код Brython, как на JavaScript, так и на Python, то увидите регулярное появление __BRYTHON__. Вам не нужно использовать этот объект, но вы должны знать об этом, когда видите ошибку или когда хотите отладить свой код в консоли браузера.
  • Встроенные типы — это реализации встроенных типов Python в JavaScript. Например, py_int.js, py_string.js и py_dicts.js являются соответствующими реализациями int, str и dict.
  • browser — это модуль браузера, который предоставляет объекты JavaScript, обычно используемые во интерфейсных веб‑приложениях, например интерфейсы DOM, использующие документ, и окно браузера, использующее объект окна.

Вы увидите каждый из этих компонентов в действии, проработав примеры этого урока.

Стандартная библиотека Brython    ↑

Теперь, когда у вас есть общее представление об основном файле Brython, brython.js, вы узнаете о его сопутствующем файле brython_stdlib.js.

brython_stdlib.js есть стандартная библиотека Python. По мере создания этого файла Brython компилирует стандартную библиотеку Python в JavaScript и объединяет результат в пакет brython_stdlib.js.
Предполагается, что Brython максимально приближен к CPython, эталонной реализации Python. Для получения дополнительной информации о CPython ознакомьтесь с вашим руководством по исходному коду CPython и внутренним компонентам CPython.

Поскольку Brython работает в контексте веб‑браузера, у него есть некоторые ограничения. Например,браузер не разрешает прямой доступ к файловой системе, поэтому открыть файл с помощью os.open() невозможно. Функции, не относящиеся к веб‑браузеру, могут быть не реализованы. Например, приведенный ниже код работает в среде Brython:

>>> import os
>>> os.unlink()
Traceback (most recent call last):
  File , line 1, in 
NotImplementedError: posix.unlink is not implemented

os.unlink() вызывает исключение, поскольку удаление локального файла из среды браузера небезопасно, а API записей файлов и каталогов — это только черновик предложения.

Brython поддерживает только собственные модули Python. Он не поддерживает модули Python, созданные на C, если они не были повторно реализованы в JavaScript. Например, hashlib написан на C в CPython и реализован на JavaScript в Brython. Вы можете ознакомиться со списком модулей в дистрибутиве Brython для сравнения с реализацией CPython.

Вам необходимо включить brython_stdlib.js или brython_stdlib.min.js для импорта модулей из стандартной библиотеки Python.

Brython в действии    ↑

На этом этапе вам может быть интересно, как Brython ведет себя в браузере, который знает только о своем движке JavaScript. Повторно используя предыдущие примеры и инструменты, доступные в браузере, вы узнаете о процессе, участвующем в выполнении кода Python в браузере.

В разделе, посвященном установке сервера CDN, вы видели следующий пример:

<!doctype html>
<html>
    <head>
        <script
            src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.js">
        </script>
    </head>
    <body onload="brython()">
        <script type="text/python">
            import browser
            browser.alert("Hello Бизнес-информатика!")
        </script>
    </body>
</html>

После загрузки и анализа HTML‑страницы brython() выполняет следующие шаги:

  1. Читает код Python, содержащийся в элементе <script type = "text/python">
  2. Компилирует код Python в эквивалентный JavaScript
  3. Оценивает результирующий код JavaScript с помощью eval()

В приведенном выше примере код Python встроен в файл HTML:

<script type="text/python">
    import browser
    browser.alert("Hello Real Python!")
</script>

Другой вариант — загрузить код Python из отдельного файла:


    <script src="https://www.example.com/main.py"
            type="text/python"></script>

В этом случае файл Python будет выглядеть так:

import browser
browser.alert("Hello Real Python!")

Отделение кода Python от кода HTML — это более чистый подход, который позволяет вам воспользоваться преимуществами и функциональными возможностями редакторов кода. Большинство редакторов поддерживают встроенный JavaScript в HTML, но не поддерживают встроенный Python в HTML.

Внутреннее устройство Brython    ↑

В этом разделе более подробно рассматривается процесс преобразования кода Python в JavaScript. Если вас не интересуют эти подробности, пропустите этот раздел, так как он не требуется для понимания остальной части урока. Чтобы проиллюстрировать этот процесс и заглянуть внутрь Brython, выполните следующие действия:

В браузере JavaScript REPL введите и выполните следующий код:

> eval(__BRYTHON__.python_to_js("import browser; browser.console.log('Hello Бизнес-информатика!')"));

python_to_js() анализирует и компилирует предоставленный код Python в JavaScript, а затем выполняет JavaScript в веб‑браузере. У вас должен получиться следующий результат:

Применение eval() к коду Brython выводит «Hello Бизнес-информатика!» в консоли браузера. Функция JavaScript возвращает значение undefined, которое является возвращаемым значением по умолчанию для функции в JavaScript.

Когда вы создаете приложение Brython, вам не нужно явно вызывать функцию в модуле __BRYTHON__ JavaScript.
Этот пример предназначен только для демонстрации того, как Brython работает за кулисами. Знание __BRYTHON__ может помочь вам прочитать код Brython и даже внести свой вклад в проект по мере того, как вы набираетесь опыта. Это также поможет вам лучше понять исключения, которые могут отображаться в консоли браузера.
Объект JavaScript __BRYTHON__ доступен в глобальной области JavaScript, и вы можете получить к нему доступ с помощью консоли JavaScript браузера.

Использование Brython в браузере    ↑

На данный момент у вас достаточно понимания Brython, чтобы работать с более подробными примерами. В этом разделе мы собираемся реализовать калькулятор Base64, чтобы поэкспериментировать в браузере с DOM API и другими функциями, которые обычно доступны только в JavaScript.

Мы начнём с изучения того, как управлять DOM с помощью Python и HTML.

DOM API в Brython    ↑

Чтобы поэкспериментировать с манипуляциями с DOM, доступными в Brython, создадим форму для кодирования строки в Base64. Готовая форма будет выглядеть так:

Создайте следующий HTML‑файл и назовите его index.html:

<!-- index.html -->
<!DOCTYPE html >
<html>
	<head>
		<meta charset="utf-8"/>
		<link rel="stylesheet"
			href="https://cdnjs.cloudflare.com/ajax/libs/pure/2.0.3/pure-min.css" />
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js">
		</script>
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython_stdlib.min.js">
		</script>
		<script src="main.py" type="text/python" defer></script>
		<style>body { padding: 30px; }</style>
	</head>
	<body onload="brython()">
		<form class="pure-form" onsubmit="return false;">
			<fieldset>
				<legend>Base64 Calculator</legend>
				<input type="text" id="text-src" placeholder="Text to Encode" />
				<button
					type="submit" id="submit"
					class="pure-button pure-button-primary"
					autocomplete="off">Ok</button>
				<button id="clear-btn" class="pure-button">Clear</button>
			</fieldset>
		</form>
		<div id="b64-display"></div>
	</body>
</html>

HTML‑код выше загружает статические ресурсы, определяет макет пользовательского интерфейса и запускает компиляцию Python:

  • Строка 7 загружает таблицу стилей PureCSS, чтобы улучшить стиль HTML по умолчанию. Для работы Brython это не обязательно.
  • Строка 9 загружает свернутую версию движка Brython.
  • Строка 12 загружает минимизированную версию стандартной библиотеки Brython.
  • Строка 14 загружает main.py, который обрабатывает динамическую логику этой статической HTML‑страницы. Обратите внимание на использование defer. Это помогает синхронизировать загрузку и оценку ресурсов и иногда необходимо убедиться, что Brython и любые скрипты Python полностью загружены перед выполнением brython().
  • Строка 21 описывает поле ввода, которое принимает строку для кодирования в качестве аргумента.
  • Строки с 22 по 25 определяют кнопку по умолчанию, которая запускает основную логику страницы. Вы можете увидеть эту логику, реализованную в main.py ниже.
  • Строка 26 определяет кнопку для очистки данных и элементов на странице. Это реализовано в main.py ниже.
  • Строка 29 объявляет div, предназначенный для использования в качестве заполнителя для таблицы.

Соответствующий код Python, в main.py, выглядит следующим образом:

from browser import document, prompt, html, alert
import base64

b64_map = {}

def base64_compute(_):
    value = document["text-src"].value
    if not value:
        alert("You need to enter a value")
        return
    if value in b64_map:
        alert(f"'The base64 value of '{value}' already exists: '{b64_map[value]}'")
        return
    b64data = base64.b64encode(value.encode()).decode()
    b64_map[value] = b64data
    display_map()

def clear_map(_) -> None:
    b64_map.clear()
    document["b64-display"].clear()

def display_map() -> None:
    table = html.TABLE(Class="pure-table")
    table <= html.THEAD(html.TR(html.TH("Text") + html.TH("Base64")))
    table <= (html.TR(html.TD(key) + html.TD(b64_map[key])) for key in b64_map)
    base64_display = document["b64-display"]
    base64_display.clear()
    base64_display <= table
    document["text-src"].value = ""

document["submit"].bind("click", base64_compute)
document["clear-btn"].bind("click", clear_map)

Код Python показывает определение функций обратного вызова и механизм для управления DOM:

  • Строка 1 импортирует модули, которые вы используете для взаимодействия с DOM и кодом API браузера в brython.min.js.
  • Строка 2 импортирует base64, который доступен в стандартной библиотеке Brython, brython_stdlib.min.js.
  • Строка 4 объявляет словарь, который вы будете использовать для хранения данных в течение всего существования HTML‑страницы.
  • Строка 6 определяет обработчик событий base64_compute(), который кодирует значение Base64 текста, введенного в поле ввода, с идентификатором text-src. Это функция обратного вызова, которая принимает событие в качестве аргумента. Этот аргумент не используется в функции, но является обязательным в Brython и необязательным в JavaScript. По соглашению вы можете использовать в качестве фиктивного заполнителя. Пример такого использования описан в Руководстве по стилю Google Python.
  • Строка 7 извлекает значение элемента DOM, идентифицированного с помощью text-src.
  • Строка 18 определяет обработчик события clear_map(), который очищает данные и представление данных на этой странице.
  • Строка 22 определяет display_map(), которая берет данные, содержащиеся в b64_map, и отображает их под формой на странице.
  • Строка 26 извлекает элемент DOM с идентификатором text-src.
  • Строка 29 очищает значение элемента DOM с идентификатором text-src.
  • Строка 31 связывает событие onclick кнопки отправки с base64_compute().
  • Строка 32 связывает событие onclick кнопки clear-btn с clear_map().

Для управления DOM Brython использует два оператора:

  1. <=  — новый оператор, специфичный для Brython, который добавляет дочерний элемент к узлу. Вы можете увидеть несколько примеров этого использования в display_map(), определенном в строке 22.
  2. + заменяет Element.insertAdjacentHTML('afterend') и добавляет родственные узлы.

Вы можете увидеть оба оператора в следующем операторе, взятом из display_map():

table <= html.THEAD(html.TR(html.TH("Text") + html.TH("Base64")))

Вы можете прочитать приведенный выше код как «добавить к элементу таблицы элемент заголовка таблицы, содержащий элемент строки таблицы, состоящий из двух смежных элементов ячеек данных таблицы. Он отображается в браузере как следующий HTML‑код:

<table>
<thead><tr><th>Text</th><th>Base64</th></tr></thead>
</table>

Код HTML показывает вложенную структуру для строки заголовка элемента таблицы. Вот более читаемый формат того же кода:

<table>
	<thead>
		<tr>
			<th>Text</th>
			<th>Base64</th>
		</tr>
	</thead>
</table>

Чтобы увидеть результат в консоли Brython, вы можете ввести следующий блок кода:

>>> from browser import html
>>> table = html.TABLE()
>>> table <= html.THEAD(html.TR(html.TH("Text") + html.TH("Base64")))
>>> table.outerHTML
'<table><thead><tr><th>Text</th><th>Base64</th></tr></thead></table>'

Чтобы выполнить полный код, вам необходимо запустить веб‑сервер. Как и раньше, вы запускаете встроенный веб‑сервер Python в том же каталоге, что и два файла index.html и main.py:

$ python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Мы расширим возможности этого примера в разделе Веб‑API браузера, разрешив сохранение данных между перезагрузками страницы.

Импорт в Brython    ↑

Вы можете использовать импорт для доступа к модулям Python или модулям Brython, скомпилированным в JavaScript.

Модули Python — это файлы с расширением .py в корневой папке вашего проекта или для пакета Python, во вложенной папке, содержащей файл __init__.py. Чтобы импортировать модули Python в код Brython, вам необходимо запустить веб‑сервер.
Чтобы узнать больше о модулях Python, ознакомьтесь с уроком Архитектура приложений на Python — модули и пакеты.

Чтобы изучить, как импортировать модули Python в код Brython, следуйте инструкциям, описанным в разделе об установке с помощью PyPI, создайте и активируйте виртуальную среду Python, установите Brython и измените index.html следующим образом:

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<script type="text/javascript" src="brython.js"></script>
		<script type="text/javascript" src="brython_stdlib.js"></script>
	</head>

	<body onload="brython()">
		<script type="text/python">
			from browser import document, html, window
			import sys
			import functional

			selection = functional.take(10, range(10000))
			numbers = ', '.join([str(x) for x in selection])

			document <= html.P(f"{sys.version=}")
			document <= html.P(f"{numbers=}")
		</script>
	</body>
</html>

В HTML‑файле выше представлены модули, импортированные из основного движка (браузера), из стандартной библиотеки (sys) и из локального модуля Python (функциональный). Вот содержание function.py:

import itertools

def take(n, iterable):
    "Return first n items of the iterable as a list"
    return list(itertools.islice(iterable, n))

Этот модуль реализует take(), один из рецептов itertools. take() возвращает первые n элементов заданной итерации. Он полагается на itertools.slice().

Если вы попытаетесь открыть index.html из файловой системы в браузере, в консоли браузера появится следующая ошибка:

Traceback (most recent call last):
  File file:///Users/andre/brython/code/import/index.html/__main__
  line 3, in 
    import functional
ModuleNotFoundError: functional

Для импорта модуля Python требуется запуск локального веб‑сервера. Запустите локальный веб‑сервер и укажите в браузере адрес http://localhost:8000. Вы должны увидеть следующую HTML‑страницу:

При работающем веб‑сервере браузер смог получить модуль function.py при выполнении функции импорта. Результаты обоих значений, sys.version и чисел, вставляются в файл HTML двумя последними строками встроенного скрипта Python и отображаются браузером.

Уменьшить размер импорта    ↑

В каталоге проекта из предыдущего примера, чтобы уменьшить размер импортированных модулей JavaScript и предварительно скомпилировать модули Python в JavaScript, вы можете использовать brython-cli с параметром --modules:

$ brython-cli --modules
Create brython_modules.js with all the modules used by the application
searching brython_stdlib.js...
finding packages...
script in html index.html

Это сгенерирует brython_modules.js, и вы можете изменить элемент заголовка index.html следующим образом:

<head>
<meta charset="utf-8">
<script type="text/javascript" src="brython.js"></script>
<script type="text/javascript" src="brython_modules.js"></script>
</head>

Строка 4 изменяет исходный источник скрипта с brython_stdlib.js на brython_modules.js.

Открытие index.html в браузере или указание в браузере локального сервера отображает ту же HTML‑страницу. Обратите внимание на следующие моменты:

  1. Вы можете отобразить HTML‑страницу в своем браузере, не запуская веб‑сервер.
  2. Вам не нужно распространять functional.py, поскольку код был преобразован в JavaScript и включен в brython_modules.js.
  3. Вам не нужно загружать brython_stdlib.js.

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

Примечание. Как и при импорте модуля Python, загрузка модуля Python с элементом сценария HTML требует запуска веб‑сервера. Рассмотрим следующий элемент сценария HTML:

<script src = "main.py" type = "text/python"></script>

Когда функция Brython выполняется и загружает содержимое скрипта, указывающее на файл Python, он пытается выполнить вызов Ajax, который может быть выполнен только при работающем веб‑сервере. Если вы попытаетесь открыть файл из файловой системы, то в консоли JavaScript браузера отобразится ошибка, подобная следующей:

IOError: can't load external script at file:///project/main.py
(Ajax calls not supported with protocol file:///)

IOError: невозможно загрузить внешний скрипт в file:///project/main.py
(Вызовы Ajax не поддерживаются протоколом file:///)

Защита безопасности предотвращает загрузку main.py из локальной файловой системы. Вы можете решить эту проблему, запустив локальный файловый сервер. Дополнительные сведения об этом поведении см. В документации Brython.

Взаимодействие с JavaScript    ↑

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

JavaScript    ↑

До этого момента вы сталкивались с несколькими сценариями, в которых код Python взаимодействует с кодом JavaScript. В частности, вы смогли отобразить окно сообщения, вызвав browser.alert().

Вы можете увидеть предупреждение в действии в следующих трех примерах, запущенных в консоли Brython, а не в стандартной оболочке интерпретатора CPython:

>>> import browser
>>> browser.alert("Real Python")

Или вы можете использовать окно:

>>> from browser import window
>>> window.alert("Real Python")

Или вы можете написать так:

>>> from javascript import this
>>> this().alert("Real Python")

Благодаря новому уровню, предоставляемому Brython и глобальному характеру как alert(), так и window, вы можете вызывать alert в browser.window или даже в javascript.this.

Вот основные модули Brython, обеспечивающие доступ к функциям JavaScript:

Модули Контекст Примеры
browser Содержит встроенные имена и модули browser.alert()
browser.document Доступ к DOM document.getElementById("element-id")
document["element-id"]
browser.html Создание HTML‑элементов html.H1("Это заголовок")
browser.window Доступ к функциям и объектам Window window.navigator
window.frames
javascript Доступ к объектам, определённым в JavaScript javascript.this()
javascript.JSON.parse()

Помимо функций JavaScript и API-интерфейсов, доступных в браузере, вы также можете получить доступ к написанным вами функциям JavaScript. В следующем примере показано, как получить доступ к пользовательской функции JavaScript из Brython:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <script
        src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.js">
    </script>
    <script type="text/javascript">
      function myMessageBox(name) {
        window.alert(`Hello ${name}!`);
      }
    </script>
  </head>
  <body onload="brython()">
    <script type="text/python">
      from browser import window
      window.myMessageBox("Jon")
    </script>
  </body>
</html>

Вот как это работает:

  • Строка 9 определяет пользовательскую функцию myMessageBox() в блоке JavaScript.
  • Строка 17 вызывает myMessageBox().

Вы можете использовать ту же функцию для доступа к библиотекам JavaScript. Вы увидите, как это сделать, в разделе Web UI Framework, где вы будете взаимодействовать с Vue.js, популярным фреймворком веб‑интерфейса.

Веб‑API браузера    ↑

Браузеры предоставляют веб‑API, к которым вы можете получить доступ из JavaScript, а Brython имеет доступ к тем же API. В этом разделе расширим возможности калькулятора Base64 для хранения данных между перезагрузками страницы браузера.

Веб-API, обеспечивающий эту функцию, - это API веб‑хранилища. Он включает в себя два механизма:

  1. sessionStorage
  2. localStorage

В следующем примере будет использовать localStorage.

Как вы узнали ранее, калькулятор Base64 создает словарь, содержащий входную строку, сопоставленную со значением этой строки в кодировке Base64. Данные сохраняются в памяти после загрузки страницы, но удаляются при перезагрузке страницы. Сохранение данных в localStorage сохранит словарь между перезагрузками страницы. LocalStorage - это хранилище ключей и значений.

Чтобы получить доступ к localStorage, необходимо импортировать хранилище. Чтобы оставаться ближе к первоначальной реализации, загрузите и сохраните данные словаря в localStorage в формате JSON. Ключом для сохранения и получения данных будет b64data.Измененный код включает новый импорт и функцию load_data():

from browser.local_storage import storage
import json, base64

def load_data():
    data = storage.get("b64data")
    if data:
        return json.loads(data)
    else:
        storage["b64data"] = json.dumps({})
        return {}

load_data() выполняется при загрузке кода Python. Он извлекает данные JSON из localStorage и заполняет словарь Python, который будет использоваться для хранения данных в памяти в течение жизни страницы. Если он не находит b64data в localStorage, он создает пустой словарь для ключа b64data в localStorage и возвращает пустой словарь..

Вы можете просмотреть полный код Python, включающий load_data(), развернув поле ниже. Он показывает, как использовать веб‑API localStorage в качестве постоянного хранилища, а не полагаться на эфемерное хранилище в памяти, как в предыдущей реализации этого примера.

from browser import document, prompt, html, alert
from browser.local_storage import storage
import json, base64

def load_data():
    data = storage.get("b64data")
    if data:
        return json.loads(data)
    else:
        storage["b64data"] = json.dumps({})
        return {}

def base64_compute(evt):
    value = document["text-src"].value
    if not value:
        alert("You need to enter a value")
        return
    if value in b64_map:
        alert(f"'{value}' already exists: '{b64_map[value]}'")
        return
    b64data = base64.b64encode(value.encode()).decode()
    b64_map[value] = b64data
    storage["b64data"] = json.dumps(b64_map)
    display_map()

def clear_map(evt):
    b64_map.clear()
    storage["b64data"] = json.dumps({})
    document["b64-display"].clear()

def display_map():
    if not b64_map:
        return
    table = html.TABLE(Class="pure-table")
    table <= html.THEAD(html.TR(html.TH("Text") + html.TH("Base64")))
    table <= (html.TR(html.TD(key) + html.TD(b64_map[key])) for key in b64_map)
    base64_display = document["b64-display"]
    base64_display.clear()
    base64_display <= table
    document["text-src"].value = ""

b64_map = load_data()
display_map()
document["submit"].bind("click", base64_compute)
document["clear-btn"].bind("click", clear_map)

Глобальный словарь b64_map заполняется load_data(), когда файл загружается и обрабатывается при вызове brython(). Когда страница перезагружается, данные извлекаются из localStorage.

Каждый раз, когда вычисляется новое значение Base64, содержимое b64_map конвертируется в JSON и сохраняется в локальном хранилище. Ключ к хранилищу - это b64data.

Вы можете получить доступ ко всем функциям веб‑API из браузера и других подмодулей. Документация высокого уровня по доступу к веб‑API доступна в документации Brython. Для получения дополнительной информации вы можете обратиться к документации по веб‑API и использовать консоль Brython для экспериментов с веб‑API.

В некоторых ситуациях возможно, вам придется выбирать между знакомыми функциями Python и функциями из веб‑API. Например, в приведенном выше коде вы используете кодировку Python Base64, base64.b64encode(), но вы могли бы использовать btoa() JavaScript:

>>> from browser import window
>>> window.btoa("Real Python")
'UmVhbCBQeXRob24='

Вы можете проверить оба варианта в онлайн-консоли. Использование window.btoa() будет работать только в контексте Brython, тогда как base64.b64encode() может выполняться с помощью обычной реализации Python, такой как CPython. Обратите внимание, что в версии CPython base64.b64encode() принимает массив байтов в качестве типа аргумента, тогда как JavaScript window.btoa() принимает строку.

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

Структура веб‑интерфейса    ↑

Популярные JavaScript-фреймворки UI, такие как Angular, React, Vue.js или Svelte, стали важной частью набора инструментов фронтенд-разработчика, и Brython легко интегрируется с некоторыми из этих фреймворков. В этом разделе мы создадим приложение, используя Vue.js версии 3 и Brython.

Приложение, которое мы создадим, представляет собой форму, которая вычисляет hash строки. Вот скриншот работающей HTML‑страницы:

Тело HTML‑страницы декларативно определяет привязки и шаблоны:

<html>
  <head>
    <meta charset="utf-8"/>
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/pure/2.0.3/pure-min.min.css"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.2/vue.global.prod.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython_stdlib.min.js"></script>
    <script src="main.py" type="text/python"></script>
    <style>
      body { padding: 30px; }
      [v-cloak] { visibility: hidden; }
    </style>
  </head>

<body onload="brython(1)">
  <div id="app">
    <form class="pure-form" onsubmit="return false;">
      <fieldset>
        <legend>Hash Calculator</legend>
        <input type="text" v-model.trim="input_text"
               placeholder="Text to Encode" autocomplete="off"/>
        <select v-model="algo" v-cloak>
          <option v-for="name in algos" v-bind:value="name">

          </option>
        </select>
        <button @click="compute_hash" type="submit"
                class="pure-button pure-button-primary">Ok</button>
      </fieldset>
    </form>
    <p v-cloak></p>
  </div>
</body>

Если вы не знакомы с Vue, ниже вы быстро расскажете о некоторых вещах, но не стесняйтесь обращаться к официальной документации за дополнительной информацией:

  • Vue.js directives - это специальные значения атрибутов с префиксом v-, которые обеспечивают динамическое поведение и отображение данных между значениями компонентов DOM и Vue.js:
    • v-модель.trim = "input_text" связывает входное значение с input_text модели Vue и обрезает значение.
    • v-model = "algo" связывает значение раскрывающегося списка с алгоритмом.
    • v-for = "name in algos" связывает значение параметра с именем.
  • Vue templates обозначаются переменными, заключенными в двойные фигурные скобки. Vue.js заменяет соответствующие заполнители на соответствующее значение в компоненте Vue:
    • hash_value
    • <name
  • Event handlers обозначаются символом at(@), как в @click = "compute_hash".

Соответствующий код Python описывает Vue и прилагаемую бизнес-логику:

from browser import alert, window
from javascript import this
import hashlib

hashes = {
    "sha-1": hashlib.sha1,
    "sha-256": hashlib.sha256,
    "sha-512": hashlib.sha512,
}

Vue = window.Vue

def compute_hash(evt):
    value = this().input_text
    if not value:
        alert("You need to enter a value")
        return
    hash_object = hashes[this().algo]()
    hash_object.update(value.encode())
    hex_value = hash_object.hexdigest()
    this().hash_value = hex_value

def created():
    for name in hashes:
        this().algos.append(name)
    this().algo = next(iter(hashes))

app = Vue.createApp(
    {
        "el": "#app",
        "created": created,
        "data": lambda _: {"hash_value": "", "algos": [], "algo": "", "input_text": ""},
        "methods": {"compute_hash": compute_hash},
    }
)

app.mount("#app")

Декларативный характер Vue.js отображается в файле HTML с директивами и шаблонами Vue. Это также продемонстрировано в коде Python с объявлением компонента Vue в строке 11 и строках 28–35. Этот декларативный метод связывает значения узлов DOM с данными Vue, обеспечивая реактивное поведение фреймворка.

Это устраняет некоторый шаблонный код, который вам приходилось писать в предыдущем примере. Например, обратите внимание, что вам не нужно было выбирать элементы из DOM с помощью такого выражения, как document["some_id"]. Создание приложения Vue и запуск app.mount() обрабатывает сопоставление компонента Vue с соответствующими элементами DOM и привязку функций JavaScript.

В Python для доступа к полям объекта Vue необходимо обратиться к объекту Vue с помощью javascript.this():

  • Строка 14 извлекает значение поля компонента this().Input_text.
  • Строка 21 обновляет компонент данных this().hash_value.
  • Строка 25 добавляет алгоритм в список this().Algos.
  • Строка 26 создает экземпляр this().algo с первым ключом hashes{}.

Если это введение Vue в сочетании с Brython вызвало у вас интерес, тогда вы можете посмотреть проект vuepy, который предоставляет полные привязки Python для Vue.js и использует Brython для запуска Python в браузере.

WebAssembly    ↑

В некоторых ситуациях вы можете использовать WebAssembly для повышения производительности Brython или даже JavaScript. WebAssembly или Wasm - это двоичный код, поддерживаемый всеми основными браузерами. Он может обеспечить повышение производительности по сравнению с JavaScript в браузере и является целью компиляции для таких языков, как C, C++ и Rust. Если вы не используете Rust или Wasm, тогда вы можете пропустить этот раздел.

В следующем примере, демонстрирующем способ использования WebAssembly, вы реализуете функцию в Rust и вызываете ее из Python.

Это не является исчерпывающим руководством по Rust. Это только царапает поверхность. Для получения дополнительных сведений о Rust ознакомьтесь с документацией по Rust.

Начните с установки Rust с помощью rustup. Для компиляции файлов Wasm вам также необходимо добавить цель wasm32:

$ rustup target add wasm32-unknown-unknown

Создайте проект с помощью Cargo, который устанавливается при установке Rust:

$ cargo new --lib op

Приведенная выше команда создает скелет проекта в папке с именем op. В этой папке вы найдете Cargo.toml, файл конфигурации сборки Rust, который вам нужно изменить, чтобы указать, что вы хотите создать динамическую библиотеку. Это можно сделать, изменив файл следующим обрахом:

[package]
name = "op"
version = "0.1.0"
authors = ["John <>"]
edition = "2018"

[lib]
crate-type=["cdylib"]

[dependencies]

Измените src/lib.rs, заменив его содержимое следующим:

#[no_mangle]
pub extern fn double_first_and_add(x: u32, y: u32) -> u32 {
    (2 * x) + y
}

В корне проекта, где находится Cargo.toml, скомпилируйте свой проект:

$ cargo build --target wasm32-unknown-unknown

Затем создайте веб‑каталог со следующим index.html:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js"></script>
  <script src="main.py" type="text/python"></script>
</head>
<body onload="brython()">

<form class="pure-form" onsubmit="return false;">
  <h2>Custom Operation using Wasm + Brython</h2>
  <fieldset>
    <legend>Multiply first number by 2 and add result to second number</legend>
    <input type="number" value="0" id="number-1" placeholder="1st number"
           autocomplete="off" required/>
    <input type="number" value="0" id="number-2" placeholder="2nd number"
           autocomplete="off" required/>
    <button type="submit" id="submit" class="pure-button pure-button-primary">
        Execute
    </button>
  </fieldset>
</form>

<br/>
<div id="result"></div>
</body>
</html>

Строка 6 выше загружает следующий файл main.py из того же каталога:

from browser import document, window

double_first_and_add = None

def add_rust_fn(module):
  global double_first_and_add
  double_first_and_add = module.instance.exports.double_first_and_add

def add_numbers(evt):
    nb1 = document["number-1"].value or 0
    nb2 = document["number-2"].value or 0
    res = double_first_and_add(nb1, nb2)
    document["result"].innerHTML = f"Result: ({nb1} * 2) + {nb2} = {res}"

document["submit"].bind("click", add_numbers)
window.WebAssembly.instantiateStreaming(window.fetch("op.wasm")).then(add_rust_fn)

Выделенные строки - это клей, позволяющий Brython получить доступ к функции double_first_and_add() в Rust:

  • Строка 16 считывает op.wasm с помощью WebAssembly, а затем вызывает add_rust_fn() при загрузке файла Wasm.
  • В строке 5 реализована функция add_rust_fn(), которая принимает в качестве аргумента модуль Wasm.
  • Строка 7 назначает double_first_and_add() локальному имени double_first_and_add, чтобы сделать его доступным для Python.

В том же веб‑каталоге скопируйте op.wasm из target/wasm32-unknown-unknown/debug/op.wasm:

$ cp target/wasm32-unknown-unknown/debug/op.wasm web

Макет папки проекта выглядит так:

├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
├── target
│   ...
└── web
    ├── index.html
    ├── main.py
    └── op.wasm

Это показывает структуру папок проекта Rust, созданного с помощью Cargo new. Для наглядности цель частично опущена.

Теперь запустим сервер в сети:

$ python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Наконец, укажите в своем интернет‑браузере http://localhost:8000. Ваш браузер должен отобразить страницу, подобную следующей:

В этом проекте показано, как создать WebAssembly, который можно использовать из JavaScript или Brython. Из-за значительных накладных расходов, связанных с созданием файла Wasm, это не должно быть вашим первым подходом к решению конкретной проблемы.

Если JavaScript не соответствует вашим требованиям к производительности, возможно, вам подойдет Rust. В основном, это полезно, если у уже есть код Wasm для взаимодействия: либо ваш код, либо существующие библиотеки Wasm.

Еще одно возможное преимущество использования Rust для создания WebAssembly - доступ к библиотекам, которых нет в Python или JavaScript, что также может быть полезно, если вы хотите использовать библиотеку Python, написанную на C и которую нельзя использовать с Brython. Если такая библиотека существует в Rust, вы можете подумать о создании файла Wasm для использования его с Brython.

Применение асинхронной разработки в Brython    ↑

Синхронное программирование - это вычислительное поведение, с которым вы, возможно, знакомы лучше всего. Например, при выполнении трех операторов A, B и C программа сначала выполняет A, затем B и, наконец, C. Каждый оператор блокирует поток программы, прежде чем передать его следующему.

Представьте себе такую ​​технику, в которой сначала выполняется А, B будет вызван, но не выполнен немедленно, а затем будет выполнен C. Вы можете думать о B как об обещании быть выполненным в будущем. Поскольку B не блокируется, он считается асинхронным. Дополнительные сведения об асинхронном программировании можно найти в статье Приступая к работе с асинхронными функциями в Python. JavaScript является однопоточным и полагается на асинхронную обработку, в частности, когда задействованы сетевые коммуникации. Например, для получения результата API не требуется блокировать выполнение других функций JavaScript.

С Brython у вас есть доступ к асинхронным функциям через ряд компонентов:

По мере развития JavaScript обратные вызовы постепенно заменялись обещаниями или асинхронными функциями. В этом руководстве вы узнаете, как использовать Promise от Brython и как использовать модули browser.ajax и browser.aio, которые используют асинхронный характер JavaScript.

Модуль asyncio из библиотеки CPython не может использоваться в контексте браузера и заменен в Brython на browser.aio.

Promise JavaScript в Brython    ↑

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

Вот пример, показывающий, как использовать объект JavaScript Promise из Brython. Вы можете работать с этим примером в онлайн-консоли:

>>> from browser import timer, window
>>> def message_in_future(success, error):
...   timer.set_timeout(lambda: success("Будущее сообщение"), 3000)
...
>>> def show_message(msg):
...   window.alert(msg)
...
>>> window.Promise.new(message_in_future).then(show_message)

В веб‑консоли вы можете сразу увидет, как выполняется код Python:

  • Строка 1 импортирует таймер для установки тайм-аута и окна для доступа к объекту Promise.
  • Строка 2 определяет исполнителя message_in_future(), который возвращает сообщение, когда Promise выполнено успешно, в конце тайм-аута.
  • Строка 5 определяет функцию show_message(), который отображает предупреждение.
  • Строка 8 создает Promise с исполнителем, связанное с блоком then, предоставляя доступ к результату обещания.

В приведенном выше примере тайм-аут искусственно имитирует длительную функцию. Реальное использование обещания может включать сетевой вызов. Через 3 секунды Promise успешно завершается со значением «Будущее сообщение».

Если функция-исполнитель message_in_future() обнаруживает ошибку, она может вызвать error() с указанием причины ошибки в качестве аргумента. Вы можете реализовать это с помощью нового связанного метода .catch() для объекта Promise следующим образом:

>>> window.Promise.new(message_in_future).then(show_message).catch(show_message)

Вы можете увидеть результат успешного выполнения обещания на подобном изображении:

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

Ajax в Brython    ↑

Асинхронные функции особенно полезны, когда функции квалифицируются как связанные с вводом-выводом. Это контрастирует с функциями, связанными с процессором. Функция, привязанная к вводу-выводу, - это функция, которая в основном тратит время на ожидание завершения ввода или вывода, тогда как функция, привязанная к ЦП, выполняет вычисления. Вызов API по сети или запрос к базе данных - это ввод-вывод, тогда как вычисление последовательности простых чисел связано с ограничением ЦП.

Browser.ajax Brython предоставляет функции HTTP, такие как get() и post(), которые по умолчанию являются асинхронными. Эти функции принимают параметр блокировки, которому можно присвоить значение True, чтобы сделать ту же функцию синхронной.

Чтобы вызвать HTTP GET асинхронно, вызовите ajax.get() следующим образом:

ajax.get(url, oncomplete=on_complete)

Чтобы получить API в режиме блокировки, установите для параметра блокировки значение True:

ajax.get(url, blocking=True, oncomplete=on_complete)

Следующий код показывает разницу между блокирующим вызовом Ajax и неблокирующим вызовом Ajax:

from browser import ajax, document
import javascript

def show_text(req):
    if req.status == 200:
        log(f"Text received: '{req.text}'")
    else:
        log(f"Error: {req.status} - {req.text}")

def log(message):
    document["log"].value += f"{message} \n"

def ajax_get(evt):
    log("Before async get")
    ajax.get("/api.txt", oncomplete=show_text)
    log("After async get")

def ajax_get_blocking(evt):
    log("Before blocking get")
    try:
        ajax.get("/api.txt", blocking=True, oncomplete=show_text)
    except Exception as exc:
        log(f"Error: {exc.__name__} - Did you start a local web server?")
    else:
        log("After blocking get")

document["get-btn"].bind("click", ajax_get)
document["get-blocking-btn"].bind("click", ajax_get_blocking)

Приведенный выше код иллюстрирует оба поведения, синхронное и асинхронное:

  • Строка 13 определяет ajax_get(), которая извлекает текст из удаленного файла с помощью ajax.get(). По умолчанию ajax.get() ведет себя асинхронно. Функция ajax_get() возвращается, а функция show_text(), назначенная параметру oncomplete, вызывается после получения удаленного файла /api.txt.
  • Строка 18 определяет ajax_get_blocking(), которая демонстрирует, как использовать ajax.get() с поведением блокировки. В этом сценарии show_text() вызывается до возврата из ajax_get_blocking().

Когда вы запустите полный пример и нажмете Async Get и Blocking Get, вы увидите следующий экран:

Вы можете видеть, что в первом сценарии ajax_get() полностью выполняется, и результат вызова API происходит асинхронно. Во второй ситуации результат вызова API отображается перед возвратом из ajax_get_blocking().

Асинхронный ввод-вывод в Brython    ↑

С asyncio у Python 3.4 появились новые асинхронные возможности. В Python 3.5 асинхронная поддержка была расширена синтаксисом async/await. Из-за несовместимости с циклом событий браузера Brython реализует browser.aio как замену стандартному asyncio.

Браузер модуля Brython.
Модуль browser.aio Python поддерживают использование ключевых слов async и await и имеют общие функции, такие как run() и sleep(). Оба модуля реализуют другие различные функции, которые относятся к их соответствующим контекстам выполнения, контекстной среде CPython для asyncio и среде браузера для browser.aio.

Сопрограммы

Вы можете использовать run() и sleep() для создания сопрограмм. Чтобы проиллюстрировать поведение сопрограмм, реализованных в Brython, воспользуемся примером сопрограммы из документации CPython:

from browser import aio as asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print(f"started at {time.strftime('%X')}")

    await say_after(1, 'hello')
    await say_after(2, 'world')

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

За исключением первой строки импорта, код такой же, как в документации CPython. Он демонстрирует использование ключевых слов async и await и показывает run() и sleep() в действии:

  • В строке 1 asyncio используется в качестве псевдонима для browser.aio. Хотя это тень aio,
    он сохраняет код, близкий к примеру документации Python, чтобы облегчить сравнение.
  • Строка 4 объявляет сопрограмму say_after(). Обратите внимание на использование async.
  • Строка 5 вызывает asyncio.sleep() с ожиданием, чтобы текущая функция уступила управление другой функции до завершения sleep().
  • Строка 8 объявляет другую сопрограмму, которая сама вызовет сопрограмму say_after() дважды.
  • Строка 9 вызывает run(), неблокирующую функцию, которая принимает сопрограмму - в этом примере main() - в качестве аргумента.

Обратите внимание, что в контексте браузера aio.run() использует внутренний цикл событий JavaScript. Это отличается от связанной функции asyncio.
run() в CPython, который полностью управляет циклом событий.

Чтобы выполнить этот код, вставьте его в онлайн-редактор Brython и нажмите Run. Вы должны получить результат, похожий на следующий снимок экрана:

Сначала выполняется сценарий, затем отображается «hello» и, наконец, отображается «world».

Дополнительные сведения о сопрограммах в Python можно найти в статье «Асинхронный ввод-вывод в Python: полное пошаговое руководство».

Общие концепции асинхронного ввода-вывода применимы ко всем платформам, использующим этот шаблон. В JavaScript
цикл событий является неотъемлемой частью среды, тогда как в CPython это то, что управляется с помощью функций, предоставляемых asyncio.

Приведенный выше пример был специальным упражнением, чтобы сохранить код в точности так, как показано в примере документации Python. При кодировании в браузере с помощью Brython рекомендуется явно использовать browser.aio, как вы увидите в следующем разделе.

Специфические веб‑функции

Чтобы выполнить асинхронный вызов API, как в предыдущем разделе, можyj написать функцию, подобную следующей:

async def process_get(url):
    req = await aio.get(url)

Обратите внимание на использование ключевых слов async и await. Функция должна быть определена как асинхронная, чтобы использовать вызов с ожиданием. Во время выполнения этой функции при достижении вызова await aio.get(url) функция возвращает управление основному циклу событий, ожидая завершения сетевого вызова aio.get(). В остальном выполнение программы не блокируется.

Вот пример того, как вызвать process_get():

aio.run(process_get("/some_api"))

Функция aio.run() выполняет сопрограмму process_get(), которая неблокирующая.

Более полный пример кода показывает, как использовать ключевые слова async и await и как aio.run() и aio.get() дополняют друг друга:

from browser import aio, document
import javascript

def log(message):
    document["log"].value += f"{message} \n"

async def process_get(url):
    log("Before await aio.get")
    req = await aio.get(url)
    log(f"Retrieved data: '{req.data}'")

def aio_get(evt):
    log("Before aio.run")
    aio.run(process_get("/api.txt"))
    log("After aio.run")

document["get-btn"].bind("click", aio_get)

Как и в самых последних версиях Python 3, вы можете использовать ключевые слова async и await:

  • Строка 7 определяет process_get() с ключевым словом async.
  • Строка 9 вызывает aio.get() с ключевым словом await. Использование await требует, чтобы включающая функция была определена с помощью async.
  • В строке 14 показано, как использовать aio.run(), которая принимает в качестве аргумента вызываемую асинхронную функцию.

Чтобы запустить полный пример, вам нужно запустить веб‑сервер. Вы можете запустить веб‑сервер разработки Python с помощью python3 -m http.server. Он запускает локальный веб‑сервер на порту 8000 и странице index.html по умолчанию:

На снимке экрана показана последовательность шагов, выполняемых после нажатия Async Get. Комбинация использования модуля aio и ключевых слов async и await показывает, как можно использовать модель асинхронного программирования, которую продвигает JavaScript.

Распространение и упаковка проекта Brython
Метод, который вы используете для установки Brython, может повлиять на то, как и где вы можете развернуть свой проект Brython. В частности, для развертывания в PyPI лучше всего сначала установить Brython из PyPI, а затем создать свой проект с помощью brython-cli. Но при типичном веб‑развертывании на частном сервере или у облачного провайдера можно использовать любой метод установки по вашему выбору.

У вас есть несколько вариантов развертывания:

  • Ручное и автоматическое развертывание
  • Развертывание в PyPI
  • Развертывание в CDN

Вы изучите каждый из них в следующих разделах.

Ручное и автоматическое веб‑развертывание    ↑

Ваше приложение содержит все статические зависимости CSS, JavaScript, Python и файлы изображений, необходимые для вашего веб‑сайта. Brython является частью ваших файлов JavaScript. Все файлы могут быть развернуты «как есть» у выбранного вами поставщика. Вы можете обратиться к Руководствам по веб‑разработке и автоматизации развертывания Django с помощью Fabric и Ansible для получения подробной информации о развертывании ваших приложений Brython.

Если вы решите использовать brython-cli --modules для предварительной компиляции кода Python, то в развертываемых вами файлах не будет исходного кода Python, только brython.js и brython_modules.js. Также будет не включать brython_stdlib.js, поскольку необходимые модули уже будут включены в brython_modules.js.

Развертывание в PyPI    ↑

Когда вы устанавливаете Brython из PyPI, вы можете использовать brython-cli для создания пакета, который можно развернуть в PyPI. Цели создания такого пакета - расширить шаблон Brython по умолчанию в качестве основы для ваших пользовательских проектов и сделать веб‑сайты Brython доступными из PyPI.

Следуя инструкциям в разделе по установке из PyPI, выполните следующую команду в своем новом веб‑проекте:

$ brython-cli --make_dist

Вам будет предложено ответить на несколько вопросов, предназначенных для создания brython_setup.json, которые вы можете изменить позже. После завершения команды у вас будет каталог с именем __dist__, содержащий файлы, необходимые для создания устанавливаемого пакета.

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





Развертывание в CDN    ↑

Создание расширений Google Chrome

Расширения Chrome - это компоненты, созданные с использованием веб‑технологий и встроенные в Chrome для настройки вашей среды просмотра. Обычно значки этих расширений видны в верхней части окна Chrome справа от адресной строки.

Общедоступные расширения доступны в интернет‑магазине Chrome. Учить, устанавливаете расширения Google Chrome из локальных файлов:

Прежде чем приступить к реализации расширения Google Chrome в Brython, вы сначала реализуете версию JavaScript, а затем переведете ее в Brython.

Расширение Hello World в JS    ↑

Для начала вы реализуете расширение, которое будет выполнять следующие действия:

  1. Открытие всплывающего окна при нажатии на значок расширения
  2. Открывать подсказку при нажатии кнопки всплывающего окна
  3. Добавьте введенное вами сообщение внизу начального всплывающего окна.

На следующем снимке экрана показано это поведение:

В пустой папке создайте файл manifest.json для настройки расширения:

// manifest.json
{
    "name": "JS Hello World",
    "version": "1.0",
    "description": "Hello World Chrome Extension in JavaScript",
    "manifest_version": 2,
    "browser_action": {
        "default_popup": "popup.html"
    },
    "permissions": ["declarativeContent", "storage", "activeTab"]
}

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

В той же папке создайте файл popup.html, используемый для определения пользовательского интерфейса расширения:

<!-- popup.html -->
<!DOCTYPE html>
<html>
  <head>
    <script src="popup.js" defer></script>
  </head>
  <body>
    <button id="hello-btn">Hello JS</button>
    <div id="hello"></div>
  </body>
</html>

HTML‑файл включает ссылку на бизнес-логику JavaScript расширения и описывает его пользовательский интерфейс:

  • Строка 5 относится к popup.js, который содержит логику расширения.
  • Строка 8 определяет кнопку, которая будет привязана к обработчику в popup.js.
  • Строка 9 объявляет поле, которое будет использоваться кодом JavaScript для отображения некоторого текста.

Также необходимо создать popup.js:

// popup.js
'use strict';

let helloButton = document.getElementById("hello-btn");

helloButton.onclick = function (element) {
  const defaultName = "Real JavaScript";
  let name = prompt("Enter your name:", defaultName);
  if (!name) {
    name = defaultName;
  }
  document.getElementById("hello").innerHTML = `Hello, ${name}!`;
};

Основная логика кода JavaScript состоит в объявлении обработчика onclick, привязанного к полю hello-btn контейнера HTML:

  • Строка 2 вызывает режим сценария, который обеспечивает более строгую проверку в JavaScript для выявления ошибок JavaScript.
  • Строка 4 выбирает поле, обозначенное hello-btn в popup.html, и назначает его переменной.
  • Строка 6 определяет обработчик, который будет обрабатывать событие, когда пользователь нажимает кнопку. Этот обработчик событий запрашивает у пользователя свое имя, а затем изменяет содержимое <div>, идентифицированного с помощью hello, на указанное имя.

Перед установкой этого расширения выполните следующие действия:

  1. Откройте меню Google Chrome в правой части экрана.
  2. Откройте подменю More Tools.
  3. Щелкните Extensions.

На экране отобразятся ваши установленные в настоящее время расширения, если таковые имеются. Это может выглядеть так:

Чтобы установить новое расширение, вам необходимо выполнить следующие действия:

  1. Убедитесь, что режим разработчика включен в правой верхней части экрана.
  2. Щелкните Load unpacked.
  3. Выберите папку, содержащую все файлы, которые вы только что создали.

Если во время установки ошибок не произошло, вы должны увидеть новый значок с буквой J справа -в адресной строке браузера. Чтобы протестировать расширение, щелкните значок J на ​​панели инструментов, показанной ниже:

Если во время установки или выполнения возникают какие-либо ошибки, вы должны увидеть красную кнопку ошибки справа от кнопки удаления карты расширения:

Вы можете нажать Errors, чтобы отобразить ошибку и определить основную причину. После исправления перезагрузите расширение, щелкнув круговую стрелку в правом нижнем углу карты расширения, затем повторяйте процесс, пока он не заработает так, как вы ожидаете.

Чтобы протестировать недавно установленное расширение, вы можете щелкнуть значок J, отображаемый справой стороны панели инструментов браузера. Если значок не отображается, нажмите «Extensions», чтобы вывести список установленных расширений, и нажмите кнопку с канцелярской кнопкой, совпадающую с только что установленным расширением JS Hello World.

Расширение Hello World в Python    ↑

Если вы дошли до этого момента, значит, вы уже выполнили самые сложные шаги, в основном для ознакомления с процессом создания расширения Chrome и его установки. Шаги будут аналогичны с Brython, но с некоторыми отличиями, о которых вы сейчас узнаете.

Файл манифеста будет отличаться,с другим именем расширения и, для удобства, другим описанием:

// manifest.json
{
    "name": "Py Hello World",
    "version": "1.0",
    "description": "Hello World Chrome Extension in Python",
    "manifest_version": 2,
    "browser_action": {
        "default_popup": "popup.html"
    },
    "content_security_policy": "script-src 'self' 'unsafe-eval';object-src 'self'",
    "permissions": ["declarativeContent", "storage", "activeTab"]
}

Обратите внимание, что вам также необходимо включить новое свойство content_security_policy. Это необходимо для того, чтобы можно было ослабить политику eval() в системе расширений Chrome. Помните, что Brython использует eval().

Это не то, что вы вводили и что вы можете контролировать в Brython. Вам нужно будет включить использование eval(), если вы хотите использовать Brython в качестве языка расширения вашего браузера. Если вы не добавите unsafe-eval в content_security_policy, вы увидите следующую ошибку:

Uncaught EvalError: Refused to evaluate a string as JavaScript because
'unsafe-eval' is not an allowed source of script in the following Content
Security Policy directive: "script-src 'self' blob: filesystem:".

HTML‑файл также будет иметь несколько обновлений, а именно:

<!-- popup.html -->
<!DOCTYPE html>
<html>
  <head>
    <script src="brython.min.js" defer></script>
    <script src="init_brython.js" defer></script>
    <script src="popup.py" type="text/python" defer></script>
  </head>
  <body>
    <button id="hello-btn">Hello Py</button>
    <div id="hello"></div>
  </body>
</html>

Код HTML очень похож на тот, который вы использовали для создания расширения Chrome в JavaScript. Стоит отметить несколько деталей:

  • Строка 5 загружает brython.min.js из локального пакета. По соображениям безопасности загружаются только локальные скрипты, и вы не можете загружать их из внешнего источника, такого как CDN.
  • Строка 6 загружает init_brython.js, который вызывает brython().
  • Строка 7 загружает popup.py.
  • Строка 9 объявляет тело без обычного onload = "brython ()".

Другое ограничение безопасности не позволяет вам вызывать brython() в событии onload тега body. Обходной путь - добавить слушателя к документу и указать браузеру выполнить brython() после загрузки содержимого документа:

// init_brython.js
document.addEventListener('DOMContentLoaded', function () {
    brython();
});

Наконец, вы можете увидеть основную логику этого приложения в следующем коде Python:

# popup.py
from browser import document, prompt

def hello(evt):
    default = "Real Python"
    name = prompt("Enter your name:", default)
    if not name:
        name = default
    document["hello"].innerHTML = f"Hello, {name}!"

document["hello-btn"].bind("click", hello)

После этого вы готовы приступить к установке и тестированию, как и для расширения Chrome для JavaScript.

Тестирование и отладка Brython    ↑

В настоящее время нет удобных библиотек для модульного тестирования кода Brython. По мере развития Brython вы будете видеть больше возможностей для тестирования и отладки кода Python в браузере. Можно воспользоваться фреймворком модульного тестирования Python для автономного модуля Python, который можно использовать вне браузера. В браузере с драйверами браузера Selenium - хороший вариант. Отладка тоже ограничена, но возможна.

Модульные тесты Python    ↑

Фреймворки модульного тестирования Python, такие как встроенный unittest и pytest, не работают в браузере. Вы можете использовать эти фреймворки для модулей Python, которые также могут выполняться в контексте CPython. Никакие специфичные для Brython модули, такие как браузер, не могут быть протестированы с такими инструментами из командной строки. Для получения дополнительной информации о модульном тестировании Python, ознакомьтесь с Getting Started With Testing in Python.

Selnium    ↑

Selenium - это фреймворк для автоматизации браузеров. Он не зависит от языка, используемого в браузере, будь то JavaScript, Elm, Wasm или Brython, потому что он использует концепцию WebDriver, чтобы вести себя как пользователь, взаимодействующий с браузером. Вы можете посмотреть Modern Web Automation With Python и Selenium для получения дополнительной информации об этой структуре.

Модульные тесты JavaScript    ↑

Существует множество фреймворков для тестирования, ориентированных на JavaScript, таких как Mocha, Jasmine и QUnit, которые хорошо работают в полной экосистеме JavaScript. Но они не обязательно хорошо подходят для модульного тестирования кода Python, работающего в браузере. Один из вариантов требует глобального доступа к функциям Brython для JavaScript, что противоречит лучшим практикам. Чтобы проиллюстрировать вариант предоставления функции Brython для JavaScript, вы воспользуетесь QUnit, набором модульных тестов JavaScript, который может выполняться автономно в файле HTML:

<!-- index.html -->
<!DOCTYPE html >
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Test Suite</title>
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.13.0.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js"></script>
  <script src="https://code.jquery.com/qunit/qunit-2.13.0.js"></script>
</head>

<body onload="brython()">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script type="text/python">
from browser import window

def python_add(a, b):
  return a + b

window.py_add = python_add
</script>

<script>
const js_add = (a, b) => a + b;
QUnit.module('js_add_test', function() {
  QUnit.test('should add two numbers', function(assert) {
    assert.equal(js_add(1, 1), 2, '1 + 1 = 2 (javascript');
  });
});

QUnit.module('py_add_test', function() {
  QUnit.test('should add two numbers in Brython', function(assert) {
    assert.equal(py_add(2, 3), 5, '2 + 3 = 5 (python)');
  });
});

QUnit.module('py_add_failed_test', function() {
  QUnit.test('should add two numbers in Brython (failure)', function(assert) {
    assert.equal(py_add(2, 3), 6, '2 + 3 != 6 (python)');
  });
});
</script>

</body>
</html>

В одном файле HTML вы написали код Python, код JavaScript и тесты JavaScript для проверки функций на обоих языках, выполняемых в браузере:

  • Строка 11 импортирует фреймворк QUnit.
  • Строка 23 представляет python_add() для JavaScript.
  • Строка 28 определяет js_add_test для тестирования функции JavaScript js_add().
  • Строка 34 определяет py_add_test для проверки функции Python python_add().
  • Строка 40 определяет py_add_failed_test для проверки функции Python python_add() с ошибкой.

Для выполнения модульного теста не нужно запускать веб-сервер. Откройте index.html в браузере, и вы должны увидеть следующее:

На странице показаны два успешных теста, js_add_test() и py_add_test(), и один неудачный тест, py_add_failed_test().

Представление функции Python для JavaScript показывает, как можно использовать среду модульного тестирования JavaScript для выполнения Python в браузере. Хотя это возможно для тестирования, в целом не рекомендуется, поскольку может конфликтовать с существующими именами JavaScript.

Отладка в Brython    ↑

На момент написания этой статьи не существовало удобных инструментов для отладки вашего приложения Brython. Вы не смогли сгенерировать файл исходной карты, который позволил бы вам выполнять отладку шаг за шагом в инструментах разработки браузера.

Это не должно мешать вам использовать Brython. Вот несколько советов, которые помогут с отладкой и устранением неполадок вашего кода Brython:

  • Используйте print() или browser.console.log() для печати значений переменных в консоли инструментов разработчика браузера.
  • Используйте отладку с помощью f-строки, как описано в разделе «Новые интересные функции в Python 3.8».
  • Время от времени очищайте IndexedDB браузера с помощью инструментов разработчика.
  • Отключите кеш браузера во время разработки, установив флажок «Отключить кеш» на вкладке «Сеть» в инструментах разработчика браузера.
  • Добавьте параметры в brython(), чтобы включить отображение дополнительной отладочной информации в консоли JavaScript.
  • Скопируйте brython.js и brython_stdlib.min.js локально, чтобы ускорить перезагрузку во время разработки.
  • Запустите локальный сервер при импорте кода Python.
  • Откройте инспектор из расширения при устранении неполадок расширения Chrome.

Одна из тонкостей Python - это REPL (read-eval-print loop). Онлайн-консоль Brython предлагает платформу для экспериментов, тестирования и отладки некоторых фрагментов кода.

Изучение альтернатив Brython

Brython - не единственный вариант написания кода Python в браузере. Доступны несколько альтернатив:

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

Как сравнить эти фреймворки?

Skulpt    ↑

Skulpt компилирует код Python в JavaScript в браузере. Компиляция происходит после загрузки страницы, тогда как в Brython компиляция происходит во время загрузки страницы.

Хотя у него нет встроенных функций для управления DOM, Skulpt очень близок к Brython в своих возможностях. Он включает в себя использование в образовательных целях и полно‑функциональное приложения Python, как показано у Anvil.

Skulpt - это проект с солидной поддержкой и направлен в сторону Python 3. Brython в основном соответствует CPython 3.9 для модулей, совместимых с выполнением в браузере.

Transcrypt    ↑

Transcrypt включает инструмент командной строки для компиляции кода Python в код JavaScript. Компиляция считается опережающей (AOT). Полученный код затем можно загрузить в браузер. Transcrypt занимает мало места, около 100 КБ. Это быстро и поддерживает манипуляции с DOM. Отличие от Skulpt и Brython заключается в том, что Transcrypt компилируется в JavaScript перед загрузкой и использованием в браузере с помощью компилятора Transcrypt, что обеспечивает скорость и небольшой размер. Однако, при этом невозможно использовать Transcrypt в качестве платформы для обучения, как и другие платформы.

Pyodide    ↑

Pyodide - это компиляция интерпретатора CPython на WebAssembly. Он интерпретирует код Python в браузере. Фаза компиляции JavaScript отсутствует. Хотя Pyodide, как и PyPy.js, требует загрузки значительного объема данных, он поставляется с такими научными библиотеками, как NumPy, Pandas, Matplotlib и другими. Pyodide может напоминать среду Jupyter Notebook, полностью работающую в браузере, а не обслуживаемую внутренним сервером. Вы можете поэкспериментировать с Pyodide, используя живые примеры.

PyPy.js    ↑

PyPy.js использует интерпретатор Python PyPy, скомпилированный в JavaScript с помощью emscripten, что делает его совместимым для работы в браузере.

На текущий момент проекта PyPy.js, представляющий собой большой пакет, около 10 МБ, что совершенно недопустимо для типичных веб-приложений, не развивается. Однако, PyPy.js все еще можно использовать в качестве платформы для изучения Python в браузере, открыв домашнюю страницу PyPy.js.

PyPy.js компилируется в JavaScript с помощью emscripten. Pyodide делает еще один шаг вперед, используя emscripten и Wasm, в частности, для компиляции расширений Python C, таких как NumPy, в WebAssembly.

На момент написания этой статьи PyPy.js не поддерживался. Что-то в том же духе относительно процесса компиляции рассмотрим Pyodide.

Заключение    ↑

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

В этом руководстве вы узнали, как:

  • установить и использовать Brython в локальной среде;
  • заменить JavaScript на Python в своих интерфейсных веб-приложениях;
  • управлять DOM;
  • взаимодействовать с JavaScript;
  • создавать расширения для браузера;
  • сравнивать альтернативы Brython.

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

По мотивам Brython: Python in Your Browser

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *