Eel เป็นไลบรารี Python เล็กๆ สำหรับสร้างแอป HTML/JS GUI ออฟไลน์ที่เหมือนอิเล็กตรอนแบบง่ายๆ พร้อมสิทธิ์เข้าถึงความสามารถและไลบรารีของ Python อย่างเต็มรูปแบบ
Eel โฮสต์เว็บเซิร์ฟเวอร์ในพื้นที่ จากนั้นให้คุณใส่คำอธิบายประกอบฟังก์ชันใน Python เพื่อให้สามารถเรียกใช้จาก Javascript และในทางกลับกัน
Eel ได้รับการออกแบบมาเพื่อขจัดความยุ่งยากในการเขียนแอปพลิเคชัน GUI ที่สั้นและเรียบง่าย หากคุณคุ้นเคยกับ Python และการพัฒนาเว็บ คุณอาจข้ามไปที่ตัวอย่างนี้ซึ่งเลือกชื่อไฟล์แบบสุ่มจากโฟลเดอร์ที่กำหนด (สิ่งที่เป็นไปไม่ได้จากเบราว์เซอร์)
มีหลายตัวเลือกสำหรับการสร้างแอป GUI ใน Python แต่ถ้าคุณต้องการใช้ HTML/JS (เพื่อใช้ jQueryUI หรือ Bootstrap เป็นต้น) โดยทั่วไปแล้ว คุณจะต้องเขียนโค้ดสำเร็จรูปจำนวนมากเพื่อสื่อสารจากไคลเอนต์ (Javascript ) ไปทางด้านเซิร์ฟเวอร์ (Python)
Python ที่ใกล้เคียงที่สุดที่เทียบเท่ากับ Electron (ตามความรู้ของฉัน) คือ cefpython มันหนักนิดหน่อยสำหรับสิ่งที่ฉันต้องการ
Eel ไม่ได้มีคุณสมบัติครบถ้วนเท่ากับ Electron หรือ cefpython - อาจไม่เหมาะสำหรับการสร้างแอปพลิเคชันเต็มรูปแบบเช่น Atom - แต่เหมาะมากสำหรับการสร้าง GUI ที่เทียบเท่ากับสคริปต์ยูทิลิตี้เล็กๆ น้อยๆ ที่คุณใช้ภายในในทีมของคุณ
ด้วยเหตุผลบางประการ ไลบรารี่การกระทืบตัวเลขและคณิตศาสตร์ที่ดีที่สุดในชั้นเรียนจำนวนมากจึงอยู่ใน Python (Tensorflow, Numpy, Scipy ฯลฯ) แต่ไลบรารีการแสดงภาพที่ดีที่สุดหลายแห่งอยู่ใน Javascript (D3, THREE.js เป็นต้น) หวังว่า Eel จะทำให้การรวมสิ่งเหล่านี้เข้ากับแอพยูทิลิตี้ง่ายๆ เพื่อช่วยเหลือการพัฒนาของคุณเป็นเรื่องง่าย
เข้าร่วมผู้ใช้และผู้ดูแลของ Eel บน Discord หากคุณต้องการ
ติดตั้งจาก pypi ด้วย pip
:
pip install eel
หากต้องการรวมการสนับสนุนเทมเพลต HTML ซึ่งปัจจุบันใช้ Jinja2:
pip install eel[jinja2]
แอปพลิเคชัน Eel จะถูกแบ่งออกเป็นส่วนหน้าซึ่งประกอบด้วยไฟล์เทคโนโลยีเว็บต่างๆ (.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 ใด ๆ หรือไม่ ถ้าไม่เช่นนั้นปลาไหลจะปิด ในกรณีที่ผู้ใช้ปิดเบราว์เซอร์แล้วต้องการออกจากโปรแกรม ตามค่าเริ่มต้น ค่าของ shutdown_delay คือ 1.0
วินาที นอกเหนือจากไฟล์ในโฟลเดอร์ส่วนหน้าแล้ว ไลบรารี Javascript จะให้บริการที่ /eel.js
คุณควรรวมสิ่งนี้ไว้ในหน้าใดๆ:
< 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 )
...จะปรากฏเป็นวิธีการบนวัตถุ eel
ฝั่ง Javascript เช่นนี้...
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 ซึ่งมีค่าเริ่มต้นอยู่ที่ 10,000 มิลลิวินาที (10 วินาที) สิ่งนี้สามารถเปลี่ยนแปลงได้ด้วยพารามิเตอร์ _js_result_timeout
เป็น eel.init
ไม่มีการหมดเวลาที่สอดคล้องกันในด้าน 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 ภาษาไม่อนุญาตให้เราบล็อกในขณะที่เรารอการติดต่อกลับ ยกเว้นโดยใช้ await
จากภายในฟังก์ชัน async
ดังนั้นโค้ดที่เทียบเท่าจากฝั่ง 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 gevent.monkey
และโทร gevent.monkey.patch_all()
ก่อนที่ จะ import eel
การแพตช์ลิงอาจรบกวนการทำงานของดีบักเกอร์ ดังนั้นควรหลีกเลี่ยงเว้นแต่จำเป็น
ในกรณีส่วนใหญ่ คุณควรหลีกเลี่ยงการใช้ time.sleep()
และใช้เวอร์ชันที่ gevent
ให้มาแทน เพื่อความสะดวก วิธีการ gevent ที่จำเป็นที่สุดสองวิธีคือ sleep()
และ spawn()
จะได้รับโดยตรงจาก Eel (เพื่อประหยัด 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()
...จากนั้นเราจะมี "เธรด" (กรีนเล็ต) สามเธรดทำงานอยู่;
my_other_thread
พิมพ์ซ้ำๆ ว่า "I'm a 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
flags เพื่อสร้างไฟล์ปฏิบัติการไฟล์เดียวศึกษาเอกสารประกอบสำหรับ PyInstaller สำหรับตัวเลือกเพิ่มเติม
สำหรับผู้ใช้ Windows 10 Microsoft Edge ( eel.start(.., mode='edge')
) จะได้รับการติดตั้งตามค่าเริ่มต้น และเป็นทางเลือกที่มีประโยชน์หากไม่ได้ติดตั้งเบราว์เซอร์ที่ต้องการ ดูตัวอย่าง: