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

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


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__، إلخ. تجعل الفئات المخصصة تتصرف مثل الأنواع المدمجة.

(1) __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__

(2) __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)

(3) __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
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
# Function defined successfully

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

PYTHON
import math

class Shape:
    def area(self):
        raise NotImplementedError

    def perimeter(self):
        raise NotImplementedError

    def __str__(self):
        return f"{self.__class__.__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}"

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)

shapes = [Rectangle(3, 4), Circle(5), Square(6)]

total_area = 0
for shape in shapes:
    print(shape)
    total_area += shape.area()

print(f"\nTotal area: {total_area:.2f}")
▶ جرّب الكود

المخرجات:

TEXT 📖 للعرض فقط
Rectangle: area=12.00, perimeter=14.00
Circle: area=78.54, perimeter=31.42
Square: area=36.00, perimeter=24.00

Total area: 126.54


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



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



❓ أسئلة شائعة

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

📖 ملخص


📝 تمارين

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

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

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

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%