Path: blob/master/الفصل العاشر-البرمجة الكائنية في بايثون.ipynb
675 views
Kernel: Python 3 (ipykernel)
In [1]:
Out[1]:
في الفصول السابقة كنا نعتمد في كتابتنا للبرامج على الأسلوب التسلسلي والذي عادة ما يكون مناسبا لكتابة البرامج الصغيرة فقط. لكن عندما يراد كتابة برامج كبيرة متعددة المهام فان استخدام الأسلوب التسلسلي يصبح غير مجد وذلك لسببين:
أولا: تصبح عملية التطوير شاقة عندما يراد إضافة مزايا ومهام جديدة في البرنامج.
ثانيا: ترابط الكود بشكل تسلسلي يجعل عملية اصلاح المشاكل في البرنامج غاية في الصعوبة وذلك لان اصلاح واضافة جزء من البرنامج قد يترتب عليه احداث مشكال جديدة في أجزاء أخرى من البرنامج.
لذلك سوف نتطرق في هذا الفصل الى التعرف على أسلوب جديد في كتابة الأكواد البرمجية يدعى البرمجة الكائنية Object Oriented Programming. يمتاز هذا الأسلوب البرمجي عن الأسلوب التسلسلي في تجزئته للبرنامج الى مكونات برمجية صغيرة لها خصائص ومهام يتم كتابتها داخل تركيب برمجي محدد يدعى كائن برمجي class.
أولا: تصبح عملية التطوير شاقة عندما يراد إضافة مزايا ومهام جديدة في البرنامج.
ثانيا: ترابط الكود بشكل تسلسلي يجعل عملية اصلاح المشاكل في البرنامج غاية في الصعوبة وذلك لان اصلاح واضافة جزء من البرنامج قد يترتب عليه احداث مشكال جديدة في أجزاء أخرى من البرنامج.
لذلك سوف نتطرق في هذا الفصل الى التعرف على أسلوب جديد في كتابة الأكواد البرمجية يدعى البرمجة الكائنية Object Oriented Programming. يمتاز هذا الأسلوب البرمجي عن الأسلوب التسلسلي في تجزئته للبرنامج الى مكونات برمجية صغيرة لها خصائص ومهام يتم كتابتها داخل تركيب برمجي محدد يدعى كائن برمجي class.
أهداف الفصل
عند اتمام هذا الفصل يجب ان يكون لديك المام بالآتي:- التعرف على كيفية انشاء كائن برمجي (class) له خواص ومهام محددة.
- التعرف على كيفية انشاء نسخ من الكائن البرمجي لأداء مهام برمجية تم كتابتها داخل تعريف الكائن البرمجي.
- معرفة كيفية توريث الخصائص والمهام بين الكائنات البرمجية.
الكائنات البرمجية Classes
في فصول سابقة تعرفنا على أنواع البيانات وطريقة التعامل معها. ما لم نخبرك به عن هذه البيانات هو ان كل نوع من هذه البيانات يمثل كائن برمجي في بايثون تم إنشاؤه بالطريقة التي سوف نتعلمها في هذا الفصل. فالبيانات النصية مثلا تمثل كائن برمجي محدد تم تعريفه داخل اصدارة بايثون وكنا نتعرف على نوع الكائن بواسطة الدالة ()type كما في المثال التالي:
In [ ]:
لاحظ ان النصوص عبارة عن كائن برمجي من النوع str تم إنشاؤه داخل مكتبة بايثون القياسية. ولتعرف على هذا الكائن وكيفية كتابته وما يحتويه من أكواد برمجية يكمن الاستعانة بالدالة ()help وكتابة str بدون علامات اقتباس بين قوسي الدالة كما يلي:
In [ ]:
تمارين استكشافية
1- تعرف على الكائنات البرمجية للاعداد الموجود في بايثون باستخدام الدالة ()type و ()help؟
يكمن انشاء كائن برمجي جديد في بايثون بنفس الأسلوب الذي استخدمناه لإنشاء الدوال في الفصل السابع الا اننا هنا سوف نستخدم أداة التعريف class بدلا عن def كما في المثال التالي:
In [ ]:
لاحظ اننا قمنا بكتابة اسم الكائن Point بعد أداة تعريف الكائن class وأنهينا السطر بنقطتين فوق بعض. اسم الكائن هنا يمكن ان يكون أي اسم تختاره شريطة ان تنطبق عليه جميع شروط المتغيرات التي ذكرناها في الفصل الاول. لاحظ أيضا اننا استخدمنا خاصية ترك المسافة التي تعلمناها سابقا عند تعريف الدوال واستخدمنا الامر pass لنخبر مفسر بايثون اننا نود فقط انشاء الكائن بحيث نستطيع لاحقا إضافة خواص ومهام لهذا الكائن. ولتأكد من صحة انشائنا للكائن السابق يمكن القيام بكتابة اسم الكائن كما يلي:
In [ ]:
ويمكننا أنشاء نسخة من الكائن Point واسنادها للمتغير p1 باستخدام اسم الكائن متبوعا بقوسين فارغين كما في المثال التالي:
In [ ]:
وعند طباعة نوع المتغير p1 فان مفسر بايثون سوف يعطينا انه من الصنف Point كما يلي:
In [ ]:
لنفرض الان اننا نريد ان نقوم باعطاء هذا الكائن بعض الخواص (attributes). ما نقصده بالخواص هنا هو مجرد متغيرات تم كتابتها داخل تعريف الكائن البرمجي كما في المثال التالي:
In [ ]:
لاحظ اننا اضفنا للكائن Point خاصية x وخاصية y وتم استدعاء هذه الخاصية باستخدام اسم الخاصية بعد اسم الكائن مفصولا بينهما بنقطة. هذا النوع من الخواص يسمى خواص الكائن البرمجي العامة. ويوجد نوع اخر من الخواص يسمى خواص النسخة التي يراد عملها من الكائن البرمجي ويتم كتابة هذه الخواص داخل دالة خاصة تسمى ()__init__ يتم تعريفها داخل الكائن بحيث يتم تنفيذها عند بداية انشاء أي نسخة من الكائن. فمثلا لنفرض اننا نريد ان ننشئ النسختين p1 و p2 من الكائن Point بحيث تكون قيمة الخاصية y مختلفة لكل نسخة من p1 و p2. للقيام بذلك نقوم بتعريف دالة خاصة تسمى ()__init__ كما يلي:
In [ ]:
هناك مجموعة ملاحظات يجب تدوينها حول المثال السابق
:
أولا: الدوال عندما تكتب داخل تعريف الكائن تسمى مهام methods.
ثانيا: self في المثال السابق تشير الى اسم النسخة التي يتم عملها أيا كان اسم هذه النسخة. فكتابة self.y يتم ترجمته الى p1.y عندما يتم عمل النسخة p1 من الكائن Point.
ثالثا: عند تعريف أي مهمة method داخل كائن فانه يجب كتابة المدخل self كأول قيمة لهذه المهمة.
رابعا: عند عمل نسخة من الكائن لا نحتاج لكتابة self بين قوسي اسم الكائن ويكتفى فقط بالمدخلات الإلزامية التي تلي المدخل self في المهمة ()__init__. الفشل في تزويد المدخلات الإلزامية التي تلي self في المهمة ()__init__ يؤدي لظهور ملاحظة بوجود خطأ من قبل مفسر بايثون. فلو قمنا بعمل نسخة من الكائن Point بدون إعطاء قيمة للخاصية y فان مفسر بايثون يعطينا الملاحظة التالية:
أولا: الدوال عندما تكتب داخل تعريف الكائن تسمى مهام methods.
ثانيا: self في المثال السابق تشير الى اسم النسخة التي يتم عملها أيا كان اسم هذه النسخة. فكتابة self.y يتم ترجمته الى p1.y عندما يتم عمل النسخة p1 من الكائن Point.
ثالثا: عند تعريف أي مهمة method داخل كائن فانه يجب كتابة المدخل self كأول قيمة لهذه المهمة.
رابعا: عند عمل نسخة من الكائن لا نحتاج لكتابة self بين قوسي اسم الكائن ويكتفى فقط بالمدخلات الإلزامية التي تلي المدخل self في المهمة ()__init__. الفشل في تزويد المدخلات الإلزامية التي تلي self في المهمة ()__init__ يؤدي لظهور ملاحظة بوجود خطأ من قبل مفسر بايثون. فلو قمنا بعمل نسخة من الكائن Point بدون إعطاء قيمة للخاصية y فان مفسر بايثون يعطينا الملاحظة التالية:
In [ ]:
لنقم الان بتعريف مهمة يمكن تنفيذها على النسخ التي يتم انشاؤها من هذا الكائن ولتكن هذه الوظيفة هي ايجاد طول الخط الذي يصل بين نقطة ونقطة الاصل. لتعريف هذه المهمة نقوم بانشاء دالة بنفس الطريقة التي تعلمنها في الفصل السابع ولكنها تختلف قليلا عن الدالة الاعتيادية في كونها تكتب داخل كائن وتحتوي على مدخل الزامي اول self كما في المثال التالي:
In [ ]:
In [ ]:
لنقم الان بتعريف مهمة جديدة تقوم بحساب المسافة بين نقطة ونقطة اخرى وليس بين بالضرورة ان تكون هذه النقطة هي نقطة الاصل. للقيام بذلك نعرف دالة جديدة داخل الكائن على النحو التالي:
In [ ]:
In [ ]:
لنقم الان بطباعة نقطة تم نسخها من الكائن Point لنرى كيف يقوم بايثون بطباعة هذه النسخة.
In [ ]:
لنقم بتحسين عملية طباعة النقطة السابقة بحيث يكون ناتج الطباعة (1,2)
بدلا من القيمة الافتراضية التي يقوم بها مفسر بايثون حينما لا يجد مهمة داخل الكائن ترشده الي كيفية الطباعة. يوجود في بايثون مهمة خاصة تحمل الاسم ()__str__
يقوم مفسر بايثون بتنفيذها عندما نستخدم امر الطباعة ()print ولتعديل طباعة النقطة سوف نكتب المهمة ()__str__ على النحو التالي:
In [ ]:
In [ ]: