טיפ: קריאה לפונקציה בפייתון רק לפי שמה
בפייתון אין מבנה מסודר של 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
במבנה כזה יש מספר יתרונות:
קל להוסיף התנהגות - פשוט מוסיפים עוד פונקציה ל Counters
קל לתת טיפול מתאים לבעיות שונות, בזכות השימוש בסוגי Exceptions שונים.
קל לבנות פעולות שמקבלות מספר שונה של ארגומנטים (או כלל לא), בזכות השימוש בכוכבית.