Eel ist eine kleine Python-Bibliothek zum Erstellen einfacher Electron-ähnlicher Offline-HTML/JS-GUI-Apps mit vollem Zugriff auf Python-Funktionen und -Bibliotheken.
Eel hostet einen lokalen Webserver und ermöglicht Ihnen dann, Funktionen in Python mit Anmerkungen zu versehen, sodass sie von Javascript aus aufgerufen werden können und umgekehrt.
Eel wurde entwickelt, um das Schreiben kurzer und einfacher GUI-Anwendungen zu vereinfachen. Wenn Sie mit Python und der Webentwicklung vertraut sind, springen Sie wahrscheinlich einfach zu diesem Beispiel, das zufällige Dateinamen aus dem angegebenen Ordner auswählt (etwas, das in einem Browser unmöglich ist).
Es gibt mehrere Möglichkeiten, GUI-Apps in Python zu erstellen. Wenn Sie jedoch HTML/JS verwenden möchten (z. B. um jQueryUI oder Bootstrap zu verwenden), müssen Sie im Allgemeinen eine Menge Boilerplate-Code schreiben, um vom Client aus zu kommunizieren (Javascript). )-Seite zur Serverseite (Python).
Das Python-Äquivalent, das Electron am nächsten kommt (meines Wissens), ist Cefpython. Für das, was ich wollte, ist es etwas schwer.
Eel ist nicht so ausgereift wie Electron oder Cefpython – es eignet sich wahrscheinlich nicht zum Erstellen vollständiger Anwendungen wie Atom – aber es eignet sich sehr gut zum Erstellen des GUI-Äquivalents kleiner Dienstprogrammskripte, die Sie intern in Ihrem Team verwenden.
Aus irgendeinem Grund sind viele der besten Bibliotheken für Zahlenverarbeitung und Mathematik in Python (Tensorflow, Numpy, Scipy usw.), aber viele der besten Visualisierungsbibliotheken sind in Javascript (D3, THREE.js usw.). Hoffentlich macht es Eel einfach, diese in einfache Hilfs-Apps zu kombinieren, um Ihre Entwicklung zu unterstützen.
Treten Sie den Benutzern und Betreuern von Eel auf Discord bei, wenn Sie möchten.
Von Pypi mit pip
installieren:
pip install eel
Um Unterstützung für HTML-Vorlagen einzuschließen, verwenden Sie derzeit Jinja2:
pip install eel[jinja2]
Eine Eel-Anwendung wird in ein Frontend, das aus verschiedenen Webtechnologiedateien (.html, .js, .css) besteht, und ein Backend, das aus verschiedenen Python-Skripten besteht, aufgeteilt.
Alle Frontend-Dateien sollten in einem einzigen Verzeichnis abgelegt werden (sie können darin bei Bedarf weiter in Ordner unterteilt werden).
my_python_script.py <-- Python scripts
other_python_module.py
static_web_folder/ <-- Web folder
main_page.html
css/
style.css
img/
logo.png
Angenommen, Sie legen alle Frontend-Dateien in einem Verzeichnis namens web
ab, einschließlich Ihrer Startseite main.html
, dann wird die App wie folgt gestartet;
import eel
eel . init ( 'web' )
eel . start ( 'main.html' )
Dadurch wird ein Webserver mit den Standardeinstellungen (http://localhost:8000) gestartet und ein Browser für http://localhost:8000/main.html geöffnet.
Wenn Chrome oder Chromium installiert ist, wird es standardmäßig im App-Modus (mit dem Befehlszeilen-Flag --app
) geöffnet, unabhängig davon, auf welche Standardbrowsereinstellung das Betriebssystem eingestellt ist (dieses Verhalten kann überschrieben werden).
Zusätzliche Optionen können als Schlüsselwortargumente an eel.start()
übergeben werden.
Zu den Optionen gehören der Modus, in dem sich die App befindet (z. B. „Chrome“), der Port, auf dem die App ausgeführt wird, der Hostname der App und das Hinzufügen zusätzlicher Befehlszeilenflags.
Ab Eel v0.12.0 stehen für start()
die folgenden Optionen zur Verfügung:
'chrome'
, 'electron'
, 'edge'
, 'msie'
, 'custom'
). Kann auch None
oder False
sein, um ein Fenster nicht zu öffnen. Standard: 'chrome'
'localhost'
)0
damit der Port automatisch ausgewählt wird. Standard: 8000
.start()
den aufrufenden Thread blockieren soll oder nicht. Standard: True
my_templates
. Standard: None
eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])
. Standard: []
None
None
{'size': (200, 100), 'position': (300, 50)}
sein. Standard: {}None
app
keine Bottle-Instanz ist, müssen Sie eel.register_eel_routes(app)
auf Ihrer benutzerdefinierten App-Instanz aufrufen.shutdown_delay
-Sekunden und prüft dann, ob jetzt WebSocket-Verbindungen bestehen. Wenn nicht, wird Eel geschlossen. Für den Fall, dass der Benutzer den Browser geschlossen hat und das Programm beenden möchte. Standardmäßig beträgt der Wert von „shutdown_delay“ 1.0
Sekunden Zusätzlich zu den Dateien im Frontend-Ordner wird eine Javascript-Bibliothek unter /eel.js
bereitgestellt. Sie sollten dies auf allen Seiten angeben:
< script type =" text/javascript " src =" /eel.js " > </ script >
Durch das Einbinden dieser Bibliothek wird ein eel
-Objekt erstellt, das zur Kommunikation mit der Python-Seite verwendet werden kann.
Alle Funktionen im Python-Code, die mit @eel.expose
wie folgt dekoriert sind ...
@ eel . expose
def my_python_function ( a , b ):
print ( a , b , a + b )
... werden als Methoden auf dem eel
-Objekt auf der Javascript-Seite angezeigt, wie folgt ...
console . log ( "Calling Python..." ) ;
eel . my_python_function ( 1 , 2 ) ; // This calls the Python function that was decorated
Ebenso alle Javascript-Funktionen, die so verfügbar gemacht werden ...
eel . expose ( my_javascript_function ) ;
function my_javascript_function ( a , b , c , d ) {
if ( a < b ) {
console . log ( c * d ) ;
}
}
kann von der Python-Seite aus wie folgt aufgerufen werden ...
print ( 'Calling Javascript...' )
eel . my_javascript_function ( 1 , 2 , 3 , 4 ) # This calls the Javascript function
Der verfügbar gemachte Name kann auch durch die Übergabe eines zweiten Arguments überschrieben werden. Wenn Ihre App JavaScript während der Erstellung minimiert, kann dies erforderlich sein, um sicherzustellen, dass Funktionen auf der Python-Seite aufgelöst werden können:
eel . expose ( someFunction , "my_javascript_function" ) ;
Bedenken Sie bei der Übergabe komplexer Objekte als Argumente, dass diese intern in JSON konvertiert und über einen Websocket gesendet werden (ein Prozess, bei dem möglicherweise Informationen verloren gehen).
Das vollständige Beispiel finden Sie unter: examples/01 – hello_world
Füge dies zu einem Hello, World! zusammen. Beispiel: Wir haben eine kurze HTML-Seite, 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 >
und ein kurzes Python-Skript 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)
Wenn wir das Python-Skript ( python hello.py
) ausführen, öffnet sich ein Browserfenster mit der Anzeige hello.html
und wir sehen ...
Hello from Python World!
Hello from Javascript World!
...im Terminal, und...
Hello from Javascript World!
Hello from Python World!
...in der Browserkonsole (zum Öffnen F12 drücken).
Sie werden feststellen, dass im Python-Code die Javascript-Funktion aufgerufen wird, bevor das Browserfenster überhaupt gestartet wird – alle frühen Aufrufe wie dieser werden in die Warteschlange gestellt und dann gesendet, sobald der Websocket eingerichtet wurde.
Während wir uns unseren Code als eine einzelne Anwendung vorstellen möchten, werden der Python-Interpreter und das Browserfenster in separaten Prozessen ausgeführt. Dies kann die Kommunikation zwischen ihnen etwas kompliziert machen, insbesondere wenn wir immer explizit Werte von einer Seite zur anderen senden mussten.
Eel unterstützt zwei Möglichkeiten zum Abrufen von Rückgabewerten von der anderen Seite der App, was dazu beiträgt, den Code prägnant zu halten.
Um ein ewiges Hängenbleiben auf der Python-Seite zu verhindern, wurde für den Versuch, Werte von der JavaScript-Seite abzurufen, ein Zeitlimit eingerichtet, das standardmäßig 10.000 Millisekunden (10 Sekunden) beträgt. Dies kann mit dem Parameter _js_result_timeout
in eel.init
geändert werden. Auf der JavaScript-Seite gibt es kein entsprechendes Timeout.
Wenn Sie eine exponierte Funktion aufrufen, können Sie anschließend sofort eine Rückruffunktion übergeben. Dieser Rückruf wird automatisch asynchron mit dem Rückgabewert aufgerufen, wenn die Ausführung der Funktion auf der anderen Seite abgeschlossen ist.
Wenn wir beispielsweise die folgende Funktion in Javascript definiert und verfügbar gemacht haben:
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
Dann können wir in Python Zufallswerte von der Javascript-Seite abrufen, etwa so:
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 ))
(Umgekehrt funktioniert es genauso).
In den meisten Situationen dienen die Aufrufe der Gegenseite dazu, schnell bestimmte Daten abzurufen, beispielsweise den Status eines Widgets oder den Inhalt eines Eingabefelds. In diesen Fällen ist es bequemer, einfach synchron ein paar Millisekunden zu warten und dann mit dem Code fortzufahren, anstatt das Ganze in Rückrufe aufzuteilen.
Um den Rückgabewert synchron abzurufen, übergeben Sie einfach nichts an den zweiten Klammersatz. In Python würden wir also schreiben:
n = eel . js_random ()() # This immediately returns the value
print ( 'Got this from Javascript:' , n )
Sie können synchrone Rückgaben nur durchführen, nachdem das Browserfenster gestartet wurde (nach dem Aufruf von eel.start()
), andernfalls bleibt der Aufruf offensichtlich hängen.
In Javascript erlaubt uns die Sprache nicht, zu blockieren, während wir auf einen Rückruf warten, außer durch die Verwendung von await
innerhalb einer async
Funktion. Der entsprechende Code von der Javascript-Seite wäre also:
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 basiert auf Bottle und Gevent, die eine asynchrone Ereignisschleife ähnlich wie Javascript bereitstellen. Viele der Standardbibliotheken von Python gehen implizit davon aus, dass es einen einzelnen Ausführungsthread gibt. Um dies zu bewältigen, kann Gevent viele der Standardmodule, wie z. B. time
mit einem Affen-Patch versehen. Dieses Monkey-Patching erfolgt automatisch, wenn Sie . Wenn Sie Monkey-Patches benötigen, sollten Sie import eel
aufrufenimport gevent.monkey
und gevent.monkey.patch_all()
aufrufen, bevor Sie import eel
. Monkey-Patching kann Dinge wie Debugger beeinträchtigen und sollte daher vermieden werden, es sei denn, es ist notwendig.
In den meisten Fällen sollten Sie die Verwendung von time.sleep()
vermeiden und stattdessen die von gevent
bereitgestellten Versionen verwenden. Der Einfachheit halber werden die beiden am häufigsten benötigten Gevent-Methoden, sleep()
und spawn()
direkt von Eel bereitgestellt (um time
und/oder auch gevent
zu sparen).
In diesem Beispiel...
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()
...wir würden dann drei „Threads“ (Greenlets) laufen lassen;
my_other_thread
-Methode gibt wiederholt „Ich bin ein Thread“ auswhile
-Schleife stecken bleibt und wiederholt „Ich bin eine Hauptschleife“ ausgibt. Wenn Sie Ihre App in ein Programm packen möchten, das auf einem Computer ohne installierten Python-Interpreter ausgeführt werden kann, sollten Sie PyInstaller verwenden.
pip install PyInstaller
python -m eel [your_main_script] [your_web_folder]
aus (Sie könnten beispielsweise python -m eel hello.py web
ausführen).dist/
erstellt.--exclude module_name
. Beispielsweise könnten Sie python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography
ausführen--onefile --noconsole
hinzu, um eine einzelne ausführbare Datei zu erstellenWeitere Optionen finden Sie in der Dokumentation zu PyInstaller.
Für Windows 10-Benutzer ist Microsoft Edge ( eel.start(.., mode='edge')
) standardmäßig installiert und ein nützlicher Ersatz, wenn kein bevorzugter Browser installiert ist. Sehen Sie sich die Beispiele an: