Eel é uma pequena biblioteca Python para criar aplicativos HTML/JS GUI offline simples do tipo Electron, com acesso total aos recursos e bibliotecas Python.
Eel hospeda um servidor web local e permite anotar funções em Python para que possam ser chamadas de Javascript e vice-versa.
O Eel foi projetado para eliminar o incômodo de escrever aplicativos GUI curtos e simples. Se você estiver familiarizado com Python e desenvolvimento web, provavelmente pule para este exemplo que seleciona nomes de arquivos aleatórios de uma determinada pasta (algo que é impossível em um navegador).
Existem várias opções para criar aplicativos GUI em Python, mas se você quiser usar HTML/JS (para usar jQueryUI ou Bootstrap, por exemplo), geralmente precisará escrever muito código padrão para se comunicar com o cliente (Javascript ) para o lado do servidor (Python).
O equivalente Python mais próximo do Electron (que eu saiba) é o cefpython. É um pouco pesado para o que eu queria.
O Eel não é tão completo quanto o Electron ou o cefpython - provavelmente não é adequado para criar aplicativos completos como o Atom - mas é muito adequado para tornar a GUI equivalente a pequenos scripts utilitários que você usa internamente em sua equipe.
Por alguma razão, muitas das melhores bibliotecas de processamento de números e matemática estão em Python (Tensorflow, Numpy, Scipy etc), mas muitas das melhores bibliotecas de visualização estão em Javascript (D3, THREE.js etc). Esperançosamente, o Eel facilita a combinação deles em aplicativos utilitários simples para auxiliar no seu desenvolvimento.
Junte-se aos usuários e mantenedores do Eel no Discord, se quiser.
Instale a partir do pypi com pip
:
pip install eel
Para incluir suporte para modelos HTML, atualmente usando Jinja2:
pip install eel[jinja2]
Um aplicativo Eel será dividido em um frontend composto por vários arquivos de tecnologia da web (.html, .js, .css) e um backend composto por vários scripts Python.
Todos os arquivos frontend devem ser colocados em um único diretório (eles podem ser divididos em pastas dentro deste, se necessário).
my_python_script.py <-- Python scripts
other_python_module.py
static_web_folder/ <-- Web folder
main_page.html
css/
style.css
img/
logo.png
Suponha que você coloque todos os arquivos frontend em um diretório chamado web
, incluindo sua página inicial main.html
, então o aplicativo é iniciado assim;
import eel
eel . init ( 'web' )
eel . start ( 'main.html' )
Isso iniciará um servidor web com as configurações padrão (http://localhost:8000) e abrirá um navegador em http://localhost:8000/main.html.
Se o Chrome ou o Chromium estiverem instalados, por padrão, ele será aberto no modo de aplicativo (com o sinalizador --app
cmdline), independentemente de como o navegador padrão do sistema operacional está definido (é possível substituir esse comportamento).
Opções adicionais podem ser passadas para eel.start()
como argumentos de palavras-chave.
Algumas das opções incluem o modo em que o aplicativo está (por exemplo, 'chrome'), a porta em que o aplicativo é executado, o nome do host do aplicativo e a adição de sinalizadores de linha de comando adicionais.
A partir do Eel v0.12.0, as seguintes opções estão disponíveis para start()
:
'chrome'
, 'electron'
, 'edge'
, 'msie'
, 'custom'
). Também pode ser None
ou False
para não abrir uma janela. Padrão: 'chrome'
'localhost'
)0
para que a porta seja escolhida automaticamente. Padrão: 8000
.start()
deve ou não bloquear o thread de chamada. Padrão: True
my_templates
. Padrão: None
eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])
. Padrão: []
None
None
{'size': (200, 100), 'position': (300, 50)}
. Padrão: {}None
app
não for uma instância Bottle, você precisará chamar eel.register_eel_routes(app)
em sua instância de aplicativo personalizada.shutdown_delay
segundos e então verifica se agora há alguma conexão de websocket. Caso contrário, a Eel fecha. Caso o usuário tenha fechado o navegador e queira sair do programa. Por padrão, o valor de shutdown_delay é 1.0
segundo Além dos arquivos da pasta frontend, uma biblioteca Javascript será veiculada em /eel.js
. Você deve incluir isso em todas as páginas:
< script type =" text/javascript " src =" /eel.js " > </ script >
A inclusão desta biblioteca cria um objeto eel
que pode ser usado para se comunicar com o lado Python.
Quaisquer funções no código Python decoradas com @eel.expose
assim...
@ eel . expose
def my_python_function ( a , b ):
print ( a , b , a + b )
...aparecerão como métodos no objeto eel
no lado Javascript, assim...
console . log ( "Calling Python..." ) ;
eel . my_python_function ( 1 , 2 ) ; // This calls the Python function that was decorated
Da mesma forma, quaisquer funções Javascript expostas assim...
eel . expose ( my_javascript_function ) ;
function my_javascript_function ( a , b , c , d ) {
if ( a < b ) {
console . log ( c * d ) ;
}
}
pode ser chamado do lado do Python assim...
print ( 'Calling Javascript...' )
eel . my_javascript_function ( 1 , 2 , 3 , 4 ) # This calls the Javascript function
O nome exposto também pode ser substituído passando um segundo argumento. Se seu aplicativo minimiza o JavaScript durante as compilações, isso pode ser necessário para garantir que as funções possam ser resolvidas no lado do Python:
eel . expose ( someFunction , "my_javascript_function" ) ;
Ao passar objetos complexos como argumentos, lembre-se de que internamente eles são convertidos para JSON e enviados para um websocket (um processo que potencialmente perde informações).
Veja o exemplo completo em: exemplos/01 - hello_world
Juntando tudo isso em um Hello, World! por exemplo, temos uma página HTML curta, 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 >
e um pequeno script 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)
Se executarmos o script Python ( python hello.py
), uma janela do navegador será aberta exibindo hello.html
e veremos...
Hello from Python World!
Hello from Javascript World!
...no terminal e...
Hello from Javascript World!
Hello from Python World!
...no console do navegador (pressione F12 para abrir).
Você notará que no código Python, a função Javascript é chamada antes mesmo de a janela do navegador ser iniciada - quaisquer chamadas iniciais como essa são enfileiradas e enviadas assim que o websocket for estabelecido.
Embora queiramos pensar em nosso código como compreendendo um único aplicativo, o interpretador Python e a janela do navegador são executados em processos separados. Isso pode tornar a comunicação entre eles um pouco complicada, especialmente se sempre tivermos que enviar valores explicitamente de um lado para o outro.
O Eel oferece suporte a duas maneiras de recuperar valores de retorno do outro lado do aplicativo, o que ajuda a manter o código conciso.
Para evitar travar para sempre no lado do Python, um tempo limite foi implementado para tentar recuperar valores do lado do JavaScript, cujo padrão é 10.000 milissegundos (10 segundos). Isso pode ser alterado com o parâmetro _js_result_timeout
para eel.init
. Não há tempo limite correspondente no lado do JavaScript.
Ao chamar uma função exposta, você pode passar imediatamente uma função de retorno de chamada posteriormente. Este retorno de chamada será chamado automaticamente de forma assíncrona com o valor de retorno quando a função terminar de ser executada no outro lado.
Por exemplo, se tivermos a seguinte função definida e exposta em Javascript:
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
Então, em Python, podemos recuperar valores aleatórios do lado Javascript assim:
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 exatamente da mesma forma ao contrário).
Na maioria das situações, as chamadas para o outro lado são para recuperar rapidamente alguns dados, como o estado de um widget ou o conteúdo de um campo de entrada. Nesses casos, é mais conveniente esperar alguns milissegundos de forma síncrona e depois continuar com seu código, em vez de dividir tudo em retornos de chamada.
Para recuperar de forma síncrona o valor de retorno, simplesmente não passe nada para o segundo conjunto de colchetes. Então em Python escreveríamos:
n = eel . js_random ()() # This immediately returns the value
print ( 'Got this from Javascript:' , n )
Você só pode realizar retornos síncronos depois que a janela do navegador for iniciada (depois de chamar eel.start()
), caso contrário, obviamente, a chamada será interrompida.
Em Javascript, a linguagem não nos permite bloquear enquanto esperamos por um retorno de chamada, exceto usando await
de dentro de uma função async
. Portanto, o código equivalente do lado Javascript seria:
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 é construído em Bottle e Gevent, que fornecem um loop de eventos assíncrono semelhante ao Javascript. Muitas bibliotecas padrão do Python assumem implicitamente que há um único thread de execução - para lidar com isso, Gevent pode "remendar macacos" muitos dos módulos padrão, como time
. Este patch de macaco é feito automaticamente quando você chama . Se você precisar de patch de macaco, import eel
import gevent.monkey
e chame gevent.monkey.patch_all()
antes de import eel
. O patch do Monkey pode interferir em coisas como depuradores, portanto deve ser evitado, a menos que seja necessário.
Na maioria dos casos, você deve evitar usar time.sleep()
e, em vez disso, usar as versões fornecidas por gevent
. Por conveniência, os dois métodos gevent mais comumente necessários, sleep()
e spawn()
são fornecidos diretamente do Eel (para economizar time
de importação e/ou gevent
também).
Neste exemplo...
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()
...teríamos então três "threads" (greenlets) em execução;
my_other_thread
, imprimindo repetidamente "Sou um tópico"while
final, imprimindo repetidamente "Eu sou um loop principal" Se quiser empacotar seu aplicativo em um programa que possa ser executado em um computador sem um interpretador Python instalado, você deve usar PyInstaller .
pip install PyInstaller
python -m eel [your_main_script] [your_web_folder]
(por exemplo, você pode executar python -m eel hello.py web
)dist/
--exclude module_name
. Por exemplo, você pode executar python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography
--onefile --noconsole
para criar um único arquivo executávelConsulte a documentação do PyInstaller para mais opções.
Para usuários do Windows 10, o Microsoft Edge ( eel.start(.., mode='edge')
) é instalado por padrão e é um substituto útil se um navegador preferencial não estiver instalado. Veja os exemplos: