2.23. מעטרים

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

@wrapper
def f():
    ...

השורה @wrapper שקולה ל-f = wrapper(f) – הפונקציה f נבנית כרגיל, אחר כך נמסרת ל-wrapper, והתוצאה נקשרת מחדש לשם f.

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

מעטר מקבל פונקציה ומחזיר פונקציה חדשה.

2.23.1. מעטרי מתודות מובנים

כמה מעטרים מגיעים עם Python ומשמשים בתוך גופי מחלקות.

2.23.1.1. @property

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

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

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

2.23.1.2. @classmethod

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

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

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

2.23.1.3. @staticmethod

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

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

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

2.23.2. כתיבת מעטר מותאם אישית

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

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

פלט:

calling add

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