Eel هي مكتبة Python صغيرة لإنشاء تطبيقات واجهة المستخدم الرسومية HTML/JS غير المتصلة بالإنترنت البسيطة، مع إمكانية الوصول الكامل إلى إمكانات ومكتبات Python.
يستضيف Eel خادم ويب محليًا، ثم يتيح لك إضافة تعليقات توضيحية إلى الوظائف في Python بحيث يمكن استدعاؤها من Javascript، والعكس صحيح.
تم تصميم Eel للتخلص من متاعب كتابة تطبيقات واجهة المستخدم الرسومية القصيرة والبسيطة. إذا كنت معتادًا على لغة Python وتطوير الويب، فمن المحتمل أن تنتقل إلى هذا المثال الذي يختار أسماء ملفات عشوائية من المجلد المحدد (وهو أمر مستحيل من المتصفح).
هناك العديد من الخيارات لإنشاء تطبيقات واجهة المستخدم الرسومية في Python، ولكن إذا كنت تريد استخدام HTML/JS (من أجل استخدام jQueryUI أو Bootstrap، على سبيل المثال)، فسيتعين عليك عمومًا كتابة الكثير من التعليمات البرمجية المعيارية للتواصل من العميل (Javascript ) بجانب جانب الخادم (Python).
أقرب ما يعادل بايثون للإلكترون (على حد علمي) هو cefpython. إنه وزن ثقيل بعض الشيء بالنسبة لما أردت.
إن Eel ليس مكتملًا مثل Electron أو cefpython - ربما لا يكون مناسبًا لإنشاء تطبيقات كاملة مثل Atom - ولكنه مناسب جدًا لجعل واجهة المستخدم الرسومية مكافئة للنصوص البرمجية الصغيرة المساعدة التي تستخدمها داخليًا في فريقك.
لسبب ما، توجد العديد من مكتبات معالجة الأعداد والرياضيات الأفضل في فئتها في 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
، ثم يتحقق مما إذا كان هناك الآن أي اتصالات لمقبس الويب. إذا لم يكن الأمر كذلك، فسيتم إغلاق Eel. في حالة قيام المستخدم بإغلاق المتصفح ورغبته في الخروج من البرنامج. افتراضيًا، تكون قيمة 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
من جانب جافا سكريبت، مثل هذا...
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 ) ;
}
}
يمكن استدعاؤها من جانب بايثون مثل هذا ...
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 (عملية من المحتمل أن تفقد المعلومات).
انظر المثال الكامل في: example/01 - 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 >
ونص بايثون قصير 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 ثوانٍ). يمكن تغيير ذلك باستخدام المعلمة _js_result_timeout
إلى eel.init
. لا توجد مهلة مقابلة على جانب JavaScript.
عند استدعاء وظيفة مكشوفة، يمكنك تمرير وظيفة رد الاتصال على الفور بعد ذلك. سيتم استدعاء رد الاتصال هذا تلقائيًا بشكل غير متزامن مع القيمة المرجعة عند انتهاء تنفيذ الوظيفة على الجانب الآخر.
على سبيل المثال، إذا كانت لدينا الوظيفة التالية محددة ومعروضة في Javascript:
eel . expose ( js_random ) ;
function js_random ( ) {
return Math . random ( ) ;
}
ثم في بايثون يمكننا استرداد القيم العشوائية من جانب جافا سكريبت كما يلي:
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 ))
(يعمل بنفس الطريقة وبالعكس).
في معظم الحالات، يكون الغرض من الاستدعاءات إلى الجانب الآخر هو استرداد بعض البيانات بسرعة، مثل حالة عنصر واجهة المستخدم أو محتويات حقل الإدخال. في هذه الحالات، من الأفضل الانتظار بشكل متزامن لبضعة ميلي ثانية ثم متابعة التعليمات البرمجية الخاصة بك، بدلاً من تقسيم الأمر برمته إلى عمليات رد اتصال.
لاسترداد القيمة المرجعة بشكل متزامن، ما عليك سوى تمرير أي شيء إلى المجموعة الثانية من الأقواس. لذلك في بايثون نكتب:
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. تفترض الكثير من مكتبات بايثون القياسية ضمنيًا وجود خيط تنفيذ واحد - للتعامل مع هذا، يمكن لـ 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
، طباعة عبارة "أنا خيط" بشكل متكرر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')
) بشكل افتراضي وهو بديل مفيد في حالة عدم تثبيت المتصفح المفضل. انظر الأمثلة: