Class Methods & Static Methods — البرمجة الكائنية
دوال الصنف والدوال الساكنة
الدوال العادية في الصنف (instance methods) تعمل على كائن محدد عبر self. لكن بعض الدوال تخص الصنف كله، أو لا تحتاج أياً منهما.
1) دالة الكائن (الافتراضية)
def describe(self):
return f"بيتزا {self.size}"تحتاج كائناً، وتصل لخصائصه.
2) @classmethod: تعمل على الصنف
تستقبل cls (الصنف نفسه) بدل self. أشهر استخدام: طرق إنشاء بديلة (factory methods):
class Date:
def __init__(self, year, month, day):
self.year, self.month, self.day = year, month, day
@classmethod
def from_string(cls, text): # "2026-03-15"
y, m, d = map(int, text.split("-"))
return cls(y, m, d)
d = Date.from_string("2026-03-15")لماذا cls(...) وليس Date(...)؟ لأن صنفاً يرث من Date سيحصل على كائن من نوعه هو عند استدعاء from_string.
استخدام آخر: الوصول لخصائص الصنف المشتركة (مثل عدّاد الكائنات المنشأة).
3) @staticmethod: دالة عادية داخل الصنف
لا تستقبل self ولا cls. نضعها في الصنف لأنها مرتبطة به منطقياً:
class Date:
@staticmethod
def is_leap(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)كيف تختار؟
| تحتاج | استخدم |
|---|---|
| بيانات كائن محدد | دالة عادية (self) |
| إنشاء كائن بطريقة أخرى، أو بيانات الصنف | @classmethod (cls) |
| لا تحتاج أياً منهما لكنها تخص الصنف | @staticmethod |
إذا لم تكن الدالة مرتبطة بالصنف أصلاً، فالأفضل أن تكون دالة عادية في الوحدة خارج الصنف.
خصائص الصنف بحذر
class Pizza:
count = 0
def __init__(self):
Pizza.count += 1 # عدّل عبر الصنفself.count += 1 لا تعدّل خاصية الصنف، بل تنشئ خاصية جديدة على الكائن تخفي خاصية الصنف. خطأ شائع جداً.
تمرين
أنشئ صنف User بـ classmethod from_dict(data)، و staticmethod is_valid_email(email)، وخاصية صنف تعدّ المستخدمين المنشأين.
class Pizza:
# Class attribute
total_pizzas = 0
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
Pizza.total_pizzas += 1
# Instance method
def describe(self):
return f"بيتزا {self.size} مع {', '.join(self.toppings)}"
# Class method - Factory
@classmethod
def margherita(cls, size):
"""إنشاء مارغريتا جاهزة"""
return cls(size, ["جبن", "طماطم", "ريحان"])
@classmethod
def pepperoni(cls, size):
"""إنشاء بيبروني جاهزة"""
return cls(size, ["جبن", "بيبروني"])
@classmethod
def get_total(cls):
"""عدد البيتزا المصنوعة"""
return cls.total_pizzas
# Static method - Utility
@staticmethod
def is_valid_size(size):
"""تحقق من صحة الحجم"""
return size in ["صغير", "وسط", "كبير"]
@staticmethod
def calculate_price(size, num_toppings):
"""حساب السعر"""
base = {"صغير": 5, "وسط": 8, "كبير": 12}
return base.get(size, 0) + num_toppings * 1.5
# استخدام Instance method
p1 = Pizza("كبير", ["جبن", "فطر", "زيتون"])
print(p1.describe())
# استخدام Class method - Factory
p2 = Pizza.margherita("وسط")
p3 = Pizza.pepperoni("صغير")
print(p2.describe()) # بيتزا وسط مع جبن, طماطم, ريحان
# عدد البيتزا
print(f"عدد البيتزا: {Pizza.get_total()}") # 3
# استخدام Static method
print(Pizza.is_valid_size("كبير")) # True
print(Pizza.is_valid_size("ضخم")) # False
print(Pizza.calculate_price("وسط", 3)) # 12.5
# مثال: Date class
class Date:
def __init__(self, day, month, year):
self.day = day
self.month = month
self.year = year
@classmethod
def from_string(cls, date_string):
"""إنشاء من نص: '25-12-2024'"""
day, month, year = map(int, date_string.split('-'))
return cls(day, month, year)
@classmethod
def today(cls):
"""تاريخ اليوم"""
from datetime import date
t = date.today()
return cls(t.day, t.month, t.year)
@staticmethod
def is_leap_year(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
d1 = Date.from_string("25-12-2024")
d2 = Date.today()
print(Date.is_leap_year(2024)) # Trueاختبر فهمك