• בלוג
  • טיפ: קריאה לפונקציה בפייתון רק לפי שמה

טיפ: קריאה לפונקציה בפייתון רק לפי שמה

12/09/2019

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

דוגמא? ברור - נניח שיש לכם קוד שקורא פקודות מהמשתמש ומבצע את ההוראות לפי הפקודה. בשביל שהקוד לא יהיה ארוך מדי נשתמש בפקודה inc כדי להגדיל ערך של תא בזיכרון וב show כדי להציג תא. הפעלה לדוגמא של התוכנית עשויה להיראות כך:

> show x
0

> inc x
> show x
1

> inc x
> inc x
> show x 
3

> inc y
> show y
1

השורות שמתחילות בסימן חץ הן הקלט שמשתמש מכניס, והשורות בלי החץ הן פלט התוכנית. מימוש ראשוני בפייתון עשוי להיראות כך:

from collections import defaultdict

counters = defaultdict(int)

while True:
    try:
        next_line = input('> ')
        cmd, arg  = next_line.split()
        if cmd == "show":
            print(counters[arg])
        elif cmd == "inc":
            counters[arg] += 1
    except Exception:
        print("Invalid command. Try again")

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

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

from collections import defaultdict 

class Counters:
    def __init__(self):
        self.counters = defaultdict(int)

    def show(self, reg):
        print(self.counters[reg])

    def inc(self, reg):
        self.counters[reg] += 1

counters = Counters()
while True:
    try:
        next_line = input('> ')
        cmd, *args  = next_line.split()
        counters.__getattribute__(cmd)(*args)
    except AttributeError:
        print("Invalid command, try again")
    except TypeError:
        print("Missing required arguments to command")
    except EOFError:
        print("Bye bye")
        break

במבנה כזה יש מספר יתרונות:

  1. קל להוסיף התנהגות - פשוט מוסיפים עוד פונקציה ל Counters

  2. קל לתת טיפול מתאים לבעיות שונות, בזכות השימוש בסוגי Exceptions שונים.

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