تعدد الأشكال والطرق السحرية

الدرسان السابقان غطيا "شكل" الفئات والوراثة. هذا الدرس يغطي "الروح." تعدد الأشكال يتيح استدعاء كائنات من فئات مختلفة بنفس الطريقة — لا تحتاج لمعرفة فئتها المحددة، فقط أن لديها الطريقة المطلوبة. الطرق السحرية تجعل الفئات المخصصة تتصرف بشكل طبيعي مثل الأنواع المدمجة.


1. ما هو تعدد الأشكال

تعدد الأشكال يعني "أشكال متعددة" — نفس الواجهة، تطبيقات مختلفة:

PYTHON
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())

المخرجات:

TEXT
Woof!
Meow!
Quack!
💡 كتابة البط: "إذا مشى كبط وبصق كبط، فهو بط." لا تهتم بايثون بنوع الكائن — فقط ما إذا كان لديه الطريقة التي تحتاجها. هذا واحد من أكثر تصاميم بايثون مرونة.

مثال: حساب مساحة الأشكال (الصعوبة ⭐⭐)

PYTHON
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}")
▶ جرّب الكود

المخرجات:

TEXT
Area: 12.00
Area: 78.54
Area: 24.00

2. الطرق السحرية: جعل الفئات أكثر "بايثونية"

الطرق السحرية هي طرق خاصة ببادئة ولاحقة من شرطتين سفليتين — __init__, __str__, __len__، إلخ. تجعل الفئات المخصصة تتصرف مثل الأنواع المدمجة.

__str__ و __repr__: طباعة أكثر ودية

PYTHON
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()

PYTHON
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__: جعل الكائنات تدعم الفهرسة

PYTHON
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. مرجع الطرق السحرية الشائعة

PYTHON
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

مثال: فئة الكسر (الصعوبة ⭐⭐⭐)

PYTHON
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 يُنشئها تلقائياً:

PYTHON
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) لا تريد كتابة هذه يدوياً. للحالات البسيطة، إنها أكثر كفاءة بكثير من كتابة الفئات يدوياً.


حالات الاستخدام الشائعة


❓ أسئلة شائعة

س ما الفرق بين الطرق السحرية والطرق العادية؟
ج الطرق السحرية تُستدعى بواسطة عمليات بايثون المدمجة — print() تستدعي __str__، len() تستدعي __len__، == تستدعي __eq__. لا تستدعيها مباشرة؛ فقط نفّذها، وبايثون تستدعيها تلقائياً في الوقت المناسب. س:ما الفرق بين @dataclass والفئة العادية؟ ج:@dataclass يُنشئ تلقائياً الطرق التي سئمت كتابتها. إنه مجرد مولد كود — الفئة المُنشأة مطابقة لفئة مكتوبة يدوياً. الفئات المُزيّنة بـ @dataclass يمكنها أيضاً أن يكون لها طرقها الخاصة. إذا وجدت نفسك تكرر كتابة __init__ لتخزين الخصائص، فقد حان وقت @dataclass. س:بايثون مكتوبة ديناميكياً — هل يأتي تعدد الأشكال بشكل طبيعي؟ ج:نعم. "كتابة البط" في بايثون تجعل تعدد الأشكال ممكناً دون وراثة — فقط وجود طرق بنفس الاسم كافٍ. اللغات الأخرى (مثل Java/C++) تحتاج واجهات أو فئات مجردة لتعدد الأشكال. الفائدة هي المرونة؛ الجانب السلبي هو أن التحقق من الأنواع لا يلتقط الأخطاء بسهولة.

📖 ملخص

  • تعدد الأشكال: كائنات من فئات مختلفة بنفس اسم الطريقة يمكن استدعاؤها بنفس الطريقة
  • كتابة البط: "إذا بصق، فهو بط" — يهتم بالطرق، وليس الأنواع
  • الطرق السحرية: __str__ (طباعة)، __len__ (طول)، __getitem__ (فهرسة)، __eq__ (مساواة)، __add__ (جمع)، إلخ.
  • @dataclass يُنشئ تلقائياً __init__, __repr__, __eq__ — مثالي لفئات البيانات البحتة

📝 تمارين

  1. مبتدئ (الصعوبة ⭐): عرّف فئة Book تنفذ __str__ و __len__ (تُرجع عدد الصفحات). أنشئ كتابين وأخرج print(book) و len(book).

  2. متوسط (الصعوبة ⭐⭐): استخدم @dataclass لتعريف فئة Product (الاسم، السعر، الكمية). نفذ __add__ — ادمج منتجين بنفس الاسم (اجمع الكميات، وسّط السعر). تلميح: نفذ داخل فئة @dataclass.

  3. متقدم (الصعوبة ⭐⭐⭐): عرّف فئة Deck (مجموعة أوراق لعب) تنفذ __len__، __getitem__ (تدعم الفهرسة والتقطيع)، و shuffle() (باستخدام random.shuffle). ثم اكتب كوداً لإنشاء مجموعة وتوزيع أول 5 أوراق كيد.

100%