Menu
Coddy logo textTech

דקורטורים למחלקות

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Python של Coddy. שיעור 40 מתוך 64.

מעטרי מחלקה מאפשרים לך לשנות או לשפר מחלקות באמצעות עטיפתן בפונקציה אחרת. הם פועלים כמו מעטרי פונקציות, אך מוחלים על מחלקות שלמות.

הנה מחלקה פשוטה ללא קישוט:

class Person:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

צרו דקורטור למחלקה שמוסיף מתודה חדשה:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # הוסף את המתודה למחלקה
    return cls

החל את הדקורטור על מחלקה באמצעות @:

@add_farewell
class EnhancedPerson:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

כעת למחלקה יש גם את השיטות המקוריות וגם את השיטות שנוספו:

person = EnhancedPerson("Alice")
print(person.greet())     # שלום, שמי אליס
print(person.farewell())  # להתראות מאליס

אפשר גם לעטוף מתודה קיימת: לשמור את המקורית כדי שעדיין יהיה אפשר לקרוא לה מתוך העוטפת. זה שימושי כשרוצים להוסיף התנהגות (כמו מעקב או רישום ביומן) סביב מתודה שכבר קיימת במחלקה:

def add_tracking(cls):
    original_greet = cls.greet  # שמור את המתודה המקורית

    def tracked_greet(self):
        print(f"greet was called")   # התנהגות נוספת לפני הקריאה
        return original_greet(self)  # קרא למתודה המקורית

    cls.greet = tracked_greet  # החלף את המתודה במחלקה
    return cls

@add_tracking
class TrackedPerson:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, my name is {self.name}"

כשקוראים עכשיו ל־greet, העוטפת רצה תחילה ואז מעבירה את הביצוע למקור:

person = TrackedPerson("Alice")
print(person.greet())
# הפונקציה greet נקראה
# שלום, שמי Alice

השלבים העיקריים לעטיפת מתודה קיימת הם:
1. שומרים את המתודה המקורית: original = cls.method
2. מגדירים פונקציה חדשה שקוראת ל־original(self) בתוספת כל לוגיקה נוספת.
3. משייכים את הפונקציה החדשה בחזרה למחלקה: cls.method = new_function
4. מחזירים את המחלקה ששונתה.

נקודה מרכזית: מעטרי מחלקות מקבלים מחלקה כקלט, משנים או משפרים אותה ומחזירים את המחלקה ששונתה. הם יכולים להוסיף מתודות, לשנות מאפיינים או לעטוף פונקציונליות קיימת. השתמשו ב־@decorator_name מעל הגדרת המחלקה כדי להחיל אותם. כך אפשר להרחיב את הפונקציונליות של מחלקה בדרך נקייה, ללא ירושה.

challenge icon

אתגר

בינוני

באתגר הזה תממשו דקורטור למחלקה שמוסיף יכולת מעקב אחר קריאות למתודות.

  • decorator.py - מימוש הדקורטור למחלקה add_counter
  • calculator.py - מכיל את המחלקות שישתמשו בדקורטור שלכם

הקובץ driver.py מכיל תרחישי בדיקה מקיפים שיאמתו את המימוש שלכם מול מגוון מקרי שימוש, דפוסי ירושה ומקרי קצה. אין צורך לשנות את הקובץ הזה, אך העיון בו יעזור לכם להבין את ההתנהגות המצופה.

נסו בעצמכם

from calculator import Calculator

# מטפל מקיף במקרי בדיקה
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # אמור להדפיס 8
    print(calc.add(2, 7))  # אמור להדפיס 9
    print(calc.subtract(10, 4))  # אמור להדפיס 6
    print(calc.call_counts)  # אמור להדפיס {'add': 2, 'subtract': 1}

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # אמור להדפיס 8
    print(calc2.add(2, 7))  # אמור להדפיס 9
    print(calc1.subtract(10, 4))  # אמור להדפיס 6
    
    # שני המופעים אמורים לחלוק את אותו call_counts
    print(calc1.call_counts)  # אמור להדפיס {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # אמור להדפיס {'add': 2, 'subtract': 1}
    print(calc1.call_counts is calc2.call_counts)  # אמור להדפיס True

elif test_case == "method_call_order":
    calc = Calculator()
    
    print(calc.add(1, 2))  # אמור להדפיס 3
    print(calc.subtract(5, 2))  # אמור להדפיס 3
    print(calc.add(10, 20))  # אמור להדפיס 30
    print(calc.add(7, 3))  # אמור להדפיס 10
    print(calc.call_counts)  # אמור להדפיס {'add': 3, 'subtract': 1}

elif test_case == "large_values":
    calc = Calculator()
    
    print(calc.add(1000000, 2000000))  # אמור להדפיס 3000000
    print(calc.subtract(5000000, 2000000))  # אמור להדפיס 3000000
    print(calc.call_counts)  # אמור להדפיס {'add': 1, 'subtract': 1}

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # אמור להדפיס -8
    print(calc.subtract(-10, -4))  # אמור להדפיס -6
    print(calc.call_counts)  # אמור להדפיס {'add': 1, 'subtract': 1}

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # אמור להדפיס 8.7
    print(calc.subtract(10.5, 4.2))  # אמור להדפיס 6.3
    print(calc.call_counts)  # אמור להדפיס {'add': 1, 'subtract': 1}

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # אמור להדפיס 0
    print(calc.subtract(0, 0))  # אמור להדפיס 0
    print(calc.call_counts)  # אמור להדפיס {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # אמור להדפיס 8
    print(calc.call_counts)  # אמור להדפיס {'add': 1, 'subtract': 0}
    
    # אפס את המונה
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # אמור להדפיס 9
    print(calc.call_counts)  # אמור להדפיס {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # אמור להדפיס 8
    print(calc.call_counts)  # אמור להדפיס {'add': 1, 'subtract': 0}
    
    # שנה את המונה ישירות
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # אמור להדפיס 9
    print(calc.call_counts)  # אמור להדפיס {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # בצע 1000 פעולות add
    for i in range(1000):
        calc.add(i, i+1)
    
    # בצע 500 פעולות subtract
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # אמור להדפיס {'add': 1000, 'subtract': 500}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Python אונליין