9.5. الوراثة Inheritance #
بالطبع، لا تستحق ميزة اللغة اسم “class” دون دعم الوراثة. تبدو صيغة تعريف الفئة المشتقة كما يلي:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
يجب تعريف اسم BaseClassName في نطاق يحتوي على تعريف الفئة المشتقة. بدلاً من اسم الفئة الأساسية، يُسمح أيضًا باستخدام تعبيرات عشوائية أخرى. قد يكون هذا مفيدًا، على سبيل المثال، عند تعريف الفئة الأساسية في وحدة نمطية أخرى:
class DerivedClassName(modname.BaseClassName):
يجري تنفيذ تعريف الفئة المشتقة بنفس طريقة تنفيذ الفئة الأساسية. عند إنشاء كائن الفئة، يتم تذكر الفئة الأساسية. يُستخدم هذا لحل مراجع السمات: إذا لم يتم العثور على السمة المطلوبة في الفئة، ينتقل البحث للبحث في الفئة الأساسية. تُطبق هذه القاعدة تكراريًا إذا كانت الفئة الأساسية نفسها مشتقة من فئة أخرى.
لا يوجد ما يميز إنشاء مثيلات للفئات المشتقة: تُنشئ دالة DerivedClassName() مثيلًا جديدًا للفئة. تُحل مراجع الدالة كما يلي: يتم البحث عن سمة الفئة المقابلة، مع النزول إلى أسفل سلسلة الفئات الأساسية إذا لزم الأمر، ويكون مرجع الدالة صالحًا إذا نتج عن ذلك كائن دالة.
قد تتجاوز الفئات المشتقة دوال فئاتها الأساسية. ولأن الدوال لا تتمتع بامتيازات خاصة عند استدعاء دوال أخرى لنفس الكائن، فإن دالة فئة أساسية تستدعي دالة أخرى مُعرّفة في نفس الفئة الأساسية قد تنتهي باستدعاء دالة فئة مشتقة تتجاوزها. (بالنسبة لمبرمجي C++: جميع الدوال في بايثون افتراضية فعليًا).
قد ترغب دالة التجاوز في فئة مشتقة في توسيع دالة الفئة الأساسية التي تحمل الاسم نفسه بدلاً من استبدالها ببساطة. هناك طريقة بسيطة لاستدعاء دالة الفئة الأساسية مباشرةً: ما عليك سوى استدعاء BaseClassName.methodname(self, arguments). هذا مفيد أحيانًا للعملاء أيضًا. (لاحظ أن هذا يعمل فقط إذا كان من الممكن الوصول إلى الفئة الأساسية باسم BaseClassName في النطاق العام.)
تحتوي بايثون على دالتين مدمجتين تعملان مع الوراثة:
- استخدم دالة isinstance() للتحقق من نوع المثيل: ستكون دالة isinstance(obj, int) صحيحة فقط إذا كان obj.__class__ عددًا صحيحًا أو فئة مشتقة من int.
- استخدم دالة issubclass() للتحقق من وراثة الفئة: ستكون دالة issubclass(bool, int) صحيحة لأن bool فئة فرعية من int. ومع ذلك، ستكون دالة issubclass(float, int) خاطئة لأن float ليست فئة فرعية من int.
9.5.1. الوراثة المتعددة #
تدعم بايثون أيضًا شكلًا من أشكال الوراثة المتعددة. يبدو تعريف الفئة مع فئات أساسية متعددة كما يلي:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
في معظم الحالات، وفي أبسطها، يُمكن اعتبار البحث عن السمات الموروثة من فئة رئيسية بمثابة بحث من العمق أولاً، من اليسار إلى اليمين، وليس البحث مرتين في نفس الفئة حيث يوجد تداخل في التسلسل الهرمي. وبالتالي، إذا لم تُعثر على سمة في DerivedClassName، فسيتم البحث عنها في Base1، ثم (بشكل متكرر) في فئات Base1 الأساسية، وإذا لم تُعثر عليها هناك، فسيتم البحث عنها.في Base2، وهكذا.
في الواقع، الأمر أكثر تعقيدًا من ذلك بقليل؛ إذ يتغير ترتيب حل الدالة ديناميكيًا لدعم الاستدعاءات التعاونية لـ super(). يُعرف هذا النهج في بعض لغات الوراثة المتعددة الأخرى باسم call-next-method، وهو أقوى من الاستدعاء super الموجود في لغات الوراثة المفردة.
يُعد الترتيب الديناميكي ضروريًا لأن جميع حالات الوراثة المتعددة تُظهر علاقة ماسية واحدة أو أكثر (حيث يمكن الوصول إلى فئة رئيسية واحدة على الأقل عبر مسارات متعددة من الفئة الأدنى). على سبيل المثال، ترث جميع الفئات من الكائن، لذا فإن أي حالة وراثة متعددة توفر أكثر من مسار واحد للوصول إلى الكائن. لمنع الوصول إلى الفئات الأساسية أكثر من مرة، تُخَطِّط الخوارزمية الديناميكية ترتيب البحث بطريقة تحافظ على الترتيب من اليسار إلى اليمين المحدد في كل فئة، والتي تستدعي كل فئة رئيسية مرة واحدة فقط، وتكون رتيبة (أي أنه يمكن إنشاء فئات فرعية للفئة دون التأثير على ترتيب أولوية الفئات الرئيسية). عند جمع هذه الخصائص، يُمكن تصميم فئات موثوقة وقابلة للتوسيع مع وراثة متعددة.