״שומע יש לי פה קוד פייתון שסופר כמה פעמים פונקציה מסוימת נקראה בעזרת משתנה גלובאלי מסוג מספר. אני צריך להוסיף הדפסה כל פעם שמעדכנים את הערך״
״מה הבעיה? רק צריך לרשת מ int ולהוסיף ל __add__
פקודת הדפסה. משהו כזה-
class LoggedAddInt(int):
def __new__(cls, *args, **kwargs):
args_without_name = {k: v for k, v in kwargs.items() if k != 'name'}
return super().__new__(cls, *args, **args_without_name)
def __init__(self, *args, **kwargs):
self.name = kwargs.get('name', 'New Value')
def __add__(self, other):
res = LoggedAddInt(super().__add__(other), name=self.name)
print(f"{self.name}: {res}")
return res
counter = LoggedAddInt(0, name="counter")
def do_something():
global counter
counter += 1
do_something()
do_something()
do_something()
וכן זה עובד, וכן איזה כיף שאנחנו יודעים ירושה והכל, אבל בעיניי קצת יותר מדי מבריק. הסיכוי לשימוש חוזר במנגנון כל כך ספציפי נמוך, וכמות הקוד שמחליפה הודעת הדפסה בודדת פשוט לא שווה את זה.
אבל בואו ניקח את זה עוד צעד קדימה - מה אם יש לי במערכת מאות מקומות בהם מעדכנים את המשתנה הזה? עכשיו להתחיל להוסיף הודעת הדפסה לכולם? ואחרי זה לחזור לעדכן כל פעם שנצטרך הודעה אחרת או מנגנון אחר? נו, סוג של.
מצד אחד אם צריך להוסיף הודעת הדפסה במאות מקומות במערכת שמעדכנים משתנה מסוים זה אומר שהמשתנה הזה מלכתחילה לא צריך להיות int פשוט, ועדיף לכל הצדדים שאותם מקומות במערכת ישתמשו בהפעלת מתודה בצורה מפורשת במקום באופרטור +=
. במילים אחרות קלאס חדש שלא קשור בכלל ל int יעבוד יותר טוב ויראה כך:
class NotifyingCounter:
def __init__(self):
self.value = 0
def inc(self):
self.value += 1
print(f"counter: {self.value}")
counter = NotifyingCounter()
def do_something():
counter.inc()
do_something()
do_something()
do_something()
והאם זה הגיוני לשנות בכל המערכת את הקריאות לפורמט החדש? בטח. במבנה כזה הרבה יותר ברור מה אפשר ומה אי אפשר לעשות עם counter, מתי הוא ידפיס ואם נצטרך בעתיד לשנות שוב את ההתנהגות כשמוסיפים 1. מבנה כזה גם יותר קל לתחזוקה בהשוואה למחלקה שיורשת מ int. נכון ירושה מ int היתה חוסכת לנו לשנות קוד בתוך הפונקציות, אבל בטווח הרחוק זה בסך הכל האק, חיסכון בעבודה היום שעוד נתחרט עליו בעתיד.