وراثة الأصناف في بايثون

من موسوعة حسوب

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

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

يجب أن يكون الاسم BaseClassName معرّفًا في النطاق الذي يحتوي تعريف الصنف المشتق. ويمكن استخدام أي تعبير محلّ اسم الصنف الأساسي، ويمكن لهذا أن يكون مفيدًا عندما يكون الصنف الأساسي معرّفًا في وحدة أخرى على سبيل المثال:

class DerivedClassName(modname.BaseClassName):

يجري تنفيذ تعريف الصنف المشتق بنفس الطريقة المتبعة في الصنف الأساسي، وعند إنشاء كائن الصنف تتذكّر اللغة الصنف الأساسي. تستخدم هذه الميزة لتحليل الإشارات إلى الخاصيات، فعندما تكون الخاصية المطلوبة غير موجودة في الصنف، تنتقل اللغة للبحث عنها في الصنف الأساسي، وتنطبق هذه القاعدة مرة أخرى إن كان الصنف الأساسي مشتقًا من صنف آخر. لا توجد صيغة خاصة لتمثيل الأصناف المشتقة، بل تستخدم الصيغة الاعتيادية:

DerivedClassName()

تنشئ العبارة السابقة نسخة جديدة من الصنف.

أما تحليل الإشارات إلى التوابع فيجري بالصورة التالية: تبحث اللغة عن خاصية الصنف المقابلة نزولًا إلى سلسلة الأصناف الأساسية إن اقتضت الحاجة، وتكون الإشارة إلى التابع صالحة إن نتج عن عملية البحث هذه كائن دالة (function object).

يمكن للأصناف المشتقة أن تعيد تعريف توابع الأصناف الأساسية، ولما كانت التوابع لا تمتلك أي امتيازات خاصة عند استدعاء توابع أخرى في الكائن نفسه، فقد يؤدي استدعاء تابع في الصنف الأساسي وظيفته استدعاء تابع آخر في نفس الكائن، إلى استدعاء تابع في الصنف المشتق (يعيد تعريف التابع الأساسي). (إن كنت خبيرًا في لغة C++‎ فإنّ جميع التوابع في بايثون هي ظاهرية virtual).

تميل التوابع المشتقة إلى إعادة تعريف التوابع الأساسية بدلًا من استبدال التابع الأساسي الذي يحمل الاسم نفسه.

يمكن استدعاء التابع الأساسي بصورة مباشرة عن طريق عبارة الاستدعاء:

BaseClassName.methodname(self, arguments)

قد تكون هذه الطريقة مفيدة عند استخدام الشيفرة من قبل أشخاص آخرين. (لاحظ أنّ عبارة الاستدعاء هذه صالحة فقط عندما يكون بالإمكان الوصول إلى الصنف الأساسي باستخدام الاسم BaseClassName ضمن النطاق العام).

تقدّم بايثون دالتين داخليتين (built-in) يمكن الاستفادة منهما في موضوع الوراثة:

  • تستخدم الدالة isinstance()‎ للتحقّق من نوع النسخة: لا يعطي التعبير isinstance(obj, int)‎ النتيجة True إلا إذا كان obj.__class__‎ من نوع int أو أي صنف مشتقّ من هذا النوع.
  • تستخدم الدالة issubclass()‎ للتحقق من وراثة الصنف: يعطي التعبير issubclass(bool, int)‎ النتيجة True لأنّ النوع bool هو صنف فرعي من النوع int . أما التعبير issubclass(float, int)‎ فيعطي النتيجة False لأنّ float ليس صنفًا فرعيًا من int.

الوراثة المتعددة

تدعم بايثون كذلك شكلًا من أشكال الوراثة المتعددة، إذ يمكن تعريف صنف مع استخدام عدد من الأصناف الأساسية وكما موضح في المثال التالي:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

في معظم الحالات يمكن القول أن عملية البحث عن الخاصيات الموروثة من الصنف الأب تجري حسب بُعْدِ الصنف الأب عن الصنف الابن، ومن اليسار إلى اليمين، ولا يجري البحث مرتين في الصنف الواحد إذا كان هناك إعادة تعريف ضمن التسلسل الهرمي للأصناف المورَِّثة. وهذا يعني أنّه إن لم تفلح اللغة في العثور على الخاصية في الصنف المشتق DerivedClassName فستبحث عنها في الصنف الأساسي Base1، وإن لم تعثر عليها هناك فستبحث عنها في الصنف الأساسي Base2 وهكذا دواليك.

الأمر أكثر تعقيدًا ممّا سبق في الواقع، فترتيب عملية تحليل التوابع يتغيّر بصورة ديناميكية لغرض دعم الاستدعاءات المشتركة للتابع super()‎. هذا الأسلوب معروف في بعض لغات البرمجة التي تدعم الوراثة المتعددة باسم استدعاء التابع اللاحق (call-next-method) وهو أكثر قوّة من استدعاء التوابع في الأصناف الأساسية (super call) في اللغات التي تدعم الوراثة المفردة.

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

فعلى سبيل المثال، ترث جميع الأصناف من الكائن؛ لذا فإنّ أي حالة من الوراثة المتعددة ستقدّم أكثر من طريق للوصول إلى ذلك الكائن. ولتجنب الوصول إلى الأصناف الأساسية أكثر من مرة واحدة تعمل خوارزمية البحث الديناميكية على جعل ترتيب عملية البحث خطّيًا linear بصورة تحافظ على طريقة الترتيب من اليسار إلى اليمين والمحدّدة في كل صنف، وتستدعي كل صنف أب مرّة واحدة فقط، وعلى وتيرة واحدة (بمعنى أنّه يمكن تفريع الصنف دون التأثير على ترتيب الأصناف الآباء).

مصادر

  • صفحة Classes في توثيق بايثون الرسمي.