לדלג לתוכן

static methods מול instance methods בפייתון - מתי משתמשים במה

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

המתודה הרגילה - instance method

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

class Order:
    def __init__(self, item, price):
        self.item = item
        self.price = price

    def describe(self):
        return f"הזמנה: {self.item}, מחיר: {self.price}"

כשקוראים ל-order.describe(), פייתון מעביר את order עצמו כארגומנט self באופן אוטומטי. זו הצורה הכי נפוצה, כי רוב המתודות במחלקה צריכות לגשת לנתונים של האובייקט הספציפי שעליו הן פועלות.

מתי צריך משהו אחר

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

מתודה סטטית - staticmethod

מתודה עם @staticmethod לא מקבלת לא self ולא cls. היא בעצם פונקציה רגילה, שפשוט "גרה" בתוך המחלקה כי היא קשורה אליה לוגית:

class Order:
    def __init__(self, item, price):
        self.item = item
        self.price = price

    def describe(self):
        return f"הזמנה: {self.item}, מחיר: {self.price}"

    @staticmethod
    def calculate_tax(price, tax_rate=0.17):
        return price * tax_rate

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

tax = Order.calculate_tax(100)

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

מתודת מחלקה - classmethod

מתודה עם @classmethod מקבלת את cls - הפניה למחלקה עצמה, לא לאובייקט ספציפי. השימוש הנפוץ ביותר הוא יצירת אובייקטים בדרכים חלופיות, מה שנקרא "constructor חלופי":

class Order:
    def __init__(self, item, price):
        self.item = item
        self.price = price

    def describe(self):
        return f"הזמנה: {self.item}, מחיר: {self.price}"

    @staticmethod
    def calculate_tax(price, tax_rate=0.17):
        return price * tax_rate

    @classmethod
    def from_string(cls, order_string):
        item, price = order_string.split(",")
        return cls(item.strip(), float(price.strip()))

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

order = Order.from_string("מקלדת, 150")
print(order.describe())

השימוש ב-cls במקום כתיבה מפורשת של Order(...) חשוב במיוחד כשיש ירושה - אם מחלקה אחרת יורשת מ-Order, הקריאה ל-cls(...) תיצור אובייקט מהמחלקה היורשת הנכונה, ולא תמיד מ-Order עצמה.

שלושתם יחד, בכלל אצבע

  • instance method - כשהמתודה צריכה לגשת או לשנות נתונים של אובייקט ספציפי. זה המצב הנפוץ ביותר, ברירת המחדל.
  • staticmethod - כשהמתודה היא בעצם פונקציית עזר עצמאית, שרק במקרה קשורה לוגית למחלקה, ולא נוגעת לא באובייקט ולא במחלקה.
  • classmethod - כשהמתודה צריכה לדעת על המחלקה עצמה, בעיקר כדי ליצור אובייקטים בדרך חלופית, או לגשת למשתנים ששייכים למחלקה כולה ולא לאובייקט ספציפי.

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

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

לסיכום

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

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