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
不是 Bottle 实例,则需要在自定义应用程序实例上调用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')
),如果未安装首选浏览器,则这是一个有用的备用浏览器。请参阅示例: