Eel es una pequeña biblioteca de Python para crear aplicaciones GUI HTML/JS sin conexión simples similares a Electron, con acceso completo a las capacidades y bibliotecas de Python.
Eel aloja un servidor web local y luego le permite anotar funciones en Python para que puedan llamarse desde Javascript y viceversa.
Eel está diseñado para eliminar la molestia de escribir aplicaciones GUI breves y sencillas. Si está familiarizado con Python y el desarrollo web, probablemente simplemente vaya a este ejemplo que selecciona nombres de archivos aleatorios de la carpeta dada (algo que es imposible desde un navegador).
Hay varias opciones para crear aplicaciones GUI en Python, pero si desea usar HTML/JS (para usar jQueryUI o Bootstrap, por ejemplo), generalmente tendrá que escribir una gran cantidad de código repetitivo para comunicarse desde el Cliente (Javascript). ) al lado del servidor (Python).
El equivalente Python más cercano a Electron (que yo sepa) es cefpython. Pesa un poco para lo que quería.
Eel no es tan completo como Electron o cefpython; probablemente no sea adecuado para crear aplicaciones completas como Atom, pero es muy adecuado para crear una GUI equivalente a pequeños scripts de utilidad que usa internamente en su equipo.
Por alguna razón, muchas de las mejores bibliotecas de cálculo numérico y matemáticas de su clase están en Python (Tensorflow, Numpy, Scipy, etc.), pero muchas de las mejores bibliotecas de visualización están en Javascript (D3, THREE.js, etc.). Con suerte, Eel facilita la combinación de estos en aplicaciones de utilidad simples para ayudarlo en su desarrollo.
Únase a los usuarios y mantenedores de Eel en Discord, si lo desea.
Instalar desde pypi con pip
:
pip install eel
Para incluir soporte para plantillas HTML, actualmente usando Jinja2:
pip install eel[jinja2]
Una aplicación Eel se dividirá en una interfaz que consta de varios archivos de tecnología web (.html, .js, .css) y un backend que consta de varios scripts de Python.
Todos los archivos de la interfaz deben colocarse en un solo directorio (se pueden dividir en carpetas dentro de este si es necesario).
my_python_script.py <-- Python scripts
other_python_module.py
static_web_folder/ <-- Web folder
main_page.html
css/
style.css
img/
logo.png
Supongamos que coloca todos los archivos frontend en un directorio llamado web
, incluida su página de inicio main.html
, luego la aplicación se inicia así;
import eel
eel . init ( 'web' )
eel . start ( 'main.html' )
Esto iniciará un servidor web con la configuración predeterminada (http://localhost:8000) y abrirá un navegador en http://localhost:8000/main.html.
Si Chrome o Chromium están instalados, de forma predeterminada se abrirá en el modo de aplicación (con el indicador --app
cmdline), independientemente de cuál esté configurado el navegador predeterminado del sistema operativo (es posible anular este comportamiento).
Se pueden pasar opciones adicionales a eel.start()
como argumentos de palabras clave.
Algunas de las opciones incluyen el modo en el que se encuentra la aplicación (por ejemplo, 'chrome'), el puerto en el que se ejecuta la aplicación, el nombre de host de la aplicación y agregar indicadores de línea de comando adicionales.
A partir de Eel v0.12.0, las siguientes opciones están disponibles para start()
:
'chrome'
, 'electron'
, 'edge'
, 'msie'
, 'custom'
). También puede ser None
o False
para no abrir una ventana. Valor predeterminado: 'chrome'
'localhost'
)0
para que el puerto se seleccione automáticamente. Predeterminado: 8000
.start()
debe bloquear o no el hilo de llamada. Valor predeterminado: True
my_templates
. Predeterminado: None
eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])
. Por defecto: []
None
None
{'size': (200, 100), 'position': (300, 50)}
. Por defecto: {}None
app
no es una instancia de Bottle, deberá llamar a eel.register_eel_routes(app)
en su instancia de aplicación personalizada.shutdown_delay
segundos y luego verifica si ahora hay conexiones de websocket. Si no, entonces Eel cierra. En caso de que el usuario haya cerrado el navegador y quiera salir del programa. De forma predeterminada, el valor de Shutdown_Delay es 1.0
segundo. Además de los archivos en la carpeta frontend, se proporcionará una biblioteca Javascript en /eel.js
. Debes incluir esto en cualquier página:
< script type =" text/javascript " src =" /eel.js " > </ script >
La inclusión de esta biblioteca crea un objeto eel
que se puede utilizar para comunicarse con el lado de Python.
Cualquier función en el código Python que esté decorada con @eel.expose
como esta...
@ eel . expose
def my_python_function ( a , b ):
print ( a , b , a + b )
...aparecerán como métodos en el objeto eel
en el lado de Javascript, así...
console . log ( "Calling Python..." ) ;
eel . my_python_function ( 1 , 2 ) ; // This calls the Python function that was decorated
De manera similar, cualquier función de Javascript que esté expuesta de esta manera...
eel . expose ( my_javascript_function ) ;
function my_javascript_function ( a , b , c , d ) {
if ( a < b ) {
console . log ( c * d ) ;
}
}
se puede llamar desde el lado de Python de esta manera...
print ( 'Calling Javascript...' )
eel . my_javascript_function ( 1 , 2 , 3 , 4 ) # This calls the Javascript function
El nombre expuesto también se puede anular pasando un segundo argumento. Si su aplicación minimiza JavaScript durante las compilaciones, esto puede ser necesario para garantizar que las funciones se puedan resolver en el lado de Python:
eel . expose ( someFunction , "my_javascript_function" ) ;
Al pasar objetos complejos como argumentos, tenga en cuenta que internamente se convierten a JSON y se envían a un websocket (un proceso que potencialmente pierde información).
Ver ejemplo completo en: ejemplos/01 - hello_world
Poniendo esto junto en un ¡Hola, mundo! Por ejemplo, tenemos una página HTML corta, 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 >
y un breve script de 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)
Si ejecutamos el script Python ( python hello.py
), se abrirá una ventana del navegador que mostrará hello.html
y veremos...
Hello from Python World!
Hello from Javascript World!
...en la terminal, y...
Hello from Javascript World!
Hello from Python World!
...en la consola del navegador (presione F12 para abrir).
Notará que en el código Python, la función Javascript se llama incluso antes de que se inicie la ventana del navegador; cualquier llamada temprana como esta se pone en cola y luego se envía una vez que se ha establecido el websocket.
Si bien queremos pensar que nuestro código comprende una sola aplicación, el intérprete de Python y la ventana del navegador se ejecutan en procesos separados. Esto puede hacer que la comunicación entre ellos sea un poco complicada, especialmente si siempre tuviéramos que enviar valores explícitamente de un lado al otro.
Eel admite dos formas de recuperar valores de retorno del otro lado de la aplicación, lo que ayuda a mantener el código conciso.
Para evitar que se quede colgado para siempre en el lado de Python, se ha establecido un tiempo de espera para intentar recuperar valores del lado de JavaScript, cuyo valor predeterminado es 10000 milisegundos (10 segundos). Esto se puede cambiar con el parámetro _js_result_timeout
a eel.init
. No hay un tiempo de espera correspondiente en el lado de JavaScript.
Cuando llama a una función expuesta, puede pasar inmediatamente una función de devolución de llamada. Esta devolución de llamada se llamará automáticamente de forma asincrónica con el valor de retorno cuando la función haya terminado de ejecutarse en el otro lado.
Por ejemplo, si tenemos la siguiente función definida y expuesta en Javascript:
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
Luego, en Python podemos recuperar valores aleatorios del lado de Javascript de esta manera:
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 ))
(Funciona exactamente igual al revés).
En la mayoría de las situaciones, las llamadas al otro lado son para recuperar rápidamente algún dato, como el estado de un widget o el contenido de un campo de entrada. En estos casos, es más conveniente esperar unos milisegundos sincrónicamente y luego continuar con el código, en lugar de dividir todo en devoluciones de llamada.
Para recuperar sincrónicamente el valor de retorno, simplemente no pase nada al segundo conjunto de corchetes. Entonces en Python escribiríamos:
n = eel . js_random ()() # This immediately returns the value
print ( 'Got this from Javascript:' , n )
Solo puede realizar retornos sincrónicos después de que se haya iniciado la ventana del navegador (después de llamar eel.start()
); de lo contrario, obviamente, la llamada se bloqueará.
En Javascript, el lenguaje no nos permite bloquear mientras esperamos una devolución de llamada, excepto usando await
desde dentro de una función async
. Entonces el código equivalente desde el lado de Javascript sería:
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 se basa en Bottle y Gevent, que proporcionan un bucle de eventos asincrónico similar a Javascript. Gran parte de la biblioteca estándar de Python supone implícitamente que hay un único subproceso de ejecución; para solucionar esto, Gevent puede "parchear" muchos de los módulos estándar, como time
. Este parche de mono se realiza automáticamente cuando llamas . Si necesita parches para monos, debe import eel
import gevent.monkey
y llamar a gevent.monkey.patch_all()
antes de import eel
. Los parches de mono pueden interferir con cosas como los depuradores, por lo que deben evitarse a menos que sea necesario.
En la mayoría de los casos, debería estar bien evitando el uso de time.sleep()
y, en su lugar, utilizando las versiones proporcionadas por gevent
. Para mayor comodidad, los dos métodos gevent más comúnmente necesarios, sleep()
y spawn()
se proporcionan directamente desde Eel (para ahorrar time
de importación y/o gevent
también).
En este ejemplo...
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()
...entonces tendríamos tres "hilos" (greenlets) ejecutándose;
my_other_thread
, que imprime repetidamente "Soy un hilo"while
final, imprimiendo repetidamente "Soy un bucle principal" Si desea empaquetar su aplicación en un programa que pueda ejecutarse en una computadora sin un intérprete de Python instalado, debe usar PyInstaller .
pip install PyInstaller
python -m eel [your_main_script] [your_web_folder]
(por ejemplo, puedes ejecutar python -m eel hello.py web
)dist/
--exclude module_name
. Por ejemplo, puede ejecutar python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography
--onefile --noconsole
para crear un único archivo ejecutable.Consulte la documentación de PyInstaller para obtener más opciones.
Para los usuarios de Windows 10, Microsoft Edge ( eel.start(.., mode='edge')
) está instalado de forma predeterminada y es un recurso útil si no está instalado un navegador preferido. Vea los ejemplos: