תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

דקורטורים של פייתון : דפוס, הבנה ויישום הלכה למעשה

מחבר:
בתאריך:

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

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

דקורטורים decorators של פייתון

 

דברים שחייבים להבין לפני שניגשים ללמוד על decorators

כדי להבין את הנושא של דקורטורים חשוב להבין שבפייתון פונקציות הם אזרחים מסוג א' first class citizens מה שאומר שניתן לקנן (=לשים אחת בתוך השנייה) פונקציה אחת בתוך אחרת, להעביר פונקציה בתור ארגומנט, ולהחזיר פונקציה בתור ערך. 

אפשר לקנן פונקציה אחת בתוך אחרת. לדוגמה:

def say_hello(to):
    def greet():
        return f"Hello, {to}"
    return greet()

res = say_hello("Moshe")
print(res)
  • הפונקציה הפנימית 'greet()' נמצאת בתוך הפונקציה העוטפת 'hello()'.
  • בגלל ש-'greet()' נמצאת בתוך 'hello()' היא יכולה לגשת למשתנה 'to' שנמצא ב-'scope' של הפונקציה העוטפת.
  • הפונקציה העוטפת קוראת לפונקציה הפנימית.

התוצאה:

Hello, Moshe

אפשר להעביר פונקציה בתור ארגומנט לפונקציה:

def say_hello(func, to):
    return func(to)

def greet(to):
    return f"Hello, {to}"

    
res = say_hello(greet, "Moshe")
print(res)
  • הפונקציה 'say_hello()' מקבלת שני ארגומנטים: 'func' ו-'to'. כאשר 'func' היא פונקציה שצריך לקרוא לה מתוך 'say_hello()'.

  • הפונקציה 'greet()' מוגדרת בנפרד היא מקבלת ארגומנט 'to' ומחזירה טקסט מפורמט.

    "Hello, {to}"
  • שים לב לשורה:

    res = say_hello(greet, "Moshe")

    הפונקציה 'greet()' מועברת כארגומנט לפונקציה 'say_hello()'. כאשר 'greet()' משמשת callback function בגלל שקוראים מתוך 'say_hello()'.

אפשר להחזיר פונקציה כערך:

def say_hello(to):
    def greet():
        return f"Hello, {to}"
    return greet()

res = say_hello("Moshe")
print(res)

בדוגמה לעיל, הפונקציה greet() מוכלת בתוך הפונקציה say_hello() ואף מוחזרת ממנה.

הפונקציה המשמשת כ- decorator מקבלת פונקציה, משנה את פעילותה ומחזירה אותה.

 

דקורטור הוא קודם כל דפוס תכנותי

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

בוא נראה לדוגמה את הפונקציה הבאה שהיא פונקציה פשוטה:

def pusht():
   print("I am the original part")

נריץ אותה:

pusht()

נקבל:

I am the original part

נוסיף פונקציה דקורטור:

def decorate(func):
   # Define the inner function that will add additional behavior
   def inner():
       # Add some additional behavior to the decorated function
       print("I am the modified part")


       # Call the original function
       func()


   # Return the inner function
   return inner
  • הקוד מגדיר פונקציה שקראנו לה 'decorate()' המקבלת 'func' כארגמונט.
  • בתוך הפונקציה 'decorate()' פונקציה פנימית שקראנו לה 'inner()' מוגדרת אף היא. הפונקציה הפנימית היא שתחזיר גרסה של 'func' אחרי שתעבור שינויים.
  • לבסוף, הפונקציה 'decorate()' מחזירה את הפונקציה הפנימית 'inner()'.

השורה הבאה תגרום לפונקציה decorate() לשנות את התנהגותה של הפונקציה pusht():

# Decorate the ordinary function by passing it to make_pretty
decorated_func = decorate(pusht)

נקרא לפונקציה אחרי שעברה את השינוי:

# Call the decorated function
decorated_func()

התוצאה:

I am the modified part
I am the original part

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

 

יישום דקורטור בפייתון באמצעות תחביר הסמל '@'

במקום להעביר פונקציה אחת לאחרת כארגומנט פייתון מאפשר לעשות את אותו הדבר על ידי שימוש בתחביר הסמל '@' (at symbol).

נערוך את הפונקציות שלנו כדי להדגים את השימוש בתחביר '@':

def decorate(func):
   def inner():
       print("I am the modified part")
       func()
   return inner


# Decorate the pusht function
@decorate
def pusht():
   print("I am the original part")


# Call the decorated function
pusht()

נריץ את הקוד ונקבל:

I am the modified part
I am the original part

קיבלנו את אותה התוצאה בתחביר תמציתי יותר.

נסביר את הקוד:

  • הקוד מגדיר פונקציית דקורטור ששמה 'decorate()' המקבלת את ארגומנט 'func' שהוא פונקציה.
  • בתוך פונקצית הדקורטור, קיימת פונקציה פנימית 'inner()' שתוסיף התנהגות על הפונקציה שהיא מקבלת כארגומנט. במקרה זה, השינוי הוא הדפסה של הטקסט "I am the modified part" לפני שקוראים לפונקציה המקורית.
  • הפונקציה 'decorate()' קוראת לפונקציה בתוכה ששמה 'inner()' ומחזירה אותה.
  • הדקורטור 'decorate()' מופעל על הפונקציה 'pusht()' בתחביר '@'. משמעות הדבר שהפונקציה 'pusht()' מוחלפת על ידי גרסה שלה אותה שינתה הפונקציה 'decorate()'.

 

העברת ארגומנטים לפונקציה מסוג דקורטור

אפשר גם להעביר ארגומנטים לפונקציה מסוג דקורטור. נראה לדוגמה את הפונקציה הבאה:

def add_five(number):
   return number + 5
  • הפונקציה 'add_five()' מקבלת מספר כארגומנט ומוסיפה לו 5.

נפעיל על הפונקציה 'add_five()' פונקציה מסוג decorator שיכולה לעשות אחד משני דברים: להכפיל פי-2 או להעלות בריבוע כתלות בפרמטר שנעביר לה. ניתן לפונקציה מסוג דקורטור את השם 'operate()' והיא תקבל פרמטר 'operation':

def operate(operation):
    if operation == "double":
        # Double the result
        return result * 2
    elif operation == "square":
        # Square the result
        return result ** 2
  • אם נעביר לפונקציה 'operate()' את הפרמטר "dobule" היא תכפיל את הערך 'result' ואם את הפרמטר "square" היא תעלה את 'result' בריבוע.

הערך 'result' צריך להיות תוצאה של הפונקציה שהדקורטור צריך לשנות. בשביל להעביר את הפונקציה ואת הערכים שהיא מקבלת נשתמש בשתי פונקציות פנימיות, שפה נקרא להם 'wrapper()' ו-'inner()'.

הפונקציה 'wrapper()' תקבל את הפונקציה func שצריך לשנות (ולזה אנחנו כבר רגילים) הפונקציה 'inner()' תקבל את הערכים שהפונקציה func צריכה לקבל בתור ארגומנטים. כך זה נראה:

def operate(operation):
   # Define the wrapper function that takes the original function as an argument
   def wrapper(func):
       # Define the inner function that performs the desired operation on the result
       def inner(*args, **kwargs):
           # Call the original function with the given arguments and store the result
           result = func(*args, **kwargs)
          
           if operation == "double":
               # Double the result
               return result * 2
           elif operation == "square":
               # Square the result
               return result ** 2
           else:
               # Raise an exception for invalid operations
               raise ValueError("Invalid operation")
      
       # Return the inner function
       return inner
  
   # Return the wrapper function
   return wrapper

נפעיל את הפונקציה דקורטור שקראנו לה 'operate()' על הפונקציה 'add_five()' באמצעות תחביר '@':

@operate("square")
def add_five(number):
    # Add 5 to the given number
    return number + 5

print(add_five(3)) # (3 + 5) ^ 2 = 64
  • פונקצית הדקורטור'operate()' משנה את התנהגות הפונקציה המקורית בהתאם למחרוזת שהיא מקבלת כפרמטר. הפונקציה 'operate()' מכילה ומחזירה את הפונקציה 'wrapper()'.
  • הפונקציה 'wrapper()' מקבלת את הפונקציה המקורית 'func' כארגומנט ומחזירה את הפונקציה 'inner()'. הפונקציה 'inner()' קוראת ל-'func' עם הארגומנטים *args ו-**kwargs (אם משהו לא ברור בבקשה לקרוא את המדריך על args ו-kwargs), ומאחסנת את התוצאה לתוך המשתנה 'result'. את המשתנה 'result' היא משנה בהתאם לערך המשתנה 'operator' (היא יכולה להכפיל או להעלות בריבוע בלבד).
  • לפונקציה 'add_five()' העברנו את הפרמטר 3, וגם קישטנו בדקורטור 'operate()' שקיבל את הפרמטר "square". כתוצאה מכך, אחרי שהפונקציה הוסיפה 3 ל-5, הדקורטור עשה את חלקו והעלה את התוצאה בריבוע.

 

שרשור של מספר דקורטורים

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

def make_pretty(func):
    def inner():
        print("I am making it pretty")
        func()
    return inner

def add_smiley(func):
    def inner():
        print("😄 Adding a smiley")
        func()
    return inner

@make_pretty
@add_smiley
def greet():
    print("Hello, World!")

greet()

התוצאה:

I am making it pretty
😄 Adding a smiley
Hello, World!

נסביר:

  • בקוד לעיל היו לנו שתי פונקציות דקורטור: 'make_pretty()' ו- 'add_smiley()'. כל אחת מהם משנה את הפונקציה המקורית על ידי הוספת טקסט.
  • את הפונקציה 'greet()' עיטרנו באמצעות שתי הפונקציות על ידי זה שכתבנו את שתיהם מעל לפונקציה. בתחילת כל אחת מהם הוספנו את הסמל '@'.
  • פייתון עושה את השרשור לפי סדר הכתיבה של פונקציות הדקורציה. במקרה זה, קודם כל הוא מיישם את 'make_pretty()' כי כתבנו אותה ראשונה מעל ל-'add_smiley()' שהיא הבאה בתור ליישום.

 

דוגמה מועילה ליישום דקורטור: מדידת משך הריצה של פונקציה

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

import time


def timer(func):
   def wrapper(*args, **kwargs):
       # Get the current time before calling the function
       start_time = time.time()   
       # Call the original function
      
       result = func(*args, **kwargs)   
       # Get the current time after calling the function      
       end_time = time.time()  
       # Calculate the elapsed time
       elapsed_time = end_time - start_time
       # print the measurement to the console
       print(f"The function {func.__name__} took {elapsed_time} seconds to execute.")
       return result
   return wrapper

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

@timer
def square(n):
   return n**2


result = square(10)
print(result)

התוצאה:

The function square took 1.430511474609375e-06 seconds to execute.

נסביר:

  • יישמנו את פונקצית הדקורטור 'timer()' על הפונקציה 'square()' והיא מדדה את זמן הביצוע של הפונקציה 'square()'.
  • פונקצית הדקורטור 'timer()' מגדירה פונקציה 'wrapper()' בתוכה נעשית עבודה על הפונקציה המקורית. כדי למדוד את הזמן מריצים את הפונקציה 'time.time()' לפני ואחרי ביצוע הפונקציה המקורית, ובסוף מחשבים את הפרש הזמנים.
  • הפרש הזמנים מודפס יחד עם שם הפונקציה המקורית באמצעות התכונה 'func.__name__'.

הדקורטור '@timer' מאפשר לנו למדוד את משך הביצוע של כל פונקציה אם רק נוסיף אותו בתור דקורטור לפונקציה זו דוגמה לדרך שבה שימוש בדקורטרים יכול להפוך את הקוד שלנו לפשוט יותר לכתיבה ולקריאה.

 

לסיכום

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

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

 

מדריכים נוספים בסדרה ללימוד פייתון

ארבעת התחומים (scope) של פייתון

עבודה עם תאריכים וזמנים בפייתון

args ו-kwargs בפייתון מוסברים בשפה פשוטה

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

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

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

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

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

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

איך אומרים בעברית אינטרנט?