השבוע בשיעור שבכלל לא דיבר על Decorators יצא לי לכתוב עם התלמידים קוד עבור מפענח שקורא הוראות מקלט חיצוני ומבצע אותן לפי הסדר. המפענח שלנו החזיק "זיכרון" וכל הוראה יכלה לקבל פרמטר מספרי או פרמטר שהוא אות, כאשר אם קיבלנו אות היינו צריכים לקרוא את הערך שמתאים לאות זו מהזיכרון.
לדוגמא המפענח תומך בפעולת set ששומרת ערך לזיכרון. אפשרות אחת היא להעביר ל set מספר:
set a 10
ואז בתא a בזיכרון נשמור את המספר 10. אפשרות שניה היא להעביר ל set אות ואז נצטרך לקרוא מהזיכרון את הערך בתא המתאים, לדוגמא:
set a 10
set b a
ישמור בתא a את הערך 10 ובתא b את הערך ששמור עכשיו ב a, כלומר גם 10.
בקיצור יצא שכתבנו קוד עם אלמנט חזרתי שנראה בערך כך:
class MyParser:
# ...
def set(self, reg, val):
ival = self.read_register_or_value(val)
self.memory[reg] = val
def sub(self, reg, val):
ival = self.read_register_or_value(val)
self.memory[reg] -= ival
def mul(self, reg, val):
ival = self.read_register_or_value(val)
self.memory[reg] *= ival
כל פונקציה משתמשת בפונקציית עזר כדי לבדוק אם הערך שקיבלה הוא מספר או אות, ואם אות אז לקרוא מהזיכרון את הערך המתאים.
הקוד עובד והשימוש החוזר בפונקציית העזר באמת משפר את המצב בהשוואה לכתיבת הלוגיקה המלאה שלוש פעמים אבל עדיין משהו כאן צורם: כל פונקציה צריכה להגדיר משתנה חדש מה שמבזבז לנו שם, והמבנה של שורה ראשונה בפונקציה שמוקדשת להכנת המשתנים עלול לשעמם כשרואים אותו שוב ושוב.
דרך אחת להפוך את הקוד לקצת יותר נעים לעין היא להמיר את הקריאה היזומה בקריאה ל Decorator:
class MyParser:
@read_value_from_register
def set(self, reg, val):
self.memory[reg] = val
@read_value_from_register
def sub(self, reg, val):
self.memory[reg] -= val
@read_value_from_register
def mul(self, reg, val):
self.memory[reg] *= val
כל מה שצריך בשביל המעבר למבנה החדש הוא להגדיר את הפונקציה read_value_from_register
מחוץ למחלקה בתור Decorator:
def read_value_from_register(f):
def inner(self, reg, val):
ival = self.read_register_or_value(val)
f(self, reg, ival)
return inner
מעניין לשים לב שבמקרה כזה הפונקציה f שעוברת כפרמטר היא עדיין לא Object Method אלא פונקציה פשוטה. היא עדיין לא קשורה לאוביקט מאחר וה Decorator נקרא לפני שנוצרו אוביקטים מסוג MyParser. מסיבה זו בהפעלת הפונקציה אנחנו חייבים להעביר את self בצורה יזומה בתור הפרמטר הראשון. מלבד תשומת הלב המיוחדת ל self, הגדרת Decorator למתודה (שנמצאת בתוך מחלקה) זהה לחלוטין להגדרת Decorator לפונקציה רגילה.