الدوال السحرية (Magic Methods) — البرمجة الكائنية
الدوال السحرية (Magic / Dunder Methods)
الدوال التي يبدأ اسمها وينتهي بشرطتين سفليتين (__init__, __str__) تسمى dunder methods. لا تستدعيها بنفسك عادة؛ Python تستدعيها تلقائياً عند استخدام العمليات المدمجة. بتعريفها تجعل أصنافك تتصرف مثل الأنواع المدمجة.
التمثيل النصي
| الدالة | متى تُستدعى | لمن |
|---|---|---|
| __str__ | print(obj)، str(obj) | للمستخدم: نص مقروء |
| __repr__ | في الطرفية، داخل القوائم، repr(obj) | للمبرمج: يوضح كيف أُنشئ الكائن |
def __repr__(self):
return f"Money({self.amount!r}, {self.currency!r})"إذا عرّفت واحدة فقط، فعرّف __repr__: Python تستخدمها بديلاً عن __str__.
المقارنة
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented
return (self.amount, self.currency) == (other.amount, other.currency)بدون __eq__، تقارن == هوية الكائنين (هل هما نفس الكائن في الذاكرة) وليس محتواهما. إرجاع NotImplemented يخبر Python أن هذه المقارنة غير مدعومة لهذا النوع.
__lt__ (أصغر من) تجعل sorted() و min() تعمل مع كائناتك.
العمليات الحسابية
def __add__(self, other):
return Money(self.amount + other.amount, self.currency)الآن price + tax تعمل. يجب أن ترجع كائناً جديداً لا أن تعدّل self.
سلوك الحاويات
| الدالة | تُفعّل |
|---|---|
| __len__ | len(obj) |
| __getitem__ | obj[i] وأيضاً التكرار بـ for |
| __contains__ | x in obj |
| __iter__ | for x in obj |
__bool__
تحدد إن كان الكائن "صحيحاً" في الشروط: if cart:.
قاعدة مهمة
عرّف الدوال السحرية التي يتوقعها المستخدم منطقياً فقط. جمع نقطتين منطقي، أما جمع موظفين فغالباً ليس كذلك.
تمرين
أنشئ صنف Vector (x, y) مع __repr__ و __add__ و __eq__ و __abs__ (الطول)، وصنف Playlist يدعم len() و in والتكرار.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
# للطباعة العادية
def __str__(self):
return f"{self.title} - {self.author}"
# للتمثيل التقني
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages})"
# len()
def __len__(self):
return self.pages
# المقارنة
def __eq__(self, other):
return self.title == other.title and self.author == other.author
def __lt__(self, other):
return self.pages < other.pages
# الجمع (دمج كتابين)
def __add__(self, other):
return Book(
f"{self.title} & {other.title}",
f"{self.author}, {other.author}",
self.pages + other.pages
)
book1 = Book("Python 101", "أحمد", 200)
book2 = Book("Python 201", "محمد", 300)
print(book1) # Python 101 - أحمد
print(repr(book1)) # Book('Python 101', 'أحمد', 200)
print(len(book1)) # 200
print(book1 < book2) # True
print(book1 + book2) # Python 101 & Python 201
# class يتصرف كـ list
class Playlist:
def __init__(self, name):
self.name = name
self._songs = []
def __len__(self):
return len(self._songs)
def __getitem__(self, index):
return self._songs[index]
def __setitem__(self, index, value):
self._songs[index] = value
def __iter__(self):
return iter(self._songs)
def __contains__(self, song):
return song in self._songs
def add(self, song):
self._songs.append(song)
playlist = Playlist("مفضلتي")
playlist.add("أغنية 1")
playlist.add("أغنية 2")
playlist.add("أغنية 3")
print(len(playlist)) # 3
print(playlist[0]) # أغنية 1
print("أغنية 2" in playlist) # True
for song in playlist:
print(song)اختبر فهمك