Eel 是一個小型 Python 庫,用於製作簡單的類似 Electron 的離線 HTML/JS GUI 應用程序,可以完全存取 Python 功能和庫。
Eel 託管一個本機網頁伺服器,然後讓您在 Python 中註解函數,以便可以從 Javascript 呼叫它們,反之亦然。
Eel 旨在消除編寫簡短的 GUI 應用程式的麻煩。如果您熟悉 Python 和 Web 開發,可能只需跳至此範例,該範例從給定資料夾中選取隨機檔案名稱(這在瀏覽器中是不可能的)。
在 Python 中製作 GUI 應用程式有多種選擇,但如果您想使用 HTML/JS(例如,為了使用 jQueryUI 或 Bootstrap),那麼您通常必須編寫大量樣板程式碼來與客戶端進行通訊(Javascript ) 端到伺服器(Python) 端。
(據我所知)與 Electron 最接近的 Python 等效項是 cefpython。對於我想要的東西來說,它的重量有點重。
Eel 不像 Electron 或 cefpython 那樣成熟 - 它可能不適合製作像 Atom 這樣的成熟應用程式 - 但它非常適合製作相當於您團隊內部使用的小型實用程式腳本的 GUI。
由於某種原因,許多一流的數位處理和數學函式庫都是用 Python(Tensorflow、Numpy、Scipy 等)編寫的,但許多最好的視覺化函式庫都是用 Javascript(D3、THREE.js 等)編寫的。希望 Eel 能夠輕鬆地將這些組合到簡單的實用應用程式中,以幫助您的開發。
如果您願意,請加入 Discord 上的 Eel 使用者和維護者。
使用pip
從 pypi 安裝:
pip install eel
要包含對 HTML 模板的支持,目前使用 Jinja2:
pip install eel[jinja2]
Eel 應用程式將分為由各種 Web 技術檔案(.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
驗證等eel.register_eel_routes(app)
shutdown_delay
秒,然後檢查現在是否有任何 websocket 連線。如果沒有,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 )
...將作為方法出現在 Javascript 端的eel
物件上,就像這樣...
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 並透過 websocket 發送(這個過程可能會遺失資訊)。
請參閱完整範例:examples/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 函數在瀏覽器視窗啟動之前就被呼叫 - 任何像這樣的早期呼叫都會排隊,然後在建立 Websocket 後發送。
雖然我們希望將程式碼視為包含單一應用程序,但 Python 解釋器和瀏覽器視窗在單獨的進程中運行。這可能會使它們之間的來回通訊變得有點混亂,特別是如果我們總是必須明確地將值從一側發送到另一側。
Eel 支援兩種從應用程式另一端檢索傳回值的方法,這有助於保持程式碼簡潔。
為了防止在 Python 端永遠掛起,嘗試從 JavaScript 端檢索值時設定了逾時,預設為 10000 毫秒(10 秒)。這可以透過eel.init
的_js_result_timeout
參數進行更改。 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 中,語言不允許我們在等待回呼時阻塞,除非在async
函數內部使用await
。因此 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 eel
之前import gevent.monkey
並呼叫gevent.monkey.patch_all()
。猴子補丁可能會幹擾調試器之類的東西,因此除非必要,否則應該避免。
在大多數情況下,避免使用time.sleep()
並使用gevent
提供的版本應該沒問題。為了方便起見,Eel 直接提供了兩個最常用的 gevent 方法sleep()
和spawn()
(以節省導入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()
……然後我們將運行三個「線程」(greenlet);
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')
),如果未安裝首選瀏覽器,則這是一個有用的備用瀏覽器。請參閱範例: