לדלג לתוכן

דקורטורים בפייתון מוסברים בפשטות - decorators בלי בלבול

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

קודם כל - פונקציות הן ערכים

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

def greet():
    return "שלום"

say_hello = greet
print(say_hello())

זה נראה טריוויאלי, אבל זה בדיוק מה שהופך דקורטורים לאפשריים.

עכשיו, פונקציה שעוטפת פונקציה

def shout(func):
    def wrapper():
        result = func()
        return result.upper()
    return wrapper

def greet():
    return "hello"

loud_greet = shout(greet)
print(loud_greet())  # HELLO

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

והנה הקסם - התחביר של @

במקום לכתוב greet = shout(greet) בכל פעם, פייתון נותנת קיצור נוח:

def shout(func):
    def wrapper():
        result = func()
        return result.upper()
    return wrapper

@shout
def greet():
    return "hello"

print(greet())  # HELLO

השורה @shout מעל ההגדרה של greet היא בדיוק שקולה ל-greet = shout(greet). זהו, זה כל הסוד. שום קסם, רק תחביר נוח לדבר שכבר יכולנו לעשות.

דוגמה שימושית באמת - מדידת זמן ריצה

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

import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} לקח {end - start:.4f} שניות")
        return result
    return wrapper

@measure_time
def slow_function():
    time.sleep(1)
    return "סיימתי"

slow_function()

שימו לב ל-*args ו-**kwargs בתוך ה-wrapper. זה מה שמאפשר לדקורטור לעבוד על כל פונקציה, לא משנה כמה פרמטרים היא מקבלת. בלי זה, הדקורטור היה עובד רק על פונקציות בלי שום פרמטר.

למה בכלל להשתמש בדקורטורים

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

זה גם בדיוק איך פועלים כלים מוכרים כמו Flask, שבו @app.route("/home") הוא דקורטור שאומר "הפונקציה הזאת מטפלת בכתובת הזאת".

הטעות הנפוצה של מתחילים

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

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

לסיכום

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

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

העמיקו בדקורטורים ובנושאים מתקדמים בקורס תכנות בסיסי

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

הצטרפו לקהילה בדיסקורד