שלום אורח התחבר

דוגמא קצרה לשיתוף קוד עם 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

מעדיפים לקרוא מהטלגרם? בקרו אותנו ב:@tocodeil

או הזינו את כתובת המייל וקבלו את הפוסט היומי בכל בוקר אליכם לתיבה:


נהניתם מהפוסט? מוזמנים לשתף ולהגיב