تعدد الأشكال والطرق السحرية
الدرسان السابقان غطيا "شكل" الفئات والوراثة. هذا الدرس يغطي "الروح." تعدد الأشكال يتيح استدعاء كائنات من فئات مختلفة بنفس الطريقة — لا تحتاج لمعرفة فئتها المحددة، فقط أن لديها الطريقة المطلوبة. الطرق السحرية تجعل الفئات المخصصة تتصرف بشكل طبيعي مثل الأنواع المدمجة.
1. ما هو تعدد الأشكال
تعدد الأشكال يعني "أشكال متعددة" — نفس الواجهة، تطبيقات مختلفة:
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Duck:
def speak(self):
return "Quack!"
# تعدد الأشكال: بغض النظر عن الفئة، طالما لديها طريقة speak()
animals = [Dog(), Cat(), Duck()]
for animal in animals:
print(animal.speak())
المخرجات:
Woof!
Meow!
Quack!
مثال: حساب مساحة الأشكال (الصعوبة ⭐⭐)
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# تعدد الأشكال — بغض النظر عن الشكل، فقط احسب المساحة
shapes = [Rectangle(3, 4), Circle(5), Triangle(6, 8)]
for shape in shapes:
print(f"Area: {shape.area():.2f}")
المخرجات:
Area: 12.00
Area: 78.54
Area: 24.00
2. الطرق السحرية: جعل الفئات أكثر "بايثونية"
الطرق السحرية هي طرق خاصة ببادئة ولاحقة من شرطتين سفليتين — __init__, __str__, __len__، إلخ. تجعل الفئات المخصصة تتصرف مثل الأنواع المدمجة.
__str__ و __repr__: طباعة أكثر ودية
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
"""تمثيل نصي سهل للمستخدم"""
return f"{self.name} ({self.age} years old)"
def __repr__(self):
"""تمثيل نصي للمطور"""
return f"Person('{self.name}', {self.age})"
p = Person("Zhang San", 25)
print(p) # Zhang San (25 years old) — يستدعي __str__
print(repr(p)) # Person('Zhang San', 25) — يستدعي __repr__
__len__: جعل الكائنات تدعم len()
class Team:
def __init__(self, members=None):
self.members = members or []
def add(self, member):
self.members.append(member)
def __len__(self):
return len(self.members)
def __str__(self):
return f"Team({', '.join(self.members)})"
team = Team()
team.add("Zhang San")
team.add("Li Si")
team.add("Wang Wu")
print(len(team)) # 3 — يستدعي __len__
print(team) # Team(Zhang San, Li Si, Wang Wu)
__getitem__: جعل الكائنات تدعم الفهرسة
class Playlist:
def __init__(self):
self.songs = []
def add(self, song):
self.songs.append(song)
def __getitem__(self, index):
return self.songs[index]
def __len__(self):
return len(self.songs)
playlist = Playlist()
playlist.add("Sunny Day")
playlist.add("Seven Li Fragrance")
playlist.add("Nocturne")
print(playlist[0]) # Sunny Day — يستدعي __getitem__
print(len(playlist)) # 3
# يدعم التقطيع والتكرار
for song in playlist:
print(song, end=" ") # Sunny Day Seven Li Fragrance Nocturne
3. مرجع الطرق السحرية الشائعة
class Demo:
def __init__(self, value): # المُنشئ
self.value = value
def __str__(self): # str() / print()
return str(self.value)
def __repr__(self): # repr() / تصحيح
return f"Demo({self.value})"
def __len__(self): # len()
return 1
def __bool__(self): # bool() / شرط if
return self.value != 0
def __eq__(self, other): # ==
return self.value == other.value
def __lt__(self, other): # < (للترتيب)
return self.value < other.value
def __add__(self, other): # عامل +
return Demo(self.value + other.value)
def __getitem__(self, key): # obj[key]
return key
مثال: فئة الكسر (الصعوبة ⭐⭐⭐)
class Fraction:
"""فئة الكسر — تدعم الجمع"""
def __init__(self, numerator, denominator=1):
if denominator == 0:
raise ValueError("Denominator cannot be zero")
self.numerator = numerator
self.denominator = denominator
def __str__(self):
if self.denominator == 1:
return str(self.numerator)
return f"{self.numerator}/{self.denominator}"
def __add__(self, other):
new_den = self.denominator * other.denominator
new_num = self.numerator * other.denominator + other.numerator * self.denominator
return Fraction(new_num, new_den)
def __eq__(self, other):
return (self.numerator * other.denominator) == (other.numerator * self.denominator)
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1) # 1/3
print(f1 + f2) # 9/18
print(Fraction(1, 2) == Fraction(2, 4)) # True
4. @dataclass: تعريف سريع لفئات البيانات
كتابة فئة فقط لتخزين البيانات، مع __init__ و __repr__ و __eq__ يدوياً، أمر ممل. @dataclass يُنشئها تلقائياً:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Student:
name: str
age: int
score: float = 0.0 # قيمة افتراضية
# يحصل تلقائياً على __init__, __repr__, __eq__
p1 = Point(3.0, 4.0)
p2 = Point(3.0, 4.0)
print(p1) # Point(x=3.0, y=4.0)
print(p1 == p2) # True (يقارن جميع الحقول تلقائياً)
s = Student("Zhang San", 20, 85.5)
print(s) # Student(name='Zhang San', age=20, score=85.5)
@dataclass: 1) فئة تخزن بشكل أساسي بيانات مع عدد قليل من الطرق. 2) تحتاج كوداً أساسياً مثل __init__, __repr__, __eq__. 3) لا تريد كتابة هذه يدوياً. للحالات البسيطة، إنها أكثر كفاءة بكثير من كتابة الفئات يدوياً.
حالات الاستخدام الشائعة
- كتابة البط:
len()وfor...inوsorted()في بايثون كلها قائمة على البروتوكول — نفذ الطريقة المقابلة وفصلك يمكنه استخدامها. - حاويات البيانات: نفذ
__getitem__+__len__لجعل فصلك يدعم الفهرسة والتكرار. - تحميل العوامل:
__add__,__eq__,__lt__تتيح للفئات المخصصة دعم+,==,<إلخ. - فئات البيانات:
@dataclassلتعريف سريع للتهيئات، DTOs، كائنات القيمة.
❓ أسئلة شائعة
print() تستدعي __str__، len() تستدعي __len__، == تستدعي __eq__. لا تستدعيها مباشرة؛ فقط نفّذها، وبايثون تستدعيها تلقائياً في الوقت المناسب.
س:ما الفرق بين @dataclass والفئة العادية؟ ج:@dataclass يُنشئ تلقائياً الطرق التي سئمت كتابتها. إنه مجرد مولد كود — الفئة المُنشأة مطابقة لفئة مكتوبة يدوياً. الفئات المُزيّنة بـ @dataclass يمكنها أيضاً أن يكون لها طرقها الخاصة. إذا وجدت نفسك تكرر كتابة __init__ لتخزين الخصائص، فقد حان وقت @dataclass.
س:بايثون مكتوبة ديناميكياً — هل يأتي تعدد الأشكال بشكل طبيعي؟ ج:نعم. "كتابة البط" في بايثون تجعل تعدد الأشكال ممكناً دون وراثة — فقط وجود طرق بنفس الاسم كافٍ. اللغات الأخرى (مثل Java/C++) تحتاج واجهات أو فئات مجردة لتعدد الأشكال. الفائدة هي المرونة؛ الجانب السلبي هو أن التحقق من الأنواع لا يلتقط الأخطاء بسهولة.📖 ملخص
- تعدد الأشكال: كائنات من فئات مختلفة بنفس اسم الطريقة يمكن استدعاؤها بنفس الطريقة
- كتابة البط: "إذا بصق، فهو بط" — يهتم بالطرق، وليس الأنواع
- الطرق السحرية:
__str__(طباعة)،__len__(طول)،__getitem__(فهرسة)،__eq__(مساواة)،__add__(جمع)، إلخ. @dataclassيُنشئ تلقائياً__init__,__repr__,__eq__— مثالي لفئات البيانات البحتة
📝 تمارين
-
مبتدئ (الصعوبة ⭐): عرّف فئة
Bookتنفذ__str__و__len__(تُرجع عدد الصفحات). أنشئ كتابين وأخرجprint(book)وlen(book). -
متوسط (الصعوبة ⭐⭐): استخدم
@dataclassلتعريف فئةProduct(الاسم، السعر، الكمية). نفذ__add__— ادمج منتجين بنفس الاسم (اجمع الكميات، وسّط السعر). تلميح: نفذ داخل فئة @dataclass. -
متقدم (الصعوبة ⭐⭐⭐): عرّف فئة
Deck(مجموعة أوراق لعب) تنفذ__len__،__getitem__(تدعم الفهرسة والتقطيع)، وshuffle()(باستخدامrandom.shuffle). ثم اكتب كوداً لإنشاء مجموعة وتوزيع أول 5 أوراق كيد.



