Eel adalah pustaka Python kecil untuk membuat aplikasi GUI HTML/JS offline sederhana seperti Electron, dengan akses penuh ke kemampuan dan pustaka Python.
Eel menghosting server web lokal, lalu memungkinkan Anda membuat anotasi fungsi dengan Python sehingga dapat dipanggil dari Javascript, dan sebaliknya.
Eel dirancang untuk menghilangkan kerumitan penulisan aplikasi GUI yang singkat dan sederhana. Jika Anda familiar dengan Python dan pengembangan web, mungkin langsung saja ke contoh ini yang mengambil nama file acak dari folder tertentu (sesuatu yang tidak mungkin dilakukan dari browser).
Ada beberapa pilihan untuk membuat aplikasi GUI dengan Python, tetapi jika Anda ingin menggunakan HTML/JS (untuk menggunakan jQueryUI atau Bootstrap, misalnya) maka biasanya Anda harus menulis banyak kode boilerplate untuk berkomunikasi dari Klien (Javascript ) sisi ke sisi Server (Python).
Setara Python terdekat dengan Electron (sepengetahuan saya) adalah cefpython. Ini agak berat untuk apa yang saya inginkan.
Eel tidak sepenuhnya lengkap seperti Electron atau cefpython - mungkin tidak cocok untuk membuat aplikasi lengkap seperti Atom - tetapi sangat cocok untuk membuat GUI setara dengan skrip utilitas kecil yang Anda gunakan secara internal di tim Anda.
Untuk beberapa alasan, banyak pustaka penghitungan angka dan matematika terbaik di kelasnya menggunakan Python (Tensorflow, Numpy, Scipy, dll) tetapi banyak pustaka visualisasi terbaik menggunakan Javascript (D3, THREE.js, dll). Semoga Eel memudahkan penggabungan ini menjadi aplikasi utilitas sederhana untuk membantu pengembangan Anda.
Bergabunglah dengan pengguna dan pengelola Eel di Discord, jika Anda mau.
Instal dari pypi dengan pip
:
pip install eel
Untuk menyertakan dukungan untuk templating HTML, saat ini menggunakan Jinja2:
pip install eel[jinja2]
Aplikasi Eel akan dipecah menjadi frontend yang terdiri dari berbagai file teknologi web (.html, .js, .css) dan backend yang terdiri dari berbagai skrip Python.
Semua file frontend harus diletakkan dalam satu direktori (dapat dibagi lagi menjadi beberapa folder di dalamnya jika perlu).
my_python_script.py <-- Python scripts
other_python_module.py
static_web_folder/ <-- Web folder
main_page.html
css/
style.css
img/
logo.png
Misalkan Anda meletakkan semua file frontend dalam direktori bernama web
, termasuk halaman awal main.html
, maka aplikasi dimulai seperti ini;
import eel
eel . init ( 'web' )
eel . start ( 'main.html' )
Ini akan memulai server web pada pengaturan default (http://localhost:8000) dan membuka browser ke http://localhost:8000/main.html.
Jika Chrome atau Chromium diinstal, maka secara default Chrome atau Chromium akan terbuka di Mode Aplikasi (dengan tanda --app
cmdline), terlepas dari browser default OS yang disetel (perilaku ini dapat dikesampingkan).
Opsi tambahan dapat diteruskan ke eel.start()
sebagai argumen kata kunci.
Beberapa opsi mencakup mode aplikasi (misalnya 'chrome'), port tempat aplikasi dijalankan, nama host aplikasi, dan menambahkan tanda baris perintah tambahan.
Pada Eel v0.12.0, opsi berikut tersedia untuk start()
:
'chrome'
, 'electron'
, 'edge'
, 'msie'
, 'custom'
). Bisa juga None
atau False
untuk tidak membuka jendela. Bawaan: 'chrome'
'localhost'
)0
agar port dipilih secara otomatis. Bawaan: 8000
.start()
harus memblokir thread panggilan atau tidak. Bawaan: True
my_templates
. Bawaan: None
eel.start('main.html', mode='chrome-app', port=8080, cmdline_args=['--start-fullscreen', '--browser-startup-dialog'])
. Bawaan: []
None
None
{'size': (200, 100), 'position': (300, 50)}
. Bawaan: {}None
app
Anda bukan instance Bottle, Anda perlu memanggil eel.register_eel_routes(app)
pada instance aplikasi kustom Anda.shutdown_delay
beberapa detik, lalu memeriksa apakah sekarang ada koneksi soket web. Jika tidak, maka Belut tutup. Jika pengguna telah menutup browser dan ingin keluar dari program. Secara default, nilai shutdown_delay adalah 1.0
detik Selain file di folder frontend, perpustakaan Javascript akan disajikan di /eel.js
. Anda harus menyertakan ini di halaman mana pun:
< script type =" text/javascript " src =" /eel.js " > </ script >
Termasuk perpustakaan ini membuat objek eel
yang dapat digunakan untuk berkomunikasi dengan pihak Python.
Fungsi apa pun dalam kode Python yang dihiasi dengan @eel.expose
seperti ini...
@ eel . expose
def my_python_function ( a , b ):
print ( a , b , a + b )
...akan muncul sebagai metode pada objek eel
di sisi Javascript, seperti ini...
console . log ( "Calling Python..." ) ;
eel . my_python_function ( 1 , 2 ) ; // This calls the Python function that was decorated
Demikian pula, fungsi Javascript apa pun yang diekspos seperti ini...
eel . expose ( my_javascript_function ) ;
function my_javascript_function ( a , b , c , d ) {
if ( a < b ) {
console . log ( c * d ) ;
}
}
dapat dipanggil dari sisi Python seperti ini...
print ( 'Calling Javascript...' )
eel . my_javascript_function ( 1 , 2 , 3 , 4 ) # This calls the Javascript function
Nama yang terekspos juga dapat diganti dengan meneruskan argumen kedua. Jika aplikasi Anda mengecilkan JavaScript selama build, hal ini mungkin diperlukan untuk memastikan bahwa fungsi dapat diselesaikan di sisi Python:
eel . expose ( someFunction , "my_javascript_function" ) ;
Saat meneruskan objek kompleks sebagai argumen, ingatlah bahwa secara internal objek tersebut dikonversi ke JSON dan mengirimkan soket web (sebuah proses yang berpotensi kehilangan informasi).
Lihat contoh lengkap di: contoh/01 - hello_world
Menggabungkannya menjadi Hello, World! Misalnya, kita mempunyai halaman HTML pendek, 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 >
dan skrip Python pendek 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)
Jika kita menjalankan skrip Python ( python hello.py
), maka jendela browser akan terbuka menampilkan hello.html
, dan kita akan melihat...
Hello from Python World!
Hello from Javascript World!
...di terminal, dan...
Hello from Javascript World!
Hello from Python World!
...di konsol browser (tekan F12 untuk membuka).
Anda akan melihat bahwa dalam kode Python, fungsi Javascript dipanggil bahkan sebelum jendela browser dimulai - setiap panggilan awal seperti ini akan dimasukkan ke dalam antrian dan kemudian dikirim setelah soket web telah dibuat.
Meskipun kami ingin menganggap kode kami terdiri dari satu aplikasi, penerjemah Python dan jendela browser berjalan dalam proses terpisah. Hal ini dapat membuat komunikasi bolak-balik di antara mereka menjadi sedikit berantakan, terutama jika kita selalu harus mengirimkan nilai secara eksplisit dari satu sisi ke sisi lain.
Eel mendukung dua cara mengambil nilai kembalian dari sisi lain aplikasi, yang membantu menjaga kode tetap ringkas.
Untuk mencegah hang selamanya di sisi Python, batas waktu telah ditetapkan untuk mencoba mengambil nilai dari sisi JavaScript, yang defaultnya adalah 10.000 milidetik (10 detik). Ini dapat diubah dengan parameter _js_result_timeout
menjadi eel.init
. Tidak ada batas waktu yang sesuai di sisi JavaScript.
Saat Anda memanggil fungsi yang terekspos, Anda dapat langsung meneruskan fungsi panggilan balik setelahnya. Callback ini secara otomatis akan dipanggil secara asinkron dengan nilai yang dikembalikan ketika fungsi telah selesai dijalankan di sisi lain.
Misalnya, jika kita mendefinisikan dan mengekspos fungsi berikut dalam Javascript:
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
Kemudian dengan Python kita dapat mengambil nilai acak dari sisi Javascript seperti:
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 ))
(Ini bekerja persis sama sebaliknya).
Dalam sebagian besar situasi, panggilan ke pihak lain adalah untuk mengambil beberapa bagian data dengan cepat, seperti status widget atau konten kolom input. Dalam kasus ini akan lebih mudah untuk menunggu beberapa milidetik secara sinkron lalu melanjutkan dengan kode Anda, daripada memecah semuanya menjadi callback.
Untuk mengambil nilai kembalian secara sinkron, cukup berikan apa pun ke kumpulan tanda kurung kedua. Jadi dengan Python kita akan menulis:
n = eel . js_random ()() # This immediately returns the value
print ( 'Got this from Javascript:' , n )
Anda hanya dapat melakukan pengembalian sinkron setelah jendela browser dimulai (setelah memanggil eel.start()
), jika tidak, jelas panggilan akan terhenti.
Dalam Javascript, bahasanya tidak mengizinkan kita memblokir saat kita menunggu panggilan balik, kecuali dengan menggunakan await
dari dalam fungsi async
. Jadi kode yang setara dari sisi Javascript adalah:
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 dibangun di atas Bottle dan Gevent, yang menyediakan loop peristiwa asinkron yang mirip dengan Javascript. Banyak pustaka standar Python yang secara implisit mengasumsikan ada satu thread eksekusi - untuk mengatasi hal ini, Gevent dapat "menambal monyet" banyak modul standar seperti time
. Patch monyet ini dilakukan secara otomatis saat Anda memanggil . Jika Anda memerlukan patching monyet, Anda harus import eel
import gevent.monkey
dan menelepon gevent.monkey.patch_all()
sebelum Anda import eel
. Monkey patching dapat mengganggu hal-hal seperti debugger sehingga harus dihindari kecuali diperlukan.
Untuk sebagian besar kasus, Anda akan baik-baik saja dengan menghindari penggunaan time.sleep()
dan sebagai gantinya menggunakan versi yang disediakan oleh gevent
. Untuk kenyamanan, dua metode gevent yang paling umum dibutuhkan, sleep()
dan spawn()
disediakan langsung dari Eel (untuk menghemat time
impor dan/atau gevent
juga).
Dalam contoh ini...
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()
...kita kemudian akan menjalankan tiga "utas" (greenlets);
my_other_thread
, berulang kali mencetak "Saya adalah thread"while
terakhir, berulang kali mencetak "Saya loop utama" Jika Anda ingin mengemas aplikasi Anda ke dalam program yang dapat dijalankan di komputer tanpa terinstal penerjemah Python, Anda harus menggunakan PyInstaller .
pip install PyInstaller
python -m eel [your_main_script] [your_web_folder]
(misalnya, Anda mungkin menjalankan python -m eel hello.py web
)dist/
--exclude module_name
. Misalnya, Anda mungkin menjalankan python -m eel file_access.py web --exclude win32com --exclude numpy --exclude cryptography
--onefile --noconsole
untuk membuat satu file yang dapat dieksekusiKonsultasikan dokumentasi untuk PyInstaller untuk opsi lebih lanjut.
Untuk pengguna Windows 10, Microsoft Edge ( eel.start(.., mode='edge')
) diinstal secara default dan merupakan cadangan yang berguna jika browser pilihan tidak diinstal. Lihat contohnya: