تطبيقات لغة البايثون - تعلم كيف تبني إضافات plugins لبرنامج بايثون

تطبيقات لغة البايثون – تعلم كيف تبني إضافات plugins لبرنامج بايثون

تطبيقات لغة البايثون متعددة وكثيرة، ويُمكن الاستفادة منها في مجالات كثيرة. في هذا المقال نصطحبكم في بايثونات في رحلة تعلم كيفية عمل plugins لبرامج البايثون. كثيرًا ما نجد أنفسنا كمستخدمين لبرامج جاهزة – مهما كان تخصصها-  في أعمالنا اليومية نقف و قد نكون عاجزين عن إتمام مهمة ما باستخدام البرنامج على الرغم من احترافنا له. فنبدأ رحلة البحث عن الحل في الانترنت بين المنتديات المتخصصة وفيديوهات اليوتيوب وغيرها. في أغلب الأحيان  نجد الحل عبارة عن مجموعة كبيرة من الخطوات التتابعية المتسلسلة و نتيجة كل خطوة تعتمد اعتمادًا كليًا على نتيجة الإجراءات التي تمت من جميع ما قبلها والتي يجب أن تكون قد انتهت وبشكل صحيح ١٠٠٪ لنحصل بالنهاية على ما نريده كنتيجة نهائية. إنها لعملية متعبة و دقيقة وقد تستغرق وقتًا مهمًا كان طويلًا أو قصيرًا، خصوصا إذا كنا نحتاج إلى استخدام ذلك الحل بشكل كبير وقد يكون أساسي. في تلك اللحظة لابد لنا أن تساءلنا أنه لو كان هناك رز أو وظيفة تقوم بكل ذلك أوتوماتيكيا أو لو كان بمقدورنا أن نضيف وظيفة ما لبرنامجنا الذي نعمل عليه ليقوم بعمل ما بالشكل الذي نريد.

الحل في plugins

الحل لتلك الأسئلة هي الإضافات plugins والتي قد نجدها بالانترنت متاحة للجميع أو مدفوعة كما في حزمة برامج شركة أدوبي وأوتوديسك، حيث أن بعض هذه الإضافات تكون غالية الثمن بشكل كبير. الحل هنا أن بعض الشركات المصنعة للبرامج أتاحت للمستخدمين إمكانية بناء plugins خاصة بهم بأن جعلت برامجها قابلة للتطوير والإضافة scriptable بإتاحة API من البرنامج وإتاحة استخدام لغة برمجة دارجة أو خاصة بالبرنامج لعمل تلك الإضافات.

محرر مايا والبايثون
محرر مايا والبايثون
محرر النصوص في برنامج بلندر المفتوح المصدر
محرر النصوص في برنامج بلندر المفتوح المصدر

لنفترض أننا قمنا بعمل مشروع ما بلغة البايثون، والمشروع متكامل للاستخدام اليومي لأتمتة automate وظائف معينة. قد نود أن نُضيف أفكارًا جديدة لنخاع البرنامج كتطوير ولكن لانريد أن نبني إصدارًا جديدًا كليًا من البرنامج نفسة بمجرد إضافة وظيفة أو إثنتين. يمكن أن نُضيف plugins على مشروعنا دون الإخلال أو التدخل بتعديل نص البرنامج الرئيسي.  سيكون الأمر أسهل وأفضل وأسرع أليس كذلك؟ إضافة ميزة جعل البرنامج scriptable ستكون أكثر من رائعة.

قبل البدء

لنفهم أولًا ماذا نريد أن نعمل و كيف للبرنامج أن يعمل. عندما نكتب برنامجًا أو مشروعا  فإننا نقوم بكتابة وظائف functions متعددة تقوم كل منها بعمل ما ويتم استدعاؤها من البرنامج الرئيسي لتعمل مع بعضها بشكل متكامل يخدم الغرض من المشروع بشكل كامل.

نريد أن نجعل في مشروعنا ميزة  أن يكون فيه python shell or python interpreter بحيث يمكننا أن نقوم بإنشاء ملفات تحتوي نصوص برمجية بلغة بايثون ويمكنها استدعاء واستخدام دوال المشروع واستخدام نتائجها والتعديل عليها كما نريد، بل و اضافة وظائف جديدة خاصة بنا تستخدم تلك الأصلية كـ API .

تطبيقات لغة البايثون – برنامج بايثون يُشغل مُفسر بايثون

لنبدأ بكتابة برنامج صغير يمكننا من خلاله أن نشغل بايثون شل تفاعلي python interactive shell و تنفيذ تعابير بايثون ثم تطويره ليقبل ملفات بايثون كاملة. لن يكون للبرنامج إلا وظيفتان وهما إمكانية تشغيل شل تفاعلي من خلاله ثم تطويرة ليقبل ملفات كاملة. لاحقا إن شاء الله سنقوم بكتابة مشروع متكامل يحتوي على دوال عدة والتي سنقوم باستيرادها في سكريبتات الإضافات الجديدة plugins scripts. لنكتب البرنامج التالي ونحفظه بإسم program.py:

from code import InteractiveInterpreter

class Interpreter(InteractiveInterpreter):
 def __init__(self):
   InteractiveInterpreter.__init__(self)

# Main program

i = Interpreter()

while True:
 code = input('>>> ')
 i.runcode(code)

أولا قمنا باستيراد الفئة  InteractiveInterpreter من مكتبة code القياسية. هذi الفئة ماهي إلا نسخة من مُفسر بايثون نفسة -النسخة التفاعلية منه- لنستخدمه كـكائن object داخل برنامجنا.

أنشأنا فئة ترث من Interactiveinterpreter وكتبنا الدالة البانية له وهي __init__ و فيها قمنا باستدعاء الوظيفة البانية للـ InteractiveInterpreter نفسه. في البرنامج الرئيسي أنشأنا كائن باسم i من الفئة InteractiveInterpreter ثم قمنا بكتابة حلقة تكرارية بشكل لانهائي لنقرأ أوامر شل البايثون من خلال دالة input، ثم ننفذها مدخلات الدالة بشكل تفاعلي تماما مثل شل البايثون القياسي من خلال دالة runcode.

و النتيجة بعد تجربة بعض الأوامر بشكل تفاعلي هي:

Python 3.7.4 (default, Jul  9 2019, 00:06:43)
[GCC 6.3.0 20170516] on linux
>>> import os
>>> for i in range(5):
  File "<string>", line 1
    for i in range(5):
                     ^
SyntaxError: unexpected EOF while parsing
>>> for i in range(5): print('tick')
tick
tick
tick
tick
tick
>>> exit()

عظيم جدا، لقد قمنا بتضمين شل البايثون التفاعلي داخل برنامجنا لينفذ تعابير البايثون الواحد تلو الآخر قياسيًا. والآن لنقم بالتعديل عليه ليستقبل ملفات بايثون كاملة و يشغلها .أي نقوم بتضمين المُفسر بأكمله داخل برنامجنا و إضافة تفاعل أفضل عن طريق استخدام مكتبة sys.

سنجعل البرنامج يعمل كالتالي:

  • اذا كتبنا i- بعد اسم البرنامج سيقوم بتشغيل الشل التفاعلي
  • اذا كتبنا f- بعد اسم البرنامج سيطلب منا البرنامج بادخال إسم ملف (لا يهم امتداده) يحتوي داخله على برنامج بايثون كامل ليتم تنفيذه.

قم بإنشاء ملف في نفس المكان الذي فيه البرنامج الرئيسي program.py والذي قمت بكتابته سابقا  سمه myplugin.oss واكتب فيه التالي:

print('this is my first plugin running here ...')

for i in range(10):
    print("SUCCESS ...")

والآن قم بتعديل program.py ليكون شكلة كالتالي:

from code import InteractiveInterpreter ,InteractiveConsole
import sys

class Interpreter(InteractiveInterpreter):
 def __init__(self):
   InteractiveInterpreter.__init__(self)

class Console (InteractiveConsole):
   def __init__(self,*args):
       InteractiveConsole.__init__(self,*args)


# Main

# writing usage instructions
USAGE =f"""
 USAGE :

 {sys.argv[0]} -i
 or
 {sys.argv[0]} -f

 EXAMPLE:
 {sys.argv[0]} -i
 {sys.argv[0]} -f

 then follow instructions
 """
# -i for interactive shell , -f to read python instructions from a file
options = ['-i','-f']

# check if no option entered, then show instructions
if len(sys.argv) < 2 or sys.argv[1] not in options:
 print(USAGE)
 sys.exit()

# check options and activate interpreter or Console according to option
if sys.argv[1] == options[0]:
   # run interpreter
   i = Interpreter()
   # infinite loop to act like standard python shell
   while True:
       code = input("--> ")
       i.runcode(code)
else:
   fn = input("please enter name of plugin file you want to run from within this script:\n>>> ")
   # run console to read full files and execute them
   c= Console()
   # open a file
   plugin = open(fn,'r')
   #read valid python instructions(programs) regardless of the extension of the file
   instructions = plugin.read()
   # execute
   c.runcode(instructions)

شرح program.py

  • قمنا باستيراد InteractiveInterpreter , InteractiveConsole من مكتبة code
  • استوردنا مكتبة sys للاستفادة منها لعمل نوع من التفاعل البسيط مع البرنامج لنستخدم منها القيمة argv وهي عبارة عن قائمة list بحيث يكون العنصر رقم صفر هو إسم البرنامج نفسه وأي شيء يليه يضاف فيها كما هو كنصstring .
  • انشأنا فئتي InteractiveInterpreter , InteractiveConsole

البرنامج الرئيسي Main

  • قمنا بكتابة شرح استخدام البرنامج وتخزينها في متغير USAGE وعمل list  تحتوي على الاختيارات التي يتيحها البرنامج.
  • تقوم العبارة الشرطية بالاستفادة من argv لتتحقق ما إذا أدخل المستخدم خيارًا غير موجودًا في قائمة options أو لم يدخل أي شيء بعد اسم البرنامج ليقوم بعرض شرح استخدام البرنامج.
  • تقوم العبارة الشرطية الثانية بالتحقق اذا ما تم إدخال القيمة i- لتقوم بإنشاء كائن object من الشيل التفاعلي الذي أنشأنا منه فئة خاصة وأسميناها Interpreter.
  • تقوم حلقة while الغير منتهية بالعمل بقبول مدخلات من المستخدم والتي هي عبارة عن تعليمات بايثون بشكل نصوص عن طريق دالة input و من ثم تنفيذها عن طريق الدالة ()i.runcode.
  • أما بعد Else وهي ما نعنيه قياسيا بأن المستخدم قام بإختيار f- مما يعني أنه لابد له من ادخال قيمة إسم الملف الذي يحتوي بداخله برنامج بايثون كامل (oss) ليتم تخزين اسمه في المتغير fn.
  • نقوم بعد ذلك بإنشاء كائن object إسمه c من فئة Console class.
  • نقوم بفتح الملف الذي تم تخزين إسمه في fn بوضعية القراءة وهو الملف الـ plugin ونخزنه في متغير أسميناه plugin.
  • قمنا بقراءة جميع محتويات الملف (بغض النظر عن امتداده) دفعة واحدة باستخدام الدالة ()plugin.read و تخزين محتوياته في متغير أسمه instructions
  • قمنا بتمرير المتغير instructions والذي يحتوي على تعليمات برنامج بايثون كامل من الملف الذي تمت قراءة محتوياته للدالة ()runcode ليتم تنفيذه بالكامل.

عند تشغيل البرنامج و تجربة كافة احتمالاته تكون المخرجات بالشكل التالي:

python3 program.py

  USAGE :

  program.py -i 
  or
  program.py -f 

  EXAMPLE:
  program.py -i
  program.py -f 

  then follow instructions
  
python3 program.py -i
--> print('hello coders...')
hello coders...
--> exit()

python3 program.py -f
please enter name of plugin file you want to run from within this script:
>>> myplugin.oss
this is my first plugin running here ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...
SUCCESS ...

رائع. لقد تعلمنا الآن كيفية جعل برامجنا  ” قابلة للبرمجة” scriptable  وكتابة إضافات plugins لها ضمنيا.

لقد كان ذلك مثالا عاما لشرح كيفية بناء وعمل برامج قابلة للبرمجة. سنرى لاحقا إن شاء الله كيفية إستخدام وتوظيف ما تعلمناه أعلاه في مشروع برمجي لتتضح الصورة بشكل أفضل وأكبر وأكثر احترافية.

لنبدأ، لنكتب ملف بايثون يحتوي على عدة دوال أو وظائف functions ثم نقوم باستيراده في ملف آخر يحتوي نصا لبرنامج رئيسي يستخدم الدوال التي في الملف الأول ليعمل. و من ثم نقوم بتعديل البرنامج الرئيسي ليكون قابلا للبرمجة scriptable لنستفيد من هذه الميزة لعمل إضافة plugin تستخدم الدوال الموجودة بملف الدوال و تحوير و نتائج تلك الدوال لخدمة البرنامج الرئيسي.

وصف المشروع

سنعمل على تجهيز مشروع يُمثل نموذجًا لتطبيقات لغة البايثون. قرر رئيس لفريق عمل في شركة صغيرة أن  يقوم  بكتابة برنامج صغير يقوم من من خلاله أن يدخل مجموعة من الأعمال اليومية بحيث يحاوره البرنامج بأن يدخل إسم العمل والشخص المسؤول بإنجازه وكل هذه القيم تخزن في قائمة  تكون بمثابة مفكرة رقمية لرئيس الفريق، فقام بكتابة البرنامج على عجالة على ملفين:

  • الملف الرئيسي py وهو البرنامج الرئيسي.
  • ملف app_functions.py  وهو ملف يحتوي على دوال قام بكتابتها وعزلها بملف خاص بها وذلك لسهولة تتبعها وصيانتها إن لزم الأمر بكل سهولة بعيدا عن سير البرنامج الرئيسي وهذا الملف ومحتوياته يتم استيراده مكتبة داخل البرنامج الرئيسي.
# main.py

import os, sys
from  app_functions import *


def main():
   os.system('clear') # clear terminal (linux/mac) ,replace with cls for windows
   print(USAGE)
  
   jobs = [] # all jobs list
   keep_going =True # keep adding jobs flag
   print('starting ...')
   # reading jobs and saving them to jobs list
   while keep_going :
       job = read_job()
       save_job(job,jobs)
       more = input('continue  ? [y/n]')
       if more.lower() == 'n':
           break
  
   print("*" * 50,'\n')
   print(f"Todat's Jobs are :\n {show_jobs(jobs)}")

if __name__ == "__main__":
   main()

# app_functions.py

def read_job():
   task =input('enter task: ')
   worker = input(f'Entyer worker for {task} : ')
   return {worker:task} # job


def save_job(job,jobs):
   # saves as job dictionary into jobs list
   jobs.append(job)
   print(f'Job : {job} is saved into jobs list ..')

def show_jobs(jobs):
   # showing all jobes for today
   for job in jobs:
       for key,value in job.items():
               print(f'Job : {key} is assigned to Worker: {value}')

البرنامج يسير بشكل جيد ولكن يوجد فيه بعض النواقص والتي بدونها يكون عديم الفائدة بالنسبة لرئيس العمل. منها أن جميع ما تحتويه قائمة الأعمال سيتم مسحها ما إن يتم إغلاق البرنامج. دعت الحاجة لاستخدام وسط تخزين بسيط كالملفات  لتخزين الأعمال بها. يمكننا أن نقوم بكتابة دالة في ملف الدوال  واستيرادها واستخدامها كباقي الدوال ولكن لنفعل ذلك عن طريق كتابة plugin منفصل نقوم بتفعيله تفاعليا عن طريق سطر الأوامر لنستفيد من هذه المهارة التي تعلمناها بمشروعنا هنا.

لنقم بإنشاء ملف اسمه save_plugin.py و نكتب به دالة انشاء وتخزين قائمة الأعمال في ملف نصي بسيط.

أولا قم بإنشاء ملف للـ plugin الذي تريد بناءه و سمه save_plugin.py:

import datetime

# to access anything from the main program , set it as global from the main
# and import it here and use it
from main import jobs
# enter desired file name
file_name = input('enter jobs file name : ')
# create file to save jobs in
jobs_file = open(file_name+str(datetime.datetime.now())+'txt','w')
print(jobs)

for job in jobs:
   for key,value in job.items():
           jobs_file.write(f'Job : {key} is assigned to Worker: {value}'+'\n')

هنا قمنا بكتابة برنامج كامل يقوم أولا باستيراد متغير jobs والذي هو عبارة عن قائمة متغير عام في البرنامج الرئيسي بعد تعديله كما سنرى لاحقا وذلك لجعله سهل الاستيراد من قبل الملفات الخارجية. وظيفة هذا البرنامج plugin هو أن يطلب من المستخدم ان يدخل اسم الملف الذي يرغب بتخزين الأعمال فيه، وقد استخدمنا دالة ()daettime.datetimer.now التي تعطينا وقت انشاء ملف التخزين ولصقة باسم الملف ليثبت تاريخ ووقت إنشائه على وضعية الكتابة w، وأخيرا في الحلقة التكرارية الأخيرة for، قمنا بالمرور على جميع عناصر القائمة jobs والتي تحتوي على قواميس dictionaries بكل عامل و العمل الموكل له ثم قراءة كل مفتاح و قيمة لكل قاموس و تنسيقه في جملة مفيدة ليتم كتابتها في الملف النهائي الذي حدد اسم المستخدم.

# main.py

import os, sys
from  app_functions import *
from code import InteractiveConsole

# InteractiveConsole class
# we added args to give the option to ad as many arguments as we like
class Console (InteractiveConsole):
   def __init__(self,*args):
       InteractiveConsole.__init__(self,*args)

USAGE = f"""
USAGE :
python {sys.argv[0]}

or with plugins

python {sys.argv[0]} <-p> <plugin> <plugin2> ... <plugin n>


"""

# global jobs list
# all jobs list

jobs =[]

def main():
   os.system('clear') # clear terminal (linux/mac) ,replace with cls for windows
   print(USAGE)
   plugin_option = sys.argv[1] # the -p option to load plugin/s
  
   global jobs

   keep_going =True # keep adding jobs flag
   print('starting ...')
   # reading jobs and saving them to jobs list
   while keep_going :
       job = read_job()
       save_job(job)
       more = input('continue  ? [y/n]')
       if more.lower() == 'n':
           break
   print("#" * 50)
   print(jobs)
   print("*" * 50,'\n')
  

   # check if -p option is chosen , then create console object
   # and read the content (python instructions)of the plugin file
   # and run it
   if plugin_option.lower() =='-p':
       plugins_names = sys.argv[2:]
       print('loading plugin/s : '+', '.join(plugins_names) )
      
      
       # iterate through all plugins, load them "read", and execute them one by one
       for plugin in plugins_names:
           c = Console()
           print(f'reading plugin : {plugin}')
            # open a file
           plugin_file = open(plugin,'r')
           #read valid python instructions(programs) regardless of the extension of the file
           instructions = plugin_file.read()
           # execute
           c.runcode(instructions)


if __name__ == "__main__":
   main()

أما هنا في البرنامج  الرئيسي فقد قمنا بكتابة شرح عمل البرنامج. و قمنا بالإعلان عن المتغير jobs خارج جميع الدوال واستدعاءه كمتغير عام global مما سهل علينا استيراده و التعديل عليه من داخل البرنامج الرئيسي وخارجة (plugin).

نلاحظ بأن في عبارة if الأخيرة قمنا بالتحقق من أن المستخدم قد أدخل خيار تحميل الإضافات p- أولا ثم عرض عدد ملفات الإضافات التي تلي خيار التحميل حيث أننا نستطيع تحميل أي عدد نرغب به من ملفات الإضافات plugins  الخارجية، ذلك لأن sys.argv هي عبارة عن قائمة يتم تعبئتها من قبل المستخدم عند بداية تشغيل البرنامج، فعندما نشغل البرنامج بكتابة:

python3 programname.py -p plugin.py

مثلا فإن:

sys.argv = ["programname.py", "-p", "plugin.py"]

لذلك فإن العنصر رقم ٢ هو p- و ما بعدة هو عدد الـ plugins التي نريد تشغيلها والاستفادة منها.

و في حلقة  for الأخيرة فإنها تقوم بإنشاء كائن من الكونسول و المرور على جميع ملفات الـ plugins و قراءة محتوياتها و تنفيذها.

فكرة

هذه مجموعة من الأفكار التي تساعدكم في التدرب في تطبيقات لغة البايثون في مجال جعل البرامج أكثر ديناميكية باستخدام مفهوم plugins:

  • قم بكتابة plugin آخر ليقوم بطباعة رسائل تحفيزية بالألوان بحيث تكون كل رسالة بملف خاص بها ويكون اسم الملف على اسم الشخص الموكل بالعمل.
  • لتكون برامجنا أكثر احترافية و للاستفادة بشكل أكبر مما تعلمنا هنا من مهارات أي كتابة برامج قابلة للبرمجة scriptable، قم ببناء واجهة رسومية متكاملة و اجعل له الامكانية لكتابة اضافاتك من داخل برنامجك نفسه :أن تفتح نافذة تقبل مدخلات نصية لتكتب فيها نصوصك البرمجية لبناء plugins خاصة بك ثم تخزنها أي كأنها نافذة محرر نصوص ضمني داخل برنامجك.

في النهاية، أتمنى أن أكون قد وفقت في تقديم محتوى مفيد والذي قد يكون نادرًا في طرحه خصوصًا على المستوى العربي. حاولت بكل ماأستطيع تبسيط المهارات التي ذكرتها وتبيان أهميتها وندرة التطرق إليها حتى باللغات الأجنبي. فماذا تنتظرون ابنوا و اكتشفوا و ابدعوا. نتمنى مشاركة مقالات السلسلة في منصات التواصل الاجتماعي مثل تويتر وفيسبوك حتى تعم الفائدة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *