بايثونالوراثة
- مفهوم الوراثة
- أشكال الوراثة في بايثون
- أمثلة تطبيقية على الوراثة في بايثون
- معرفة طبيعة العلاقة بين الكلاسات و الكائنات
- تضارب الأسماء عند وراثة أكثر من كلاس
- حل مشكلة تضارب الأسماء
- الكلاس object
مفهوم الوراثة
في البداية, كلمة وراثة تعني تضمين محتوى كلاس في كلاس آخر.
في بايثون, الكلاس يمكنه أن يرث من كلاس آخر حتى يحصل على الدوال و المتغيرات الموجودة فيه.
إذاً فكرة الوراثة بسيطة لكن فائدتها قوية جداً. فمثلاً إذا كنت تريد إنشاء كلاس جديد و لاحظت أنه يوجد كلاس جاهز يحتوي على كودات قد تفيدك يمكنك استغلالها بدل كتابتها من الصفر, أي يمكنك جعل الكلاس الذي قمت بتعريفه يرث هذا الكلاس, و بعدها يمكنك إستخدام جميع المتغيرات و الدوال التي ورثها الكلاس الجديد من الكلاس الجاهز.
لجعل الكلاس يرث من كلاس آخر, نضع بعد إسم الكلاس قوسين و بداخلهما إسم الكلاس الذي نريده أن يرث منه.
في حال كان الكلاس يرث من أكثر من كلاس, يجب وضع فاصلة بين كل كلاسَين نضعهما بين القوسين.
مصطلحات تقنية
- الوراثة تسمى Inheritance في اللغة الإنجليزية.
- الكلاس الذي يرث من كلاس آخر يقال له الكلاس الإبن, و يسمى Subclass و يقال له أيضاً ( Derived Class, Extended Class أو Child Class ).
- الكلاس الذي يورّث محتوياته لكلاس آخر يسمى الكلاس الأب, و يسمى Superclass و يقال له أيضاً ( Base Class أو Parent Class ).
الـ Subclass يرث كل شيء موجود في الـ Superclass بشكل مباشر ما عدا الخصائص التي تم تعريفها كباراميترات بداخل الدالة __init__() و السبب في ذلك أن هذه الدالة تولد الخصائص للكائن و تربطها بالكلاس لحظة إنشاء الكائن.
أي إذا لم تنشئ كائن من الكلاس لن يتم إستدعاء هذه الدالة أصلاً و لهذا لا تعتبر كأنها خصائص موجودة بشكل مباشر في الكلاس نفسه.
في حال أردت إستدعاء الدالة __init__() الموجودة في الـ Superclass نستخدم دالة جاهزة إسمها super().
لا تقلق إذا لم تفهم كل ما ذكرناه الآن لأنك ستفهم كل هذه الامور بتفصيل من الأمثلة. و الدالة super() سنشرحها بتفصيل في الدرس التالي.
أشكال الوراثة في بايثون
في بايثون يوجد 4 أشكال للوراثة كما في الجدول التالي.
إذاَ أشكال الوراثة في بايثون هي كالتالي:
- وراثة فردية: تعني كلاس يرث من كلاس واحد فقط.
- وراثة متتالية: تعني كلاس يرث من كلاس واحد و هذا الكلاس كان في الأصل يرث من كلاس آخر.
- وراثة هرمية: تعني أن الكلاس موروث من قبل أكثر من كلاس.
- وراثة متعددة: تعني أن الكلاس يرث من أكثر من كلاس.
أمثلة تطبيقية على الوراثة في بايثون
في جميع الأمثلة سنفترض أن كل كلاس معرف بداخل ملف خاص كما يفعل المطورون على أرض الواقع.
مثال عن الوراثة الفردية
في المثال التالي قمنا بتعريف كلاس إسمه A يحتوي على متغير إسمه x و دالة إسمها print_msg().
بعدها قمنا بإنشاء كلاس إسمه B يحتوي على متغير إسمه y و يرث من الكلاس A.
شكل الوراثة سيكون كالتالي.
المثال الأول
سنحصل على النتيجة التالية عند تشغيل الملف Test.
x: 10
Hello from class A
مثال عن الوراثة المتتالية
في المثال التالي قمنا بتعريف كلاس إسمه A يحتوي على دالة إسمها print_a().
بعدها قمنا بإنشاء كلاس إسمه B يحتوي على دالة إسمها print_b() و يرث من الكلاس A.
بعدها قمنا بإنشاء كلاس إسمه C يحتوي على دالة إسمها print_c() و يرث من الكلاس B.
شكل الوراثة سيكون كالتالي.
المثال الثاني
سنحصل على النتيجة التالية عند تشغيل الملف Test.
Hello from class B
Hello from class C
مثال عن الوراثة المتعددة
في المثال التالي قمنا بتعريف كلاس إسمه A يحتوي على دالة إسمها print_a().
بعدها قمنا بإنشاء كلاس إسمه B يحتوي على دالة إسمها print_b().
بعدها قمنا بإنشاء كلاس إسمه C يحتوي على دالة إسمها print_c() و يرث من الكلاس A و الكلاس B.
شكل الوراثة سيكون كالتالي.
المثال الثالث
سنحصل على النتيجة التالية عند تشغيل الملف Test.
Hello from class B
Hello from class C
مثال عن الوراثة الهرمية
في المثال التالي قمنا بتعريف كلاس إسمه A يحتوي على دالة إسمها print_a().
بعدها قمنا بإنشاء كلاس إسمه B يحتوي على دالة إسمها print_b() و يرث من الكلاس A.
بعدها قمنا بإنشاء كلاس إسمه C يحتوي على دالة إسمها print_c() و يرث من الكلاس A أيضاً.
شكل الوراثة سيكون كالتالي.
المثال الرابع
سنحصل على النتيجة التالية عند تشغيل الملف Test.
Hello from class C
Hello from class A
Hello from class B
معرفة طبيعة العلاقة بين الكلاسات و الكائنات
الجدول التالي يحتوي على دوال جاهزة في بايثون يمكن إستخدامها لمعرفة طبيعة العلاقة بين كلاس و آخر أو بين كائن و آخر.
ملاحظة: في الأمثلة الموضوع إستخدمنا الكلمة المفتاحية pass من أجل تعريف كلاسات فارغة. لذلك ننصحك بالإضطلاع على الأمثلة.
إسم الدالة مع تعريفها | |
---|---|
1 | issubclass(subclass, superclass)
تستخدم لمعرفة ما إذا كان الكلاس الذي نمرره مكان الباراميتر subclass يرث من الكلاس الذي نمرره مكان الباراميتر superclass أم لا. ترجع True في حال كان كذلك, و غير ذلك ترجع False. شاهد المثال |
2 | isinstance(obj, classinfo)
تستخدم لمعرفة ما إذا كان الكائن الذي نمرره مكان الباراميتر obj تم إنشاؤه من الكلاس الذي نمرره مكان الباراميتر classinfo أم لا. ترجع True في حال كان كذلك, و غير ذلك ترجع False. شاهد المثال |
تضارب الأسماء عند وراثة أكثر من كلاس
عندما يقوم الكلاس بوارثة أكثر من كلاس فيوجد إحتمال كبير أن يرث خصائص و دوال عندها نفس الإسم و بالتالي سيحدث تضارب في الأسماء.
الآن عليك معرفة أن التضارب في الأسماء لا يسبب خطأ في الكود عند التشغيل لكنه قد يؤدي لحدوث خطأ منطقي في النتيجة المرجو الحصول عليها.
معلومة تقنية
في حال كان الكلاس يرث من كلاسيَن و كان يوجد في كلاهما متغير عنده نفس الإسم و دالة عندها نفس الإسم, سيقوم مفسّر لغة بايثون بتوريث الكلاس المتغير و الدالة الموجودين في أول كلاس يرث منه. لأن أول كلاس يتم الوراثة منه يتم إعطاؤه أولوية التنفيذ في حال حدث تضارب في الأسماء.
في المثال التالي قمنا بتعريف كلاس إسمه A يحتوي على دالة إسمها print_msg().
بعدها قمنا بإنشاء كلاس إسمه B يحتوي على دالة إسمها print_msg() أيضاً.
بعدها قمنا بإنشاء كلاس إسمه C يرث من الكلاس A و الكلاس B و يحتوي على دالة إسمها call_print_msg().
إذاً هنا عندنا وراثة متعددة و شكلها كالتالي.
مثال
سنحصل على النتيجة التالية عند تشغيل الملف Test.
حل مشكلة تضارب الأسماء
في حال كنت تريد إنشاء كلاس و تريد ضمان عدم حدوث مشكلة في تضارب الأسماء عند وراثة هذا الكلاس, يمكنك إعتماد مبدأ إخفاء البيانات ( Data Hiding ) عند تسمية المتغيرات و الدوال. إذاً الفكرة من إخفاء البيانات جعل المبرمج قادر على وضع نفس أسماء المتغيرات و الدوال في الكلاس الأب و الكلاس الإبن مع ضمان عدم حدوث أي تضارب.
الآن, يمكنك ببساطة وضع الرمز __ في بداية إسم المتغير أو الدالة لتصبح قادر على إستخدام نفس الإسم في الكلاس الذي يرثهم.
عندها سيتولى مفسّر لغة بايثون التفريق بين الأشياء الموجودة في الكلاس الأب و الأشياء الموجودة في الكلاس الإبن.
معلومة تقنية
عند وراثة شيء خفي سواء كان دالة أو متغير فإنك تصل له كالتالي: تضع الرمز _ClassName__ItsName
فمثلاً إذا قمت بإنشاء كلاس إسمه A و وضعت فيه متغير خفي إسمه __x, في حال قمت بوراثة هذا المتغير فإنك تستطيع الوصول له كالتالي _A__x.
إذاّ مفسر بايثون يعيد تسمية المتغير الخفي نسبة لإسم الكلاس و هكذا يضمن أن لا يحدث تضارب في الأسماء.
في المثال التالي قمنا بتعريف كلاس يرث من كلاس و كلاهما يحتويان على متغير خفي إسمه __x و دالة خفية إسمها __print_msg().
بعدها قمنا بإنشاء كائن من الكلاس الإبن و فيه عرضنا قيمة كل المتغيرات الخفية الموجودة فيه و قمنا باستدعاء الدوال الخفية أيضاَ.
مثال
سنحصل على النتيجة التالية عند تشغيل الملف Test.
__x obtained from class B = 20
Hello from class A
Hello from class B
الكلاس object
في بايثون, أي كلاس جاهز أو تنشئه بنفسك يرث بشكل تلقائي من كلاس إسمه object.
إذاَ, الكلاس object يعتبر Superclass لجميع الكلاسات في بايثون.
و بالتالي فإن أي كائن ننشئه سيحتوي على كل الخصائص و الدوال الموجودة فيه.
الآن, إذا قمنا بتعريف كلاس إسمه Test لا يرث من أي كلاس.
فكن على يقين أن مفسّر لغة بايثون سيجعل الكلاس Test يرث بشكل تلقائي من الكلاس object كالتالي.
و بالنسبة للدوال التي تظهر لك أثناء كتابة الكود بمجرد أن تضع نقطة بعد إسم الكائن و التي لم تقم بتعريفها بنفسك كما في الصورة التالية.
فهذه الدوال وجدت في الكائن لأنه تم إنشاء هذا الكائن في الأساس من كلاس يرث من الكلاس object.
معلومة تقنية
الكلاس object موضوع بداخل موديول إسمه builtins.py.
هذا الموديول يقوم مفسّر بايثون بتضمينه بشكل تلقائي في كل كلاس, لهذا السبب لا تحتاج أن تفعل له import.
أيضاَ عليك معرفة أن الموديول builtins.py يحتوي على كل الدوال التي كنا نستخدمها سابقاً و التي كنا نقول أنها دوال جاهزة في بايثون.
و هو يحتوي أيضاً على كلاسات كثيرة غير الكلاس object لكننا لن نتطرق إليها في هذا الدرس.
الجدول التالي يحتوي على خصائص الكلاس object.
إسم الخاصية مع تعريفها | |
---|---|
1 | __class__ ترجع إسم الكلاس الذي أنشئ منه الكائن الذي قام باستدعائها. |
2 | __dict__ ترجع كائن dict فيه كل خصائص الكلاس الذي أنشئ منه الكائن الذي قام باستدعائها. |
3 | __doc__ ترجع التفسير ( Documentation ) الموضوع في الكلاس الذي أنشئ منه الكائن الذي قام باستدعائها. |
4 | __module__ ترجع إسم الموديول الذي يحتوي على الكلاس الذي أنشئ منه الكائن الذي قام باستدعائها. |
الجدول التالي يحتوي على بعض دوال الكلاس object.
إسم الدالة مع تعريفها | |
---|---|
1 | __init__()
تستخدم هذه الدالة لتعريف خصائص للكائن و إعطائهم قيم أولية. يتم إستدعاء هذه الدالة بشكل تلقائي عند إنشاء كائن من الكلاس الذي يحتويها. |
2 | __del__()
تستخدم هذه الدالة لتنفيذ أوامر معينة عندما يتم مسح الكائن من الذاكرة. يتم إستدعاء هذه الدالة بشكل تلقائي عند مسح كائن من الكلاس الذي يحتويها. |
3 | __sizeof__() ترجع عدد صحيح يمثل حجم الكائن ( أي المساحة التي يحتلها ) في الذاكرة بالـ Byte. |
4 | __repr__()
ترجع نص يمثل شكل الكائن كما الذي قام باستدعائها في الذاكرة مع احترام نوع و قيمة الكائن. ملاحظة: استدعاء هذه الدالة مطابق لاستدعاء دالة إسمها repr(object) موضوعة مباشرةً في الموديول builtins.py. |
5 | __str__()
ترجع نص يمثل شكل الكائن كما الذي قام باستدعائها في الذاكرة. ملاحظة: استدعاء هذه الدالة مطابق لاستدعاء دالة إسمها str(object) موضوعة مباشرةً في الموديول builtins.py. |
في المثال التالي قمنا بتعريف كلاس إسمه Car وضعنا فيه الدالة __init__() و الدالة __del__().
بعدها قمنا بإنشاء كائن منه و استدعاء الخصائص و الدوال التي ورثها الكلاس Car في الأساس من الكلاس object.
المثال الأول
سنحصل على النتيجة التالية عند تشغيل الملف Test.
Object module name: Car
Object class documentation: This class represent any car object
Object properties and values: {'model': 'BMW', 'year': '2015', 'color': 'Black', 'price': 22000}
Object size in Byte: 16
Object representation: <Car.Car object at 0x01080A30>
Object string representation: <Car.Car object at 0x01080A30>
Object is destroyed
لعلك تتساءل الآن: ما الفرق بين الدالة __repr__() و __str__() حيث أنك لم تلاحظ أي فرق بينهما في المثال السابق.
المثال التالي يوضح لك الفرق بينهما.
المثال الثاني
سنحصل على النتيجة التالية عند تشغيل الملف Test.
Object string representation: Python Tutorial