تخطَّ إلى المحتوى

الوراثة في Python: بناء كلاس من كلاس آخر

الدرس 20 من 25 · Python

في الدرس السابق تعلمت كيف تكتب كلاساً. لكن ماذا لو احتجت كلاسين متشابهين جداً، مثل «موظف» و«مدير»؟ هل تنسخ الكود مرتين؟ بالطبع لا. الوراثة (Inheritance) تسمح لك أن تبني كلاساً جديداً فوق كلاس موجود، فيأخذ كل ما فيه، ثم تضيف إليه أو تغيّر فيه ما تريد.

الفكرة: الابن يرث من الأب

Section titled “الفكرة: الابن يرث من الأب”

الطفل يرث من والديه لون العينين وشكل الوجه، ثم تكون له صفاته الخاصة. في البرمجة نسمي الكلاس الأصلي الكلاس الأب (Parent Class)، ونسمي الكلاس الذي يرث منه الكلاس الابن (Child Class).

لتجعل كلاساً يرث من آخر، اكتب اسم الأب بين قوسين بعد اسم الابن:

class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} يأكل")
def speak(self):
print(f"{self.name} يصدر صوتاً")
class Dog(Animal):
def speak(self):
print(f"{self.name} ينبح: هو هو")
class Cat(Animal):
def speak(self):
print(f"{self.name} يموء: مياو")
dog = Dog("ركس")
dog.eat()
dog.speak()
Cat("مشمش").speak()
الناتج
ركس يأكل
ركس ينبح: هو هو
مشمش يموء: مياو

انظر إلى كلاس Dog: لم نكتب فيه __init__ ولا eat، ومع ذلك عملا! لقد ورثهما من Animal. فالكلب حيوان، وكل ما يستطيعه الحيوان يستطيعه الكلب.

لكن الكلب لا يكتفي بأن «يصدر صوتاً»، بل ينبح. لذلك كتبنا في Dog تابعاً بالاسم نفسه speak. هذا يسمى إعادة التعريف (Overriding): عندما تجد Python التابع في الابن تستخدمه، وإذا لم تجده تبحث عنه في الأب.

بهذا نحصل على أفضل ما في الأمرين: الكود المشترك (eat) مكتوب مرة واحدة في الأب، وكل ابن يكتب فقط ما يميّزه.

أحياناً لا تريد استبدال تابع الأب كله، بل تريد أن تضيف إليه. الدالة super() تعطيك الوصول إلى الأب لتستدعي توابعه:

class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def describe(self):
return f"{self.name} - الراتب {self.salary}"
class Manager(Employee):
def __init__(self, name, salary, team_size):
super().__init__(name, salary)
self.team_size = team_size
def describe(self):
return super().describe() + f" - يدير {self.team_size} موظفين"
boss = Manager("هدى", 9000, 5)
print(boss.describe())
print(boss.name)
الناتج
هدى - الراتب 9000 - يدير 5 موظفين
هدى

المدير له خاصية إضافية هي حجم الفريق، فاحتجنا __init__ جديدة. وبدلاً من تكرار سطري self.name و self.salary، استدعينا super().__init__(name, salary) ليجهّزهما الأب، ثم أضفنا الخاصية الجديدة. وفعلنا الشيء نفسه في describe: أخذنا نص الأب وزدنا عليه.

كائنات مختلفة وتعامل واحد

Section titled “كائنات مختلفة وتعامل واحد”

من أجمل نتائج الوراثة أنك تستطيع التعامل مع كائنات من كلاسات مختلفة بالطريقة نفسها، وكل كائن يتصرف حسب نوعه:

animals = [Dog("ركس"), Cat("مشمش"), Animal("سلحفاة")]
for animal in animals:
animal.speak()
الناتج
ركس ينبح: هو هو
مشمش يموء: مياو
سلحفاة يصدر صوتاً

الحلقة لا تسأل عن نوع كل حيوان، بل تطلب منه أن «يتكلم» فقط. وإذا أضفت غداً كلاس Bird، فستعمل الحلقة معه دون تغيير حرف واحد. هذه الفكرة اسمها تعدد الأشكال (Polymorphism).

الدالة isinstance تخبرك هل الكائن من كلاس معيّن. والكائن المصنوع من الابن يُعتبر من نوع الأب أيضاً:

print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
print(isinstance(dog, Cat))
الناتج
True
True
False

الكلب Dog وهو كذلك Animal، لكنه ليس Cat.

اكتب كلاساً أباً اسمه Shape فيه تابع area يعيد صفراً، وتابع describe يطبع «المساحة:» ثم ناتج area. ثم اكتب كلاسين يرثان منه: Rectangle له عرض وارتفاع، و Square له ضلع واحد، وكل منهما يعيد تعريف area. جرّب مستطيلاً أبعاده 4 و 5، ومربعاً ضلعه 3:

الناتج
المساحة: 20
المساحة: 9

لاحظ أن describe مكتوبة في الأب فقط، ومع ذلك تعطي نتيجة صحيحة لكل شكل.

  • نسيان القوسين واسم الأب: class Dog: بدون (Animal) كلاس مستقل لا يرث شيئاً، وسيفشل dog.eat().
  • كتابة super بدون قوسين: الصحيح super().__init__(...) وليس super.__init__(...).
  • خطأ في اسم التابع المعاد تعريفه: إذا كتبت speek بدلاً من speak في الابن، فلن يحدث خطأ، بل سيُستخدم تابع الأب بصمت.
  • سلاسل وراثة طويلة: كلاس يرث من كلاس يرث من ثالث ورابع يجعل الكود صعب الفهم. اكتفِ بمستوى أو مستويين.

سؤال

كيف نجعل الكلاس Car يرث من الكلاس Vehicle؟

سؤال

ما فائدة super().__init__(...) داخل الكلاس الابن؟

  • الوراثة تبني كلاساً جديداً فوق كلاس موجود: class Child(Parent):.
  • الابن يرث كل خصائص الأب وتوابعه دون إعادة كتابتها.
  • إعادة تعريف تابع بالاسم نفسه في الابن تغيّر سلوكه.
  • super() تستدعي توابع الأب، وأهمها __init__.
  • isinstance(كائن, كلاس) تفحص النوع، والابن يُعتبر من نوع أبيه.