• בלוג
  • פיתוח מערכת פלאגינים פשוטה ב Python

פיתוח מערכת פלאגינים פשוטה ב Python

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

אחד היתרונות בעבודה עם שפה דינמית הוא שמאוד קל לטעון קבצים שאולי לא ידעתם על קיומם בעת כתיבת הקוד וכך לקבל מערכת יותר גמישה - מערכת שבשביל להרחיב אותה כל מה שצריך זה לשים עוד קובץ בתיקיה מסוימת ובאופן אוטומטי המערכת מקבלת יכולות חדשות.

בשביל לבנות מערכת מבוססת פלאגינים כל מה שאנחנו צריכים זה להחליט (עם עצמנו) על דרך בה המערכת הולכת לתקשר עם הפלאגינים, להחליט על מבנה עבור הפלאגינים ועל תיקיה עבורם ולצאת לדרך.

בפוסט זה אראה דוגמה פשוטה לכתיבת מערכת מבוססת פלאגינים שטוענת את כל התוספים מתיקיה בתוך תיקיית הפרויקט בזמן העליה ומפעילה את הקוד מהתוספים לפי ההגדרות של כל תוסף.

1. מה אנחנו בונים

נרצה לבנות מערכת לעיבוד טקסט שתקרא טקסט ממשתמש ותעביר אותו ב Pipeline של פלאגינים, כאשר כל פלאגין חושף פונקציה אחת:

def process(text):
    return text

פונקציה זו תיקרא תמיד process, תקבל תמיד טקסט ותחזיר טקסט. אנחנו לא יודעים מה כל פלאגין יעשה או אפילו איזה פלאגינים יהיו במערכת כשנפעיל אותה, וזה חלק מהקסם.

בנוסף ובשביל לקבוע את סדר העבודה של פלאגינים, כל פלאגין יוכל להגדיר משתנה גלובאלי בשם weight. ערך ברירת המחדל של המשתנה הוא 0 והמערכת טוענת את הפלאגינים לפי המשקלים שלהם: משקל נמוך ייטען לפני משקל גבוה. בצורה כזאת כל פלאגין יוכל לבקש להיטען בזמן מסוים שמתאים לו.

הפלאגינים עצמם יהיו קבצי py שיישמרו בתוך תיקיה בשם addons. המערכת תטען את כולם בעליה, תבקש מהמשתמש טקסט ותשלח את הטקסט דרך כל הפלאגינים לפי הסדר, כל פלאגין יקבל כקלט את מה שהפלאגין הקודם החזיר.

2. פלאגינים לדוגמה

הנה שלוש דוגמאות לפלאגינים שנעבוד איתם. הפלאגין הראשון נקרא lowercase והוא פשוט הופך את הטקסט שקיבל לאותיות קטנות. בקובץ addons/lowercase.py ארשום את התוכן הבא:

def process(text):
    return text.lower()

אני מוותר על המשקל ולכן המשקל שלו יהיה 0.

הפלאגין השני נקרא rot13. הוא הופך כל אות לאות שנמצאת 13 אותיות אחריה. בקובץ addons/rot13.py נרשום את התוכן הבא:

numletters = ord('z') - ord('a') + 1

weight = 1

def add13(letter):
    if letter == " ":
        return letter
    else:
        return chr(((ord(letter) - ord('a') + 13) % numletters) + ord('a'))

def process(text):
    return ''.join(add13(letter) for letter in text)

והפלאגין השלישי נקרא titlize ובו נרשום את התוכן הבא:

weight = 2

def process(text):
    return text.title()

3. קוד המערכת

עכשיו אפשר לעבור לכתוב את התוכנית שקוראת את הפלאגינים. מסתבר שכל מה שצריך בשביל לקרוא קובץ חיצוני בפייתון, אם אתם יודעים את שמו, זה להפעיל פונקציה בשם __import__. אנחנו נעביר לה בתור פרמטר ראשון את שם החבילה ונקבל אוביקט מודול. במודולים מקוננים צריך להעביר גם פרמטר שנקרא fromlist שאומר איזה תת-מודול אנחנו רוצים לטעון.

הקוד הבא לדוגמה יטען את כל המודולים מתיקיית addons וידפיס את השמות והמשקלים שלהם:

from pathlib import Path
import os

script_dir = os.path.dirname(os.path.realpath(__file__))
addons_dir = Path(script_dir) / "addons"

for filename in addons_dir.glob("*.py"):
    addon_name = filename.stem
    module = __import__(f"addons.{addon_name}", fromlist=addon_name)
    weight = getattr(module, 'weight', 0)
    print(f"Addon: {addon_name}; weight = {weight}")

עכשיו אפשר להשתמש ברשימה זו כדי להפעיל את כולם אחד אחרי השני על טקסט שנקבל מהמשתמש. הפונקציה reduce של פייתון תעזור וכמובן sorted עם מפתח שהוא המשקל. כך הכל מתחבר:

from pathlib import Path
from functools import reduce
import os

script_dir = os.path.dirname(os.path.realpath(__file__))
addons_dir = Path(script_dir) / "addons"

addon_names = [filename.stem for filename in addons_dir.glob("*.py")]
addons = sorted(
        [__import__(f"addons.{name}", fromlist=name) for name in addon_names],
        key=lambda mod: getattr(mod, 'weight', 0))

text = input()
print(reduce(lambda acc, val: val.process(acc), addons, text))

בהנחה שכל שלושת הפלאגינים נמצאים בתיקיה כשתריצו את הקוד תוכלו להקליד שורה ובאופן אוטומטי השורה תהפוך לאותיות קטנות בלבד, לאחר מכן כל אות תוחלף בזו שנמצאת 13 מקומות אחריה ב abc ולסיום אות ראשונה בכל מילה תהפוך לאות גדולה. הפעלה לדוגמה אצלי נראית כך:

$ python3 main.py
hello world
Uryyb Jbeyq

אבל כמובן שמה שמלהיב כאן הוא שהפונקציונאליות של התוכנית נקבעת לפי הקבצים שבתיקיה. מחקו פלאגין והתוכנית תתנהג אחרת. הוסיפו קובץ פלאגין חדש והתוכנית באופן אוטומטי תפעיל גם אותו על הטקסט.

מבנה כזה של טעינה דינמית הוא הבסיס לתוכניות מבוססות הרחבות שאנחנו מכירים: החל מדפדפני אינטרנט (המאפשרים טעינת תוספים), סביבות פיתוח, כלי עיבוד סאונד ואפילו משחקים כמו מיינקראפט. מרגע שנתתי תוכנה כזו ללקוח הוא יכול לקחת פלאגין מכל מפתח אחר, וכל עוד הפלאגין כתוב לפי הכללים התוכנית באופן אוטומטי תצליח לעבוד איתו.