Eel — это небольшая библиотека Python для создания простых автономных приложений с графическим интерфейсом HTML/JS, подобных Electron, с полным доступом к возможностям и библиотекам Python.
Eel размещает локальный веб-сервер, а затем позволяет аннотировать функции Python, чтобы их можно было вызывать из Javascript, и наоборот.
Eel создан для того, чтобы облегчить написание коротких и простых приложений с графическим интерфейсом. Если вы знакомы с Python и веб-разработкой, возможно, просто перейдите к этому примеру, который выбирает случайные имена файлов из заданной папки (что невозможно в браузере).
Существует несколько вариантов создания приложений с графическим интерфейсом на Python, но если вы хотите использовать HTML/JS (например, для использования jQueryUI или Bootstrap), то вам обычно приходится писать много шаблонного кода для связи с клиентом (Javascript ) на сторону сервера (Python).
Насколько мне известно, ближайшим эквивалентом Electron в Python является cefpython. Это немного тяжеловато для того, что я хотел.
Eel не так полноценен, как Electron или cefpython — он, вероятно, не подходит для создания полноценных приложений, таких как Atom, — но он очень подходит для создания графического интерфейса, эквивалентного небольшим служебным сценариям, которые вы используете внутри своей команды.
По какой-то причине многие из лучших в своем классе библиотек для обработки чисел и математических вычислений написаны на Python (Tensorflow, Numpy, Scipy и т. д.), но многие из лучших библиотек визуализации — на Javascript (D3, THREE.js и т. д.). Надеемся, что Eel позволит легко объединить их в простые служебные приложения, которые помогут вам в разработке.
Присоединяйтесь к пользователям и сопровождающим Eel в Discord, если хотите.
Установите из pypi с помощью pip
:
pip install eel
Чтобы включить поддержку шаблонов HTML, в настоящее время использующих Jinja2:
pip install eel[jinja2]
Приложение Eel будет разделено на интерфейс, состоящий из различных файлов веб-технологий (.html, .js, .css), и серверную часть, состоящую из различных скриптов Python.
Все файлы внешнего интерфейса должны быть помещены в один каталог (при необходимости их можно разделить внутри него на папки).
my_python_script.py <-- Python scripts
other_python_module.py
static_web_folder/ <-- Web folder
main_page.html
css/
style.css
img/
logo.png
Предположим, вы поместили все файлы внешнего интерфейса в каталог с именем web
, включая стартовую страницу main.html
, тогда приложение запускается следующим образом;
import eel
eel . init ( 'web' )
eel . start ( 'main.html' )
Это запустит веб-сервер с настройками по умолчанию (http://localhost:8000) и откроет браузер по адресу http://localhost:8000/main.html.
Если установлен Chrome или Chromium, то по умолчанию он откроется в режиме приложения (с флагом --app
cmdline), независимо от того, какой браузер ОС установлен по умолчанию (это поведение можно переопределить).
Дополнительные параметры могут быть переданы в eel.start()
в качестве аргументов ключевого слова.
Некоторые из параметров включают режим, в котором находится приложение (например, «chrome»), порт, на котором работает приложение, имя хоста приложения и добавление дополнительных флагов командной строки.
Начиная с Eel v0.12.0, для start()
доступны следующие параметры:
'chrome'
, 'electron'
, 'edge'
, 'msie'
, 'custom'
). Также может быть None
или False
чтобы не открывать окно. По умолчанию: 'chrome'
'localhost'
)0
, чтобы порт выбирался автоматически. По умолчанию: 8000
.start()
блокировать вызывающий поток. По умолчанию: True
my_templates
. По умолчанию: None
eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])
. По умолчанию: []
None
None
{'size': (200, 100), 'position': (300, 50)}
. По умолчанию: {}None
app
не является экземпляром Bottle, вам нужно будет вызвать eel.register_eel_routes(app)
в вашем пользовательском экземпляре приложения.shutdown_delay
секунд, а затем проверяет, есть ли теперь какие-либо соединения с веб-сокетами. Если нет, то Eel закрывается. В случае, если пользователь закрыл браузер и хочет выйти из программы. По умолчанию значение Shutdown_delay составляет 1.0
секунды. В дополнение к файлам в папке внешнего интерфейса в /eel.js
будет размещена библиотека Javascript. Вы должны включить это на все страницы:
< script type =" text/javascript " src =" /eel.js " > </ script >
Включение этой библиотеки создает объект eel
, который можно использовать для взаимодействия со стороной Python.
Любые функции в коде Python, украшенные @eel.expose
вроде этого...
@ eel . expose
def my_python_function ( a , b ):
print ( a , b , a + b )
...будут отображаться как методы объекта eel
на стороне Javascript, вот так...
console . log ( "Calling Python..." ) ;
eel . my_python_function ( 1 , 2 ) ; // This calls the Python function that was decorated
Аналогично, любые функции Javascript, которые отображаются следующим образом...
eel . expose ( my_javascript_function ) ;
function my_javascript_function ( a , b , c , d ) {
if ( a < b ) {
console . log ( c * d ) ;
}
}
можно вызвать со стороны Python вот так...
print ( 'Calling Javascript...' )
eel . my_javascript_function ( 1 , 2 , 3 , 4 ) # This calls the Javascript function
Открытое имя также можно переопределить, передав второй аргумент. Если ваше приложение минимизирует JavaScript во время сборки, это может быть необходимо, чтобы обеспечить возможность разрешения функций на стороне Python:
eel . expose ( someFunction , "my_javascript_function" ) ;
При передаче сложных объектов в качестве аргументов имейте в виду, что внутри они преобразуются в JSON и отправляются через веб-сокет (процесс, который потенциально теряет информацию).
Полный пример см. в: example/01 - hello_world.
Объединив все это в Hello, World! Например, у нас есть короткая HTML-страница web/hello.html
:
<!DOCTYPE html >
< html >
< head >
< title > Hello, World! </ title >
<!-- Include eel.js - note this file doesn't exist in the 'web' directory -->
< script type =" text/javascript " src =" /eel.js " > </ script >
< script type =" text/javascript " >
eel . expose ( say_hello_js ) ; // Expose this function to Python
function say_hello_js ( x ) {
console . log ( "Hello from " + x ) ;
}
say_hello_js ( "Javascript World!" ) ;
eel . say_hello_py ( "Javascript World!" ) ; // Call a Python function
</ script >
</ head >
< body >
Hello, World!
</ body >
</ html >
и короткий скрипт Python hello.py
:
import eel
# Set web files folder and optionally specify which file types to check for eel.expose()
# *Default allowed_extensions are: ['.js', '.html', '.txt', '.htm', '.xhtml']
eel . init ( 'web' , allowed_extensions = [ '.js' , '.html' ])
@ eel . expose # Expose this function to Javascript
def say_hello_py ( x ):
print ( 'Hello from %s' % x )
say_hello_py ( 'Python World!' )
eel . say_hello_js ( 'Python World!' ) # Call a Javascript function
eel . start ( 'hello.html' ) # Start (this blocks and enters loop)
Если мы запустим скрипт Python ( python hello.py
), откроется окно браузера, отображающее hello.html
, и мы увидим...
Hello from Python World!
Hello from Javascript World!
...в терминале и...
Hello from Javascript World!
Hello from Python World!
... в консоли браузера (чтобы открыть, нажмите F12).
Вы заметите, что в коде Python функция Javascript вызывается еще до запуска окна браузера — любые ранние вызовы, подобные этому, ставятся в очередь и затем отправляются после установления веб-сокета.
Хотя мы хотим думать, что наш код состоит из одного приложения, интерпретатор Python и окно браузера выполняются в отдельных процессах. Это может затруднить обмен данными между ними, особенно если нам всегда приходилось явно отправлять значения с одной стороны на другую.
Eel поддерживает два способа получения возвращаемых значений с другой стороны приложения, что помогает сохранить краткость кода.
Чтобы предотвратить вечное зависание на стороне Python, был установлен тайм-аут для попытки получить значения со стороны JavaScript, который по умолчанию равен 10 000 миллисекунд (10 секунд). Это можно изменить с помощью параметра _js_result_timeout
на eel.init
. На стороне JavaScript соответствующего тайм-аута нет.
Когда вы вызываете открытую функцию, вы можете сразу же после этого передать функцию обратного вызова. Этот обратный вызов будет автоматически вызываться асинхронно с возвращаемым значением, когда функция завершит выполнение на другой стороне.
Например, если у нас есть следующая функция, определенная и представленная в Javascript:
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
Затем в Python мы можем получить случайные значения со стороны Javascript следующим образом:
def print_num ( n ):
print ( 'Got this from Javascript:' , n )
# Call Javascript function, and pass explicit callback function
eel . js_random ()( print_num )
# Do the same with an inline lambda as callback
eel . js_random ()( lambda n : print ( 'Got this from Javascript:' , n ))
(В обратную сторону это работает точно так же).
В большинстве ситуаций вызовы другой стороны предназначены для быстрого получения некоторой части данных, например состояния виджета или содержимого поля ввода. В этих случаях удобнее просто синхронно подождать несколько миллисекунд, а затем продолжить работу с кодом, а не разбивать все это на обратные вызовы.
Чтобы синхронно получить возвращаемое значение, просто ничего не передавайте во второй набор скобок. Итак, в Python мы бы написали:
n = eel . js_random ()() # This immediately returns the value
print ( 'Got this from Javascript:' , n )
Вы можете выполнять синхронные возвраты только после запуска окна браузера (после вызова eel.start()
), в противном случае вызов, очевидно, зависнет.
В Javascript язык не позволяет нам блокировать время ожидания обратного вызова, за исключением использования await
внутри async
функции. Таким образом, эквивалентный код со стороны Javascript будет:
async function run ( ) {
// Inside a function marked 'async' we can use the 'await' keyword.
let n = await eel . py_random ( ) ( ) ; // Must prefix call with 'await', otherwise it's the same syntax
console . log ( "Got this from Python: " + n ) ;
}
run ( ) ;
Eel построен на Bottle и Gevent, которые обеспечивают асинхронный цикл событий, аналогичный Javascript. Многие стандартные библиотеки Python неявно предполагают наличие единственного потока выполнения — чтобы справиться с этим, Gevent может «исправить» многие стандартные модули, такие как time
. Это исправление обезьяны выполняется автоматически, когда вы вызываете . Если вам нужно исправление обезьяны, вам следует import eel
import gevent.monkey
и вызвать gevent.monkey.patch_all()
перед import eel
. Исправления обезьян могут мешать работе таких вещей, как отладчики, поэтому их следует избегать, если в этом нет необходимости.
В большинстве случаев все будет в порядке, если вы избежите использования time.sleep()
и вместо этого будете использовать версии, предоставленные gevent
. Для удобства два наиболее часто используемых метода gevent, sleep()
и spawn()
предоставляются непосредственно из Eel (чтобы также сэкономить time
на импорт и/или gevent
).
В этом примере...
import eel
eel . init ( 'web' )
def my_other_thread ():
while True :
print ( "I'm a thread" )
eel . sleep ( 1.0 ) # Use eel.sleep(), not time.sleep()
eel . spawn ( my_other_thread )
eel . start ( 'main.html' , block = False ) # Don't block on this call
while True :
print ( "I'm a main loop" )
eel . sleep ( 1.0 ) # Use eel.sleep(), not time.sleep()
... тогда у нас будет работать три «потока» (гринлета);
my_other_thread
, неоднократно печатающий «Я поток»while
и постоянно печатает «Я основной цикл». Если вы хотите упаковать свое приложение в программу, которую можно будет запускать на компьютере без установленного интерпретатора Python, вам следует использовать PyInstaller .
pip install PyInstaller
python -m eel [your_main_script] [your_web_folder]
(например, вы можете запустить python -m eel hello.py web
).dist/
--exclude module_name
. Например, вы можете запустить python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography
--onefile --noconsole
чтобы создать один исполняемый файл.Дополнительные параметры см. в документации PyInstaller.
Для пользователей Windows 10 Microsoft Edge ( eel.start(.., mode='edge')
) устанавливается по умолчанию и является полезным запасным вариантом, если предпочтительный браузер не установлен. Посмотрите примеры: