דוגמא קצרה לשיתוף קוד עם Decorators ב Python
דקורייטורים בפייתון הם פיצ'ר סופר שימושי שלפעמים הולך לאיבוד בגלל התחביר המבלבל שלו, ובמיוחד כשצריך דקורייטור שמקבל פרמטרים. בכל זאת לפעמים הם ממש עוזרים והנה דוגמא קטנה שאולי תתן לכם השראה.
1. מה אנחנו בונים
יש לנו מכונה וירטואלית שמיוצגת באמצעות מחלקה שנקראת VM, ולמכונה זו יש זיכרון ופעולות שהמכונה יודעת לעשות. הזיכרון מנוהל באמצעות dictionary כאשר כל תא בזיכרון מיוצג על ידי אות קטנה באנגלית.
התחלה של המחלקה יכולה להיראות כך:
class VM:
def __init__(self):
self.memory = defaultdict(int)
def add(self, x, y):
self.memory[x] += y
עכשיו אנחנו יכולים להפעיל את הפקודה add כדי להוסיף ערך לזיכרון:
vm = VM()
vm.add('a', 10)
# prints 10
print(vm.memory['a'])
אפשר בקלות לדמיין שיהיו עוד פעולות בתוכנית למשל:
def set(self, x, y):
self.memory[x] = y
def mul(self, x, y):
self.memory[x] *= y
def mod(self, x, y):
self.memory[x] %= y
ועכשיו אנחנו רוצים לעדכן את הקוד כך שאפשר יהיה להעביר בתור פרמטר שני גם שם של רגיסטר ולא רק מספר, כלומר נרצה להיות מסוגלים לכתוב:
vm = VM()
vm.add('a', 10)
vm.set('b', 20)
vm.add('a', 'b')
כדי לקבל ברגיסטר a את הערך 30.
2. פיתרון בלי Decorator
פיתרון קל בלי דקורייטור ידרוש לכתוב פונקציה חדשה, נקרא לה valify, שלוקחת פרמטר ובודקת האם להפוך אותו לערך (זכרו אם זה מספר אנחנו לא צריכים לגשת למילון, רק אם זה אות). שימוש בפונקציה מתוך add עשוי להיראות כך:
def add(self, x, y):
self.memory[x] += valify(y)
זה עובד אבל מעצבן - עכשיו צריך בכל אחת מהפעולות לזהות כל פעם שהפעולה פונה למשתנה בתור ערך ולהפעיל פונקציה חדשה. שינוי כזה ישפיע על הקוד ממש של כל הפונקציות שכתבנו.
3. פיתרון עם Decorator
דקורייטור מאפשר לנו לקחת גישה שונה ולשים את השינוי הזה מחוץ לפונקציה, כלומר נוכל לכתוב:
@valify(1)
def add(self, x, y):
self.memory[x] += y
כדי שבאופן אוטומטי הפונקציה תקבל את y אחרי שהפכנו אותו מרגיסטר למספר. השימוש בפרמטר בדקורייטור יאפשר לנו לכתוב פעולות יותר מתוחכמות למשל כאלה שצריכות להתיחס גם לפרמטר הראשון שלהן בתור מספר.
קוד הדקורייטור אגב נראה כך:
def valify(index):
def decorator(f):
def inner(self, *args):
next_args = list(args)
if args[index] in string.ascii_lowercase:
next_args[index] = self.memory[args[index]]
else:
next_args[index] = int(args[index])
f(self, *next_args)
return inner
return decorator
אז נכון שילמנו מחיר בכתיבת פונקציית Decorator שהיא יותר מורכבת מאשר פונקציה רגילה, אבל הרווחנו שרוב הקוד שלנו יישאר נקי יותר וכמעט לא יושפע מהעדכון בדרישות.
נ.ב. אני מסביר ממש לעומק על התחביר של Decorators עם עוד המון דוגמאות למקרים בהם כדאי להשתמש בו בקורס פייתון מתקדם כאן באתר. אם אתם כבר כותבים פייתון ורוצים להעלות את הרמה ולכתוב קוד נקי יותר ממליץ להעיף מבט. זה הקישור לקורס: https://www.tocode.co.il/bundles/advanced-python3