الوراثة في 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. فالكلب حيوان، وكل ما يستطيعه الحيوان يستطيعه الكلب.
إعادة تعريف التوابع
Section titled “إعادة تعريف التوابع”لكن الكلب لا يكتفي بأن «يصدر صوتاً»، بل ينبح. لذلك كتبنا في Dog تابعاً بالاسم نفسه speak. هذا يسمى إعادة التعريف (Overriding): عندما تجد Python التابع في الابن تستخدمه، وإذا لم تجده تبحث عنه في الأب.
بهذا نحصل على أفضل ما في الأمرين: الكود المشترك (eat) مكتوب مرة واحدة في الأب، وكل ابن يكتب فقط ما يميّزه.
super: استدعاء الأب
Section titled “super: استدعاء الأب”أحياناً لا تريد استبدال تابع الأب كله، بل تريد أن تضيف إليه. الدالة 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
Section titled “فحص النوع: isinstance”الدالة isinstance تخبرك هل الكائن من كلاس معيّن. والكائن المصنوع من الابن يُعتبر من نوع الأب أيضاً:
print(isinstance(dog, Dog))print(isinstance(dog, Animal))print(isinstance(dog, Cat))TrueTrueFalseالكلب Dog وهو كذلك Animal، لكنه ليس Cat.
جرّب بنفسك
Section titled “جرّب بنفسك”اكتب كلاساً أباً اسمه Shape فيه تابع area يعيد صفراً، وتابع describe يطبع «المساحة:» ثم ناتج area. ثم اكتب كلاسين يرثان منه: Rectangle له عرض وارتفاع، و Square له ضلع واحد، وكل منهما يعيد تعريف area. جرّب مستطيلاً أبعاده 4 و 5، ومربعاً ضلعه 3:
المساحة: 20المساحة: 9لاحظ أن describe مكتوبة في الأب فقط، ومع ذلك تعطي نتيجة صحيحة لكل شكل.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان القوسين واسم الأب:
class Dog:بدون(Animal)كلاس مستقل لا يرث شيئاً، وسيفشلdog.eat(). - كتابة
superبدون قوسين: الصحيحsuper().__init__(...)وليسsuper.__init__(...). - خطأ في اسم التابع المعاد تعريفه: إذا كتبت
speekبدلاً منspeakفي الابن، فلن يحدث خطأ، بل سيُستخدم تابع الأب بصمت. - سلاسل وراثة طويلة: كلاس يرث من كلاس يرث من ثالث ورابع يجعل الكود صعب الفهم. اكتفِ بمستوى أو مستويين.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
كيف نجعل الكلاس Car يرث من الكلاس Vehicle؟
في Python نكتب اسم الكلاس الأب بين قوسين بعد اسم الكلاس الابن.
سؤال
ما فائدة super().__init__(...) داخل الكلاس الابن؟
بها نعيد استخدام كود التجهيز المكتوب في الأب بدلاً من تكراره في الابن.
الخلاصة
Section titled “الخلاصة”- الوراثة تبني كلاساً جديداً فوق كلاس موجود:
class Child(Parent):. - الابن يرث كل خصائص الأب وتوابعه دون إعادة كتابتها.
- إعادة تعريف تابع بالاسم نفسه في الابن تغيّر سلوكه.
super()تستدعي توابع الأب، وأهمها__init__.isinstance(كائن, كلاس)تفحص النوع، والابن يُعتبر من نوع أبيه.