לדלג לתוכן

7.1 דקורטורים מתקדמים הרצאה

הקדמה

  • בהרצאה נלמד איך לבנות decorator-ים מתקדמים בפייתון. לפני שאתם מתקדמים בשיעור, עברו שוב בסיכומים על מה למדנו על דקורטורים וודאו שאתם יודעים לכתוב דקורטור פשוט.

חזרה קצרצרה

  • איך דקורטור עובד מאחורי הקלעים?
  • דקורטור היא פונקציה שמטרתה לקשט פונקציות אחרות, דקורטור מקבל פונקציה כפרמטר, ומחזיר פונקציה מקושטת.
    def log(func):
         def wrapper(*args):
             print(f"function {func.__name__} started to run!")
             res = func(*args)
             print(f"function {func.__name__} finished!")
             return res
        return wrapper
    
    @log
    def func(x):
         print(x)
    
    func(5)
    
  • הנה דוגמה לדקורטור, ניתן לראות שהדקורטור קיבל פונקציה כפרמטר, הגדיר פונקציה חדשה בתוכו, והחזיר את הפונקציה החדשה שהגדיר.
  • אנחנו משתמשים באופרטור "@" בפייתון כדי לקשט את הפונקציות, אבל איך הקסם הזה עובד מאחורי הקלעים? כשאנחנו כותבים את הקוד הבא:
    @log
    def func(x):
         print(x)
    
    func(5)
    
  • הוא מתורגם לקוד הבא:
    def func(x):
         print(x)
    
    log(func)(5)
    
  • הפונקצית דקורטור מקבלת כפרמטר את הפונקציה שאותה אנחנו מקשטים, הביטוי הזה מחזיר את הפונקציה המקשוטת ולה אנחנו מעבירים את הפרמטר "5" ומריצים.

דקורטור כפול

  • אנחנו יכולים להכיל מספר של דקורטורים על פונקציה
    def log(func):
         def wrapper(*args):
             print(f"function {func.__name__} started to run!")
             res = func(*args)
             print(f"function {func.__name__} finished!")
             return res
         return wrapper
    
    def twice(func):
        def wrapper(*args):
            res = func(*args)
            res = func(*args)
            return res
        return wrapper
    
    @log
    @twice
    def func(x):
         print("nice")
         print(x)
    
    func(5)
    

    שני הדקורטורים יכולו על הפונקציה, קודם כל log ואז twice.
  • למה זה יתורגם?
    def func(x):
         print("nice")
         print(x)
    
    log(twice(func))(5)
    

פרמטר לדקורטור

  • לפעמים נרצה לכתוב דקורטור שיכול לקבל פרמטר, זה יראה כך:
    @setting_first_param(2)
    def add(x, y):
        return x + y
    
  • למה שנרצה לעשות דקורטור כזה? אנחנו לא ממש, אני לא ראיתי את זה הרבה בשימוש בפייתון - אבל השאירו את האופציות פתוחות.
  • במקרה הזה המטרה של הדקורטור זה לתת ערך לפרמטר הראשון של הפונקציה, בדוגמה למעלה הוא יביא לו 2.
  • כך ניצור דקורטור כזה:
    def setting_first_param(param_1):
        # Creating a decorator
        def wrapper1(func):
            def wrapper2(param_2):
                return func(param_1, param_2)
            return wrapper2
         # Returning a decorator
        return wrapper1
    
  • חשבו על זה שבמקרה הזה הדקורטור שלנו מחזיר דקורטור, יצרנו דקורטור לדקורטורים.
  • הדקורטור הראשון מקבל את הפרמטר של הדקורטור, והדקורטור השני הוא הדקורטור של הפונקציה.

    @setting_first_param(2)
    def add(x, y):
        return x + y
    
    
    print(add(1))  # Output: 3
    print(add(10))  # Output: 12
    

  • למה זה יתורגם?

    def add(x, y):
        return x + y
    
    
    print(setting_first_param(2)(add)(1))  # Output: 3
    print(setting_first_param(2)(add)(10))  # Output: 12
    

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

דקורטור כקלאס

  • נוכל ליצור דקורטור עם class, איך זה עובד? אנחנו יכולים לממש את המתודות: __init__, ו - __call__.
    כך יראה המימוש:
    class MyDocrator:
        def __init__(self, func):
            self._func = func
    
        def __call__(self, *args):
            print('before function call!')
            result = self._func(*args)
            print('after function call!')
            return result
    
  • אחרכך נוכל להשתמש בזה ממש כמו דקורטור רגיל לחלוטין:
    @MyDocrator
    def my_function(param):
        return f'function called! {param}'
    
    print(my_function("hello"))
    
    # Output:
    #  before function call!
    #  function called! hello
    #  after function call!
    
  • למה זה יתורגם? (ספוילר: בדיוק כמו דקורטור רגיל)
    def my_function(param):
        return f'function called! {param}'
    
    print(MyDocrator(my_function)("hello"))
    
    # Before function call!
    # Function called! hello
    # After function call!
    
  • שימו לב שהדקורטור מקבל כפרמטר את הפונקציה, ואז עושה call לאובייקט שנוצר. שזה ממש תואם את הפונקציות __init__ ו - __call__שמימשנו, כאשר __init__ נקרא בחלק הזה: MyDocrator(my_function) ומחזיר את אובייקט ואז __call__ נקרא שאנחנו קוראים לאובייקט - ("hello")

דקורטור לקלאס

כמו שאפשר לכתוב דקורטור לפונקציה, אפשר לכתוב דקורטור לקלאס.
- דקורטור לקלאס זה דקורטור שמקבל קלאס כפרמטר ומחזיר קלאס חדשה. הנה דוגמה לדקורטור כזה:

def add_method(cls):
    """
    This decorator adds a new method to the decorated class.
    """
    def new_method(self, x, y):
        """
        This method adds two numbers.
        """
        return x + y

    # Adding the new method to the class
    cls.new_method = new_method
    return cls

- הדקורטור הזה מקבל קלאס, מוסיף לה פונקציה חדשה ומחזיר את הקלאס.
דוגמה לשימוש בדקורטור על קלאס:
@add_method
class Calculator:
    def __init__(self):
        pass

    def multiply(self, x, y):
        """
        This method multiplies two numbers.
        """
        return x * y

# Creating an instance of the Calculator class
calc = Calculator()

# Using the added method
print(calc.new_method(3, 4))  # Output: 7
print(calc.multiply(3, 4))     # Output: 12