النص الكامل للمحاضرة 18: المزيد من توابع أصناف Python
النص الكامل للمحاضرة 18: المزيد من توابع أصناف Python
المصدر الأصلي: النص الرسمي الإنجليزي، PDF، صفحة المحاضرة، صفحة الفيديو الرسمي.
النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة وتكييف عربي غير رسمي، لا تعتمده MIT ولا يعني نشره تأييدها له. الاستخدام غير تجاري، وتُنشر الترجمة والمواد الأصلية المشمولة وفق رخصة CC BY-NC-SA 4.0، مع وجوب المشاركة بالمثل. لا تشمل الرخصة الافتراضية مواد الأطراف الثالثة المستثناة.
منهج الترجمة: المصدر هو النص المستخرج من PDF الرسمي، لا ملخصات الشرائح. تُترجم فقراته بالترتيب مع حفظ التكرار والتردد والتصحيحات الشفهية ومداخلات الحضور والإشارات الصوتية. تتبع عناوين «صفحة المصدر» الصفحات غير الفارغة المفصولة بمحرف فصل الصفحات في الاستخراج. تُحفظ أسماء الكود والتعبيرات؛ وتُميَّز توضيحات المترجم عن كلام المصدر، ولا تُصحَّح زلاته صامتًا.
صفحة المصدر 1
MITOCW | المحاضرة 18 من 6.100L، النسخة المتعددة، الإصدار 2.mp4
[صرير] [حفيف] [نقرات]
آنا بيل (ANA BELL):
حسنًا، لنبدأ محاضرة اليوم. في المحاضرة الماضية بدأنا مغامرتنا في إنشاء أنواع البيانات (Data Types) الخاصة بنا. اليوم سنبدأ بمراجعة قصيرة، فقط لتذكيركم ببعض تفاصيل إنشاء أنواع البيانات الخاصة بنا. ثم سنبني على صنف النقطة الإحداثية (Coordinate Class) الذي بدأناه في المحاضرة الماضية. سنبني صنف دائرة (Circle Class)، ثم سنبني بعض أنواع بيانات الكسور (Fraction Data Types).
حسنًا، أول شيء أود ذكره هو تذكيركم بكتابة الكود من هذين المنظورين المختلفين. تمامًا مثلما كتبنا الدوال (Functions)، كنّا نكتب تعريف الدالة، ونخبر Python بأن لدينا هذه الدالة التي نعرّفها بصورة مجردة وهذا ما تفعله. ثم كنّا نستدعي الدالة لاحقًا في البرنامج مرات كثيرة، كثيرة.
الشيء نفسه موجود — الفكرة نفسها موجودة الآن ونحن ننشئ أنواع البيانات الخاصة بنا. علينا كتابة الكود من وجهة نظر شخص ينفّذ الصنف (Implements the Class)، أي يقرر كل هذه التفاصيل التي تدخل في إنشاء الصنف نفسه، مقابل شخص يستخدم صنفًا كُتب بالفعل فقط، حيث ننشئ مثيلات (Instances)، مجموعة من الكائنات المختلفة التي صادف أنها من نوع البيانات هذا.
عندما ننفّذ الصنف، ما بعض الأشياء التي فعلناها؟ حسنًا، نخبر Python بأن هذا الكائن موجود الآن. ونخبر Python باسم نوع البيانات الذي ننشئه، وهو شيء نختاره. ونتخذ قرارات التصميم (Design Decisions) هذه التي نقرر فيها ما السمات (Attributes) التي يتكوّن منها صنفنا.
فالسمات إما بيانات، مثل الخصائص (Properties) — ما المتغيرات التي يتكوّن منها كائنك — أو سلوكيات (Behaviors) عبر التوابع (Methods). هذا هو تنفيذ الصنف. ثم عندما نستخدم الصنف، نقول الآن: حسنًا، لنفترض أن تعريف هذا الصنف موجود.
يوجد هذا الكائن الذي له هذه السلوكيات وهذه السمات البيانية (Data Attributes). فلننشئ الآن مجموعة كاملة من الكائنات من هذا النوع. وعندئذ ننشئ هذه المثيلات، ثم نتعامل مع كل هذه المثيلات بتشغيل توابع عليها، وما شابه.
عندما ننفّذ الصنف، هذا الشيء على الجانب الأيسر، نخبر Python أساسًا، بصيغة مجردة، ما الخصائص والسلوكيات المشتركة لنوع بياناتنا. ثم عندما نستخدم الصنف — الشيء على الجانب الأيمن هنا — ننشئ كائنات فعلية لها قيم محددة جدًّا لسمات بياناتها، نستطيع التعامل معها بطرق مختلفة.
فلنتذكّر صنف النقطة الإحداثية هذا الذي كتبناه في المحاضرة الماضية. ليس هذا جديدًا، لكنني سأمرّ عليه بسرعة فقط. السطر الأول هنا يخبر Python بأننا ننشئ نوع بيانات جديدًا. اسمه Coordinate. وهذه الكلمة المفتاحية (Keyword) class تخبر Python بأننا ننشئ نوع البيانات. بين القوسين هنا object، وهو يشير إلى نوع بيانات الكائن في Python. إنه شيء عام جدًّا.
وهذا، بين القوسين هنا، هو الأب (Parent) لصنفنا. كل ما يستطيع كائن Python عادي فعله، الأشياء الأساسية جدًّا، يستطيع صنفنا فعله أيضًا. في المحاضرة الماضية ذكرت أن مثالًا على شيء أساسي كهذا هو أخذ اسم متغير وإسناده إلى نوع كائن ننشئه.
أول تابع ينبغي أن نكتبه لنوع بيانات جديد ننشئه هو تابع init. وهذا سمّيته تابع دندر (Dunder Method)، لأنه يبدأ بشرطتين سفليتين قبل init وينتهي بشرطتين سفليتين بعد init. وهذا هو الاسم الفعلي لهذا التابع، __init__.
صفحة المصدر 2
هذا التابع مثل منشئ (Constructor) للصنف. يخبر Python كيف تنشئ كائنًا فعليًّا من هذا النوع. إنه دالة. مجرد دالة لا تعمل إلا مع كائنات من النوع Coordinate. وبصفته دالة، يأخذ معاملات (Parameters). ترون أنه يأخذ ثلاثة معاملات هنا — self، وx، وy.
والآن، عندما ننشئ كائنات من النوع Coordinate فعلًا، لا نمرّر إلا معاملات لكل ما عدا self، لأن self اسم متغير نستخدمه لوصف وجود مثيل من الصنف دون إنشاء واحد فعلًا بعد؛ فتذكّروا ما نفعله هنا في هذا التعريف. نخبر Python بأن نوع الكائن هذا موجود الآن. نكتبه ونحن نتحدث.
لكن ليس لدينا بعد مثيل فعلي نتعامل معه. هذا هو التعريف فقط. لذلك يخبر self برنامج Python بأننا عندما نكتب هذا الكود، سنستخدم اسم المتغير self بوصفه اسمًا شكليًّا (Formal Name) لنتمكن من تشغيل هذا التابع عليه. سنرى في الشريحة القادمة ما الذي يرتبط بـself بالضبط عندما نشغّله. لكن هذا ما يعنيه self داخل قائمة المعاملات هنا وهنا.
ثم إلى جانب ذلك، نستخدم self داخل تابع init لإخبار Python أي هذه المتغيرات هي سمات بيانات فعلًا، مقابل أي هذه المتغيرات هي متغيرات عادية قديمة مثل التي عملنا معها. فأي متغير يُعرَّف باستخدام self. — هنا لدي self.x وself.y — هو سمة بيانات. يعني ذلك أنني أعرف أن أي كائن أنشئه من النوع Coordinate سيكون له متغير x ومتغير y مرتبطان به، لأنني عرّفت هذه الـx وهذه الـy باستخدام self.x وself.y.
في المحاضرة الماضية، كانت لدي في الواقع قوائم المعاملات هذه — كانت المعاملات في هذه القائمة هنا مختلفة عن x وy. أظن أن لدي xval وyval. ثم كان لدي self.x يساوي xval، حيث إن هذه الـx هنا على يمين علامة المساواة هي x من قائمة المعاملات. بهذا المعنى، لا يهم ما هذه المتغيرات في قائمة المعاملات. ستكون هي نفسها هنا على الجانب الأيمن من علامة المساواة. لكن المعاملات الفعلية — آسفة، سمات البيانات الفعلية لكائني هي self.x وself.y.
حسنًا. كان لدينا تابع واحد كتبناه في المحاضرة الماضية. اسمه distance. ويأخذ معاملين — الأول بالطبع self. ويمثّل self هذا الشيء، هذا الكائن الذي ستستدعي التابع عليه. ليس لدي ذلك الكائن بعد، لذلك أسمّيه self الآن فقط، لأن هذا تعريف الصنف. ثم هذا المعامل الآخر كائن إحداثي آخر سأشغّل هذا التابع عليه.
جسم distance يقول: حسنًا، كيف أجد المسافة بين نقطتين في مستوى ثنائي الأبعاد (2D Plane)؟ إنها فيثاغورس (Pythagoras) فقط، صحيح؟ يعني ذلك أخذ قيمة x لإحدى نقطتيّ، وطرح قيمة x للنقطة الأخرى، وتربيعهما. وكذلك قيم y، ربّعهما، واجمعهما، وخذ الجذر التربيعي.
ما قيمة x لنقطة واحدة؟ حسنًا، ستكون إحدى النقطتين الشيء الذي أستدعي تابع distance عليه، أي self. آخذ قيمة x الخاصة بـself باستخدام صيغة النقطة (Dot Notation) هذه، self.x. ثم ما قيمة x لهذه النقطة الإحداثية الأخرى؟ اسمها other في قائمة المعاملات عندي. لذلك سأخذ قيمة x الخاصة بـother، مرة أخرى باستخدام صيغة النقطة. ثم ننفّذ الحساب فقط. نعم؟
طالب (STUDENT):
هل لا يمكنك استدعاء دوال على صنف إلا إذا كانت معرَّفة بهذه الطريقة؟
آنا بيل:
نعم، يمكنك إنشاء توابع. يمكنك إنشاء توابع لصنف محدد.
طالب:
صحيح، لكن لا يمكنك استدعاء إلا تلك الدوال التي تعرّفينها؟ لا يمكنك استدعاء دوال أخرى؟
آنا بيل:
بالضبط، نعم. نعم. نعم؟
صفحة المصدر 3
طالب:
هل هناك طريقة يمكننا بها تعريف صنف بوصفه شيئًا غير object؟
آنا بيل:
صنف بوصفه شيئًا غير object؟
طالب:
صحيح، مثلًا نقول Coordinate ثم object، ثم تضعين شيئًا آخر.
آنا بيل:
بين القوسين؟ نعم، نستطيع وضع أشياء أخرى بين القوسين. هذا في الواقع ما ستكون عنه محاضرة يوم الاثنين. في تلك الحالة، يصبح الشيء بين القوسين الأب للصنف الذي تكتبه حاليًّا. لن أخوض في تفاصيل كثيرة. لكن وجود هذا الكائن الآخر أبًا يعني أن كل ما يستطيع ذلك الكائن فعله، يستطيع كائنك فعله تلقائيًّا أيضًا.
ثم فوق ذلك، تستطيع تقرير مجموعة أشياء إضافية يفعلها كائنك الجديد. لكن بمعنى ما، كائنك الإحداثي هو أيًّا كان هذا الشيء بين القوسين. ثم يستطيع فعل مجموعة أشياء أخرى أيضًا. نعم؟
طالب:
على نحو مشابه، كيف يمكنك نسخ الأقواس المربعة ولصقها؟
آنا بيل:
نعم؟
طالب:
كيف يمكنك فعل ذلك؟
آنا بيل:
أوه، لإنشاء نسخة من الكائن؟ أم —
طالب:
لإنشاء نسخة من الصنف.
آنا بيل:
أوه، هنا لا تستطيع إنشاء نسخة من الصنف هنا بالتحديد، لأننا نعرّف الصنف فقط. لكن عندما تنشئ كائنات إحداثية، تستطيع عندئذ تعريف تابع ينسخ كائنك إلى كائن آخر. أساسًا سيُرجع كائنًا جديدًا من النوع Coordinate بأي معاملات تريد له أن يستخدمها. نعم، كل هذه الأشياء ممكنة.
فلنضف تابعًا آخر إلى هذا الصنف. لنسمّه to_origin. هذا التابع distance، فقط لتذكير أنفسنا، كان يُرجع عددًا. كان يأخذ الفرق بين هاتين النقطتين ويُرجع عددًا، أي مدى بعدهما فقط. لكن هذا التابع to_origin سيفعل شيئًا مختلفًا قليلًا.
أساسًا، سأجعل هذا التابع يأخذ نقطتي من أي موضع هُيّئت إليه في مستواي ثنائي الأبعاد، ويقول إنني سأعيد ضبطها إلى نقطة الأصل (Origin). ولفعل ذلك، كل ما يعنيه هذا هو أنني سأجعل قيمتي x وy عندها 0. أستطيع تعديل سمتي البيانات x وy لهذا الكائن بالتحديد لتكونا ما أريده. فأستطيع إعادة ضبطهما كلتيهما إلى 0. إذا استدعيت هذا التابع على كائن له قيمتا x وy غير 0، فستُعادان إلى 0.
فلنشغّل الكود الذي كتبناه للتو فعلًا. هنا يجري إنشاء كائنين إحداثيين. جمال كتابة هذا الصنف بالنسبة إلينا هو أننا نستطيع الآن إنشاء أي عدد نريده من الكائنات الإحداثية. سيكون لكل منها قيمة x وقيمة y مرتبطتان بها. الأمر فقط أن القيم المحددة لـx وy ستكون مختلفة.
هنا يجري إنشاء كائن إحداثي فيه x هو 3 وy هو 4. وسيُربَط باسم المتغير c، وهنا كائن إحداثي فيه قيمتا x وy كلتاهما 0، مرتبط بمتغير اسمه origin.
صفحة المصدر 4
أستطيع بعد ذلك استخدام صيغة النقطة التي تحدثنا عنها في المحاضرة الماضية للوصول إما إلى البيانات أو لتشغيل توابع على الكائن. في تعليمة printf هذه هنا، أستخدم صيغة النقطة على c وorigin لأخذ قيمة x الخاصة بـc هنا، وorigin هنا.
ثم أشغّل تابع distance على c. تذكّروا أن صيغة النقطة تقول إن الشيء قبل النقطة سيكون كائنًا. والشيء بعد النقطة سيكون اسم التابع الذي يستطيع العمل على هذا الكائن من النوع، أيًّا كان هذا، Coordinate. ثم بين القوسين، إنها دالة فقط. نمرّر كل المتغيرات التي يتوقعها ذلك التابع. سيطبع c.distance مدى بعده، 5، لأن تلك أعداد مناسبة.
إذا شغّلنا بعد ذلك هذه الدالة التي كتبناها للتو، .to_origin، فهذه الدالة، فقط لتذكيركم، لا تُرجع شيئًا فعلًا. تعيد فقط ضبط المتغيرين x وy لذلك الكائن بالتحديد إلى 0. هنا، عندما أستدعي هذا التابع — مرة أخرى، صيغة النقطة، الشيء قبل النقطة كائن. إنه c. وقيمتا x وy عنده حاليًّا 3 و4.
لكن بعد أن أشغّل هذه الدالة — إنها تُرجع None، بالمناسبة — ستتغيّر قيمتا x وy عنده إلى 0 و0. إذا نظرت إلى كودي هنا — هذه تعليمة printf. قيمة x الخاصة بـc هي 3، وc — وقيمة x الخاصة بـorigin هي 0، جيد. ثم لدي هذان الاستدعاءان هنا — c.to_origin. أجري استدعاء الدالة هذا. قبل استدعاء الدالة، x عند c هو 3 وy عند c هو 4. وبعد استدعاء الدالة، ترون أن x عند c هو 0 وy عند c هو 0. أغيّر حرفيًّا قيمتي x وy لهذا الكائن، c.
حسنًا، هل هناك أسئلة حتى الآن؟ هل الأمور جيدة حتى الآن؟ آمل أنها مراجعة؟ حسنًا. مرة أخرى، على نحو مشابه نوعًا ما لأول شريحة بدأنا بها، لدينا هذا class Coordinate(object). اسم الصنف هو نوعنا. هذا الكائن الذي أنشئه من النوع Coordinate. نعرّف الصنف بطريقة عامة، بطريقة مجردة.
لذلك علينا استخدام المتغير self إما في قائمة المعاملات لإخبار Python إلى ماذا سيرتبط الشيء قبل النقطة — حسنًا، سيرتبط بـself في قائمة المعاملات عندي — أو نستخدم self لإخبار Python بما سمات بيانات هذا الكائن. كل ما يُعرَّف بـself. ثم اسم متغير ما سيكون سمة بيانات. وهذا مشترك بين جميع المثيلات التي أنشئها من هذا النوع.
عندما ننشئ مثيلات فعلية، فهذا حين يبدأ استخدام مخططنا، تعريفنا المجرد. والآن أنشئ كائنات فعلية يمكنني أخذ قيم x منها، وتغيير قيم x لها، والحصول على مسافات بينها وبين كائنات أخرى، وهكذا وهكذا.
حسنًا. ما أود فعله تاليًا هو أخذ صنف النقطة الإحداثية هذا وبناء صنف دائرة به. وهذا يسير جنبًا إلى جنب مع فكرة أنك عندما تقرر كيف تنشئ صنفًا، تملك اتخاذ قرار التصميم. عندما كان تمرين الأصابع (Finger Exercise) لمحاضرة الاثنين — اليوم الأربعاء، نعم. محاضرة الاثنين — كان عليكم إنشاء مركز — آسفة، صنف دائرة.
لكن الطريقة التي عرّفنا بها صنف الدائرة في تمرين الأصابع ذاك كانت أساسًا بنصف قطر (Radius) تلك الدائرة. كانت تلك الطريقة الوحيدة التي جرّدنا بها تلك الدائرة. لكن الآن، في هذه المحاضرة، سنتخذ قرار تصميم مختلفًا ونقول إن الدائرة ستُعرَّف الآن باستخدام شيئين.
الأول نصف القطر. سأقول إن ذلك عدد صحيح (Integer). والثاني سيكون مركز الدائرة (Center). كما في الصورة هناك، سأقول إن الدائرة تعتمد على المركز ونصف القطر هذا. ولن يكون المركز عدد فاصلة عائمة (Float). ولن يكون صفًّا (Tuple). ولن يكون int. سيكون كائنًا إحداثيًّا (Coordinate Object)، نوع البيانات الذي كنّا نكتبه للتو.
صفحة المصدر 5
حسنًا، جيد. ليس هذا سرًّا. لم أخفض صوتي عمدًا. أردت فقط أن تستقر هذه الفكرة في أذهانكم. إحدى سمات البيانات عندنا لصنف الدائرة كائن إحداثي. نستخدم إذًا كائنًا كتبناه للتو لإنشاء كائن أكثر تعقيدًا، دائرة.
ها هو تعريف صنفي. نوع البيانات اسمه Circle. مرة أخرى، يرث كل — الأب لـCircle مجرد كائن Python عام. أول تابع يجب أن نكتبه هو تابع init. المعامل الأول self، أي هذا الشيء الذي أنشئه الآن. وأقول إنه لإنشاء دائرة، يجب أن أعطيها مركزًا، ويجب أن أعطيها نصف قطر.
سمات بيانات صنف الدائرة — السمتان اللتان تتكوّن منهما دائرتي هما self.center هذا — متغير center هنا — وself.radius. هذان الشيئان معًا يتكوّن منهما كائن الدائرة عندنا. وفي البداية، في تابع init وعندما نبني كائننا، سنضبط هذين الشيئين فقط على ما يُمرَّر بوصفه معاملات في المنشئ.
حسنًا، ما أود قوله هو أن المعامل center هذا سيكون كائنًا إحداثيًّا، وradius سيكون int. لاحظوا أنني في هذا الكود لا أفرض ذلك فعلًا. أستطيع إنشاء كائن دائرة بتمرير سلسلتين نصيتين فقط. عند هذه النقطة لا يهتم هذا الكود. لا أفرض في أي موضع حقيقة أن center نقطة إحداثية وأن radius سيكون int. لكنه مجرد شيء نعرفه.
عندما ننشئ الكائن الفعلي هنا في الأسفل، سيكون my_circle متغيرًا مرتبطًا بكائن دائرتي. هنا أستدعي اسم صنفي، Circle. ثم ما المعاملان اللذان أمرّرهما؟ الأول، قلت هنا في الأعلى إنه ينبغي أن يكون كائنًا إحداثيًّا.
إذًا center اسم متغير. وما هو؟ كان عليّ إنشاء هذا الكائن الإحداثي. لذلك أستدعي فقط اسم Coordinate، هذا الصنف الذي ينشئ لي كائنًا إحداثيًّا. وصادف أنني أضع مركز الدائرة عند 2 و2. نعم، حسنًا.
هذا الشيء center كائن إحداثي. ليس صفًّا أو عدد فاصلة عائمة أو أي شيء. إنه كائن إحداثي. ثم نصف قطر هذه الدائرة 2، وهو int. هل هذا واضح للجميع؟ حسنًا.
ما أود منكم فعله تاليًا هو تعديل تابع init هذا قليلًا فقط، لأريكم أن تابع init لا يضبط دائمًا سمات البيانات وينتهي فقط. يمكنه تنفيذ كثير من كود التهيئة (Initialization Code). ومن أهم الأشياء التي يستطيع فعلها محاولة فرض الأنواع (Enforce Types) على المعاملات هنا.
أود أن تضيفوا إلى هذا الكود للتحقق من أن نوع center هو Coordinate، ونوع radius عدد صحيح. وفقط إذا كان هذان الأمران صحيحين، تضبطون سمتي البيانات. وإلا، ارفعوا لي خطأ قيمة (ValueError). ينبغي أن يكون ذلك قرب السطر 48.
حسنًا. هل لدى أحد كود لي؟ نعم؟
طالب:
إذا كان نوع center لا يساوي Coordinate [غير مسموع].
آنا بيل:
لا يساوي Coordinate — نعم، فهذا رفع خطأ قيمة.
طالب:
خطأ قيمة.
آنا بيل:
نعم. جيد، هذا يعالج واحدًا. نعم؟
طالب:
والشيء نفسه لـradius، لكن مع —
صفحة المصدر 6
آنا بيل:
نعم. إذا كان نوع radius، لأنه المعامل الممرَّر، لا يساوي int، فارفع خطأ قيمة. إذا دخلنا أيًّا من هذه الـif، نرفع خطأ القيمة فورًا. الكود لا يكتمل. وفقط إذا لم ندخل هذه أو هذه، نواصل بعد ذلك إنشاء كائني.
ها نحن هنا. سينجح هذان السطران. لا يُرفَع خطأ أو شيء كهذا. لكن هذا السطر هنا يرفع خطأ القيمة عندنا، لأننا حاولنا إنشاء دائرة يكون مركزها عددًا صحيحًا، وهو بالطبع ليس كائنًا إحداثيًّا. وهنا، مرة أخرى، رفعنا خطأ قيمة لأننا حاولنا تمرير سلسلة نصية بوصفها نصف القطر. هل هناك أسئلة عن هذا الكود؟ أوه، نعم، آسفة.
طالب:
إذًا [غير مسموع] من المهم جدًّا لهاتين التعليمتين [غير مسموع]؟
آنا بيل:
نعم، من المهم وضعهما قبل أن تنشئ الكائن فعلًا، لأنك لا تريد إنشاءه إلا إذا — نعم، إلا إذا كان كل شيء مناسبًا.
حسنًا. لنضف الآن تابعًا مفيدًا آخر إلى صنفنا، Circle. بعدما عرّفنا دائرة باستخدام نقطة مركز ونصف قطر، نستطيع إضافة هذه الدالة الصغيرة التي تتحقق مما إذا كان كائن إحداثي آخر داخل دائرتنا. مرة أخرى، لن أفرض أن هذه النقطة كائن إحداثي. لكن يمكنك فعل ذلك في سلسلة التوثيق (Docstring)، أو تنفيذ تحقق (Check)، أو شيء من هذا القبيل. لكننا مرة أخرى سنفترض فقط أن المستخدم الذي يستخدم هذا التابع سيتبع القواعد.
كيف سيعمل هذا التابع؟ الفكرة هنا أننا سنستخدم المركز، وهو كائن إحداثي، ونقطة أخرى، p، أينما كانت. ما سنفعله هو أن نقول: ما المسافة بين هذه النقطة ومركز الدائرة؟ إذا كانت أكبر من نصف القطر، نعرف أن النقطة خارج الدائرة. وإذا كانت أصغر من نصف القطر، نعرف أن النقطة داخل الدائرة.
هذا الكود ينفّذ ذلك فقط. لدينا تعليمة إرجاع (Return Statement) بسيطة فقط ستشغّل تابع distance. هذا تابع كتبناه سابقًا في صنف Coordinate. ولا بأس بذلك، لأنكم تعرفون ماذا؟ point كائن من النوع Coordinate. وself.center — مركز كائن الدائرة هذا الذي أحاول التعامل معه لمعرفة ما إذا كانت نقطة أخرى داخلي أم لا — هو أيضًا كائن إحداثي.
فلم لا؟ كتبنا بالفعل الكود الذي يحسب المسافة بين هاتين النقطتين. فلنستدعه. هنا لدي الشيء قبل النقطة، كائن إحداثي، وصيغة النقطة، والتابع الذي أريد تشغيله على هذا الكائن الإحداثي. ثم بين القوسين، هذا كائن إحداثي آخر. هذا سيخبرني فقط بعدد ما يعبّر عن مدى بُعد هاتين النقطتين. وكل ما نفعله هو إرجاع ما إذا كان ذلك العدد أقل من نصف القطر. هل هذا واضح؟
ومرة أخرى، لا يعمل هذا إلا إذا كان point، الشيء الممرَّر هنا، كائنًا إحداثيًّا. وإلا فسيفشل هذا الكود، لأنه سيحاول تمرير — سيحاول تشغيل تابع distance على سلسلة نصية مثلًا. وبالطبع السلسلة النصية ليس لها تابع distance.
هنا في الأسفل، هذان السطران كما كانا عندنا بالضبط. ننشئ كائن دائرة مركزه عند 2 و2 ونصف قطره 2. ثم لدي كائن إحداثي آخر هنا في الأسفل، عند 1، فاصلة، 1، ولذلك هو بوضوح داخل الدائرة. ستطبع تعليمة الطباعة تلك True. فهذا أساسًا ما كتبته فقط. هذا كائن إحداثي. وهذا هو التابع. وهذا كائن إحداثي آخر.
صفحة المصدر 7
حسنًا، فلنشغّله. هذا هو الكود من الشريحة تمامًا. إذا شغّلت هذا التابع على كائن إحداثي عند 1 و1، في موضع ما هنا، فـTrue. وفي غير ذلك، إذا شغّلته على كائن إحداثي هنا، 10 و10، وهو بوضوح خارج الدائرة، يطبع False. أسئلة؟ حسنًا. نعم، هذا بالضبط ما قلته بالفعل. حسنًا، جيد.
أريدكم الآن أن تجيبوا عن هذا السؤال. لا شيء نبرمجه هنا، لكن لدي تابعي is_inside هذين. الأول هنا، is_inside1، هو بالضبط الذي رأيناه للتو. يشغّل تابع distance هذا مع point وself.center. يبدو is_inside2 مختلفًا قليلًا؛ الفروق ظلّلتها في هذا الإطار. ما أود منكم إخباري به — يمكننا رفع الأيدي — هو: هل هذان التابعان متكافئان وظيفيًّا (Functionally Equivalent)؟ أي هل سيُرجعان الشيء نفسه إذا أُعطيا المدخل نفسه؟ فكّروا في ذلك، ثم سأطلب رفع الأيدي.
من يعتقد: نعم، هما متكافئان وظيفيًّا؟ إذا أُعطيا المدخل نفسه، فسيُرجع كلاهما True أو False؟ ومن يعتقد: لا، ليسا متكافئين وظيفيًّا؟ البعض؟ نصف ونصف. حسنًا. لنفكّر فيما يفعله تابع distance. يُشغَّل على كائن من أي نوع؟
طالب:
Coordinate؟
آنا بيل:
Coordinate، بالضبط. هنا، هل point كائن من النوع Coordinate؟ نعم، حسنًا. ثم هنا، ما المعامل لتابع distance؟ هل هو كائن من النوع Coordinate؟ self.center كائن من النوع Coordinate، نعم. الآن لننظر إلى is_inside2. ما نوع self.center؟
طالب:
Coordinate.
آنا بيل:
Coordinate. ونشغّل تابع distance على هذا الكائن من النوع Coordinate. وما الكائن في قائمة المعاملات هنا؟ ما نوعه؟
طالب:
Coordinate.
آنا بيل:
Coordinate. عندما كتبنا تابع distance، هل يهم أي كائن نستدعي التابع عليه للحصول على المسافة بين هاتين النقطتين؟ لا، صحيح؟ لأن المسافة بين — القول إنني أريد المسافة بين هذه النقطة وهذه النقطة هو نفسه القول إنني أريد المسافة بين هذه النقطة وهذه النقطة. الترتيب فقط مختلف.
بسبب الطريقة التي يعمل بها تابع distance هذا، لا يهم فعلًا أي كائن أستدعي التابع عليه، ما دام كلاهما كائنين إحداثيين، وهما كذلك. هل هذا واضح؟ هل هو جيد؟ هل هناك أسئلة عن هذا لمن كانوا في مجموعة «لا»؟ نعم.
طالب:
إذًا لا يوجد [غير مسموع].
آنا بيل:
self.center؟ self.center كائن من النوع Coordinate، وليس Circle. إذًا self دائرة، لأن self يتحدث عنّي، الصنف الذي أعرّفه حاليًّا. والصنف الذي أعرّفه حاليًّا دائرة. لكن self.center — كتبنا حتى كودًا — نود فرض كونه كائنًا إحداثيًّا. كان يمكننا وضع أقواس حول self.center هذا، لو أردنا، ثم استدعاء distance عليه. هل هذا واضح؟ حسنًا.
حسنًا، هذا كل ما كان لدي بخصوص صنف الدائرة. سنغيّر الاتجاه الآن وننظر إلى الكسور (Fractions) — وضع بسط/مقام هنا (Numerator/Denominator). سننشئ نوع بيانات جديدًا لتمثيل عدد بوصفه كسرًا. أول شيء نحتاج إلى فعله إذًا هو اتخاذ قرار التصميم — ما البيانات التي ستمثّل كسرنا؟ فكّروا في ذلك. أخبروني أنتم. ما رأيكم؟ ما مجموعة البيانات المعقولة التي يمكن أن تمثّل عددنا بوصفه كسرًا؟ عندما تفكّرون في — نعم؟
صفحة المصدر 8
طالب:
[غير مسموع].
آنا بيل:
نعم، مجموعة من شيئين، ربما عددين صحيحين، أحدهما يمثّل البسط (Numerator)، الشيء فوق الخط، والآخر المقام (Denominator)، الشيء تحت الخط. جيد، هذا بالضبط ما كان في ذهني. ما بعض سلوكيات الكسور؟ أخبروني أنتم. ما الأشياء التي ينبغي أن تفعلها الكسور؟ نعم؟
طالب:
[غير مسموع].
آنا بيل:
نعم. نعم، جمعها، وضرب الكسور معًا، وقسمتها، وقلب الكسر (Inverting) أيضًا شيء نستطيع فعله، أي 1 على قيمته الحالية، وما شابه. حسنًا، سنكتب ذلك معًا. الكود الكامل موجود فعلًا في ملف Python. لذلك سأمرّ غالبًا عبر الشرائح فقط، لأنه تدريجي، فيسهل عليّ الحديث عنه. لكن الكود الكامل نفسه موجود بالفعل في ملف Python إذا كنتم ستشغّلونه لاحقًا.
أول شيء سنفعله هو إنشاء صنف الكسر هذا. وسأسمّيه في الواقع SimpleFraction بدلًا من Fraction، لأننا سنحسّن كائن SimpleFraction هذا بعد قليل. هذا سأسمّيه SimpleFraction فقط.
مثلما سبق، أبوه كائن Python العام. مرة أخرى، بسيط جدًّا، جدًّا. لا يفعل شيئًا خاصًّا بعد. أول تابع نحتاج إلى كتابته هو تابع init. كيف نهيّئ كائن كسر؟ بالطبع لا نريد أن يكون البسط أو المقام فارغًا.
عندما ننشئ كائن SimpleFraction، نريد من المستخدم إخبارنا بقيم البسط والمقام. فهذان المعاملان اللذان أود من المستخدم بدء هذا الكسر بهما. ثم ما سمتا البيانات اللتان قررناهما؟ البسط — أي self.num وself.denom ستكونان سمتي البيانات. self.num وself.denom سمتا بيانات، وستُضبطان في البداية على ما يُمرَّر في استدعاء المنشئ ذاك.
حسنًا. هل الأمور جيدة حتى الآن؟ لنكتب تابعًا يساعدنا على ضرب كائني كسر معًا. سنسمّيه times. سيُستدعى تابع times هذا على كائن، الشيء قبل النقطة. ذلك الكائن، الشيء قبل النقطة، سيُربَط بـself. ثم الشيء بين القوسين، المعامل الآخر الوحيد، سيُربَط بـoth.
كيف نضرب كائني كسر معًا؟ خذ البسطين واضربهما، وخذ المقامين واضربهما؛ لديك بسطك الجديد ومقامك الجديد. كيف نأخذ بسطي هذين الكائنين؟
بسط الشيء قبل النقطة الذي يرتبط بـself هو self.num. وبسط الكائن الآخر الذي سيكون في قائمة المعاملات هو اسم معاملي، oth، نقطة، البسط الخاص به، num. هل هذا واضح للجميع حتى الآن؟ نعم، حسنًا. رأيت بعض الإيماءات بالرأس. هذا جيد.
المقام سيكون بالطريقة نفسها. مقامي الجديد هو فقط ضرب مقامي، الخاص بالشيء قبل النقطة، في مقام الشيء في المعامل. لدي إذًا بسطي الجديد ومقامي الجديد. وكل ما سأفعله هو إجراء القسمة وإرجاع هذه القيمة. ما نوع الإرجاع هنا؟ ماذا سيُرجع هذا التابع؟ أي نوع؟
طالب:
عدد فاصلة عائمة؟
صفحة المصدر 9
آنا بيل:
عدد فاصلة عائمة، بالضبط. نعم، جيد. نعم، لأن كل ما أفعله هو قسمة عدد على عدد آخر. حسنًا، ممتاز. هذا ما قلته بالفعل.
الآن نستطيع تعريف تابع آخر، plus، لجمع كائني كسر معًا. شيء مشابه جدًّا، إلا أن البسط سيكون مختلفًا قليلًا، صحيح؟ تأخذ بسط أحدهما في مقام الآخر، زائد بسط الآخر في مقام الأول، تلك العملية المتقاطعة.
المقام هو نفسه، اضرب المقامين معًا فقط. ومرة أخرى نُرجع القسمة — الأعلى مقسومًا على الأسفل — ومرة أخرى سيكون إرجاع هذا التابع عدد فاصلة عائمة. رغم أنني أضرب أو أجمع هذين الكائنين الكسريين معًا، سيكون إرجاعي عدد فاصلة عائمة. جيد.
فلنشغّل الكود. أنشئ كائني كسر بسيط. سيكون الوصول إلى الأول باستخدام متغير اسمه f1. هذا يمثّل العدد 3 على 4. والوصول إلى الثاني بمتغير اسمه f2. وهذا يمثّل الكسر 1 على 4.
إذا وصلت الآن إلى بسط f1، تقول Python: حسنًا، ما الكائن قبل النقطة؟ إنه f1. فما بسطك؟ ضبطته على 3. فيخبرني هذا أنه 3. بسيط إلى حدٍّ ما. الشيء نفسه مع مقام f1 مرة أخرى، تنظر إلى الشيء قبل النقطة. إنه كائن كسر. تقول: هل لديك سمة بيانات denom؟ لديك. وقيمتها 4. إذًا هذا 4.
والآن، ما نتيجة f1.plus(f2)؟ طريقة غريبة جدًّا لكتابتها، لكنها ما لدينا حتى الآن. الشيء قبل النقطة كائن. إنه كائن كسر بسيط. والشيء قبل النقطة، تذكّروا، يرتبط بـself في قائمة المعاملات عندي، لأنها دالة فقط. مثل الدوال المعتادة قبل مجموعة من المحاضرات، نربط أساسًا المعاملات الفعلية (Actual Parameters) عندما نشغّل الدالة بالمعاملات الشكلية (Formal Parameters)، أي الأشياء في تعريف الدالة عندي.
المعامل الفعلي هنا لـself هو الشيء قبل النقطة، f1. والمعامل f2 يرتبط بـoth. هكذا نقرأ ذلك. هذا يجري الجمع فقط. فسيعطيني 1.0 لأنه عدد فاصلة عائمة. وكذلك الـ، الـ، الشيء قبل النقطة يرتبط بـself، وكل معامل آخر في قائمة المعاملات يرتبط بكل شيء آخر باستثناء self. هذا سيحسب 3 على 16 ليعطيني 0.1875. حسنًا. هل هذا واضح للجميع حتى الآن؟
الحيلة هنا هي تذكّر أن الشيء قبل النقطة يرتبط بـself في تعريف التابع. ثم كل ما عداه يرتبط بكل ما عدا self. حسنًا. يسعدني أن كل شيء واضح حتى الآن، لأنني سأجعلكم تكتبون هذا الكود هنا. يبدو كثيرًا، لكن نصفه الأول مجرد إعادة تعريف تابع init لـSimpleFraction.
أريدكم أن تكتبوا هذين التابعين. وسيكونان أساسًا من سطر واحد. get_inverse سيُرجع شيئًا. ويُرجع عدد فاصلة عائمة يمثّل 1 على نفسي. فإذا كان المدخل، كما في هذا المثال هنا، لدي كائن SimpleFraction يمثّل 3 على 4، وإذا استدعيت get_inverse عليه هنا، f1.get_inverse، يصبح self هو f1. وأود منه إرجاع، وبالتالي طباعة، 4 على 3، أي 1.3333333.
هذا هو get_inverse. ثم invert تابع لا يُرجع شيئًا. أي يُرجع None. وبدلًا من ذلك، يبدّل داخليًّا بسط self ومقامه فقط. يصبح بسط self أيًّا كانت قيمة مقامه، والعكس. عندما تستدعيه، لا يطبع هذا شيئًا. لكن بدلًا من ذلك، إذا وصلنا إلى بسط f1 ومقامه، سيكونان قد تبدّلا.
هذا هنا عند السطر 133. سأعطيكم بضع لحظات، ثم يمكننا كتابته معًا. لا ينبغي أن يكون كودًا كثيرًا.
صفحة المصدر 10
حسنًا. كيف نكتب get_inverse؟ تذكّروا أن عليكم إرجاع شيء. كيف نُرجع — نعم؟ 1 على —
طالب:
هل يُرجع فقط على self؟
آنا بيل:
1 على self؟ تذكّر أن self كائن من النوع SimpleFraction. لذلك نحتاج إلى التعامل مع بسطه ومقامه إذا أردنا إجراء الإرجاع، لأننا إذا فعلنا هذا فقط — هل هو هذا؟ نعم، هذا هنا — عندئذ تقول Python — أوه، آسفة، إنها تحاول قسمة 1، عدد صحيح، على كائن من النوع SimpleFraction. وهذا هو الخطأ الذي نحصل عليه هنا. وهي لا تعرف كيف تنفّذ قسمة بين عدد صحيح وSimpleFraction. فكيف نستطيع فعل ذلك بالعمل مع الأعداد الفعلية التي هي جزء من كسري البسيط؟ أي أحد؟ نعم؟
طالب:
[غير مسموع].
آنا بيل:
self.denom مقسومًا على self.num. نعم، نستطيع فعل ذلك، نعم، أو 1 على self.num مقسومًا على self.denom. ذلك أيضًا مقبول. نعم، لكن هذا أنظف قليلًا. الآن denom عدد صحيح، صحيح؟ لأننا عندما ننشئه، نمرّر int. إذًا denom وnum عددان صحيحان، وهذا يعني أن Python تعرف كيف تنفّذ قسمة بين عدد وعدد آخر، نعم. إذا شغّلنا ذلك، يطبع الآن 1.33333 بالضبط. حسنًا. كيف ننفّذ الدالة invert — التابع، آسفة. نعم؟ آسفة، تفضّل.
ملاحظة المترجم: العبارة البديلة «1 على
self.numمقسومًا علىself.denom» في الكلام تُفهَم هنا بوصفها1 / (self.num / self.denom)، لا(1 / self.num) / self.denom؛ التعبير المباشر الذي تؤكده المتحدثة هوself.denom / self.num.
طالب:
أولًا أسندت [غير مسموع] يساوي self.numerator.
آنا بيل:
نعم.
طالب:
[غير مسموع].
آنا بيل:
نعم.
طالب:
[غير مسموع] يساوي new_num [غير مسموع].
آنا بيل:
هكذا، نعم. نعم، هذه طريقة واحدة لفعل ذلك. نعم، ترون الآن أن الوصول إلى البسط والمقام الجديدين يعطيني 4 على 3. هل لديكم طرق أخرى فعلتم بها ذلك؟ نعم؟
طالب:
استخدمت [غير مسموع].
آنا بيل:
نعم، حيلة الصف (Tuple Trick). تعجبني. self.num، نعم. ممتاز. نعم؟
طالب:
[غير مسموع] قلت فقط self.num يساوي self.denom. لكنني لم أقل كليهما.
آنا بيل:
أوه، حسنًا، نعم. ذلك يعمل أيضًا، نعم. ممتاز. كلها طرق صالحة جدًّا لفعل ذلك، جميل. نعم، لاحظوا أنه لا يوجد إرجاع لهذا. لم أرد إرجاع أي شيء. ستُرجع Python تلقائيًّا None. وستكون قيم البسط والمقام الداخلية هذه قد انقلبت، ممتاز. هل هناك أسئلة عن هذا الكود؟ حسنًا.
ملاحظة المترجم: المداخلة الأخيرة غير مسموعة بالكامل، وتُذكر فيها فقط
self.num = self.denom. هذه التعليمة وحدها لا تبدّل القيمتين؛ يلزم حفظ القيمة القديمة أو استخدام إسناد متزامن مثلself.num, self.denom = self.denom, self.num. لا يمكن استنتاج بقية حل الطالب من التفريغ، ونُقل رد المتحدثة كما ورد.
فلنجرّبه قليلًا أكثر. هنا لدي عمليتا الجمع هاتان. هذا بالضبط ما كان لدينا سابقًا، الكود نفسه تمامًا. لكن الغريب — وربما استغربتم أنتم أيضًا ذلك عندما شغّلناه أول مرة — هو أنني أجري عمليات بكائني كسر، ومع ذلك يعطيني تابعا plus وtimes أعداد فاصلة عائمة، وهذا غريب قليلًا، صحيح؟
من الناحية المثالية، إذا كنّا نعمل مع كائنات كسور، أود أن يكون الإرجاع أيضًا كائن كسر، حتى أستطيع بعد ذلك العمل مع المزيد من كائنات الكسور لاحقًا. هذا شيء غريب. وشيء غريب آخر هو أننا إذا طبعنا بعد ذلك أحد الكائنات التي أنشأناها، f1 في هذه الحالة — نستخدم تعليمات الطباعة كثيرًا لتنقيح الأخطاء (Debugging) وما شابه.
صفحة المصدر 11
إذا استخدمت تعليمة الطباعة على كائن من نوع أنشأته — في هذه الحالة SimpleFraction — تُخرج Python هذا. تقول: كائنك كائن من النوع SimpleFraction عند موقع الذاكرة (Memory Location) هذا. لا، شكرًا. هذا ليس مفيدًا جدًّا لي، صحيح؟ ما أود معرفته ربما تمثيل لطيف لكائن الكسر عندي، مثل 3/4. لا يهمني موقع الذاكرة الذي يوجد فيه.
وهناك شيء آخر نود محاولة فعله — هذا صنف يمثّل شيئًا عدديًّا. فمن الأشياء التي قد يريد الناس فعلها بالفطرة استخدام معاملات (Operators) مثل النجمة أو الزائد أو الشرطة المائلة للقسمة والجمع والضرب لهذه الأشياء الكسرية. لكن إذا شغّلنا معامل النجمة بين كائن من النوع SimpleFraction مضروبًا في كائن آخر من النوع SimpleFraction، تعطينا Python خطأ. وسأريكم حتى الخطأ، هنا.
هنا أطبع كائني. فتُخرج هذا، وهو مقبول، لكنه ليس ما أريد. وهذه، بالطبع، رأيناها بالفعل، تطبع هذا. ثم إذا حاولت ضرب كائني SimpleFraction معًا، تقول: لا أعرف كيف أفعل ذلك. إذًا هي أنواع معاملات عملية غير مدعومة (Unsupported Operand Types). معاملا العملية SimpleFraction وSimpleFraction غير مدعومين مع معامل النجمة.
حسنًا، لا مفاجأة في ذلك. كيف يُفترض لـPython أن تعرف كيف تضرب كائني SimpleFraction معًا؟ قبل أن أشغّل هذا البرنامج مباشرة، لم تكن تعرف حتى ما كائن كسر بسيط. لذلك نحتاج إلى إخبارها بكل هذه التفاصيل. وسنفعل ذلك بالضبط.
كل هذه المعاملات — print، وlen اللذان كنّا نستخدمهما، والنجمة، والجمع، وأصغر من، وأكبر من، وحتى الأقواس المربعة لفهرسة شيء — هي في الواقع صيغ مختصرة (Shorthand Notations). إنها عمليات شائعة حقًّا تريد تنفيذها. وتتيح لكم Python استخدام هذه العمليات الشائعة بدلًا من كتابة أسماء الدوال المطوّلة جدًّا هذه.
لكن في الواقع، وراء الكواليس، كل هذه العمليات المختصرة تشغّل تابعًا بالفعل. مرة أخرى، ليس سرًّا — لا أخفض صوتي لأنه سر، لكنه رائع حقًّا فقط. كل هذه العمليات، مثل الضرب أو تعليمة الطباعة، تُستبدَل بتابع فقط.
وأسماء التوابع تبدو هكذا. إنها توابع دندر، تمامًا كما كان تابع init تابعًا خاصًّا تشغّله Python عندما يحدث شيء خاص، مثل أنك تنشئ كائنًا. حسنًا، عندما يحدث شيء خاص، مثل أنك تستخدم معامل الزائد بين كائن من نوعك وشيء آخر، ستشغّل Python أيضًا تابع دندر الخاص هذا وراء الكواليس.
وكذلك هنا، إذا أردت ضرب كائني في شيء آخر، ستشغّل Python تابع دندر الخاص هذا وراء الكواليس. وإذا أردت طباعة كائن من نوعي، ستشغّل Python تابع دندر الخاص هذا وراء الكواليس. حتى شيء مثل تحويل النوع (Casting)، إذا أردت تحويل كائني إلى عدد فاصلة عائمة، أو إلى سلسلة نصية، أو إلى شيء، ستشغّل Python تابع دندر الخاص هذا وراء الكواليس.
ثم هناك مجموعة كاملة من التوابع الأخرى، حتى الفهرسة في قائمة — أو آسفة، ليست قائمة، الفهرسة في كائن من نوعك. إذا أنشأت كائنًا مثل طابور (Queue) أو مكدّس (Stack)، حيث تعرف أن لديك مجموعة تسلسلات من الكائنات، تستطيع إخبار Python كيف تفهرس كائنًا من نوعك، كائنًا هو طابور.
كل هذه الأشياء — كل هذه التوابع يجب أن تُنفَّذ في موضع ما. معظمها الآن غير منفّذ في كائن Python الأساسي، باستثناء str. تابع str في الواقع يطبع موقع الذاكرة لهذا الكائن فقط. هذا بالضبط ما رأيناه من السلوك الافتراضي (Default Behavior). لكن لا شيء من هذه الأخرى منفّذ فعلًا.
صفحة المصدر 12
إذا أردت للكائن الذي تكتبه حاليًّا أن يعمل مع النجمة أو الزائد أو علامة المساواة المزدوجة لاختبار المساواة (Equality) بين هذا الكائن وشيء آخر، فعليك كتابة التابع في تعريف صنفك. عليك تنفيذه لإخبار Python بأن هذا ما تريد فعله عندما يستخدم شخص هذه الصيغة المختصرة الخاصة.
فلنبدأ بالطباعة، لأنها أبسط شيء على الأرجح تريد تنفيذه عندما تنشئ نوع بيانات جديدًا. لأغراض تنقيح الأخطاء، ستجد نفسك تقول بالفطرة: اطبع f1، لطباعة كائن الكسر هذا ورؤية شكله. لذلك فإن تابع str من الأشياء الأساسية حقًّا التي ينبغي تنفيذها مباشرة بعد تابع init.
لننظر إليه في سياق الكائن الإحداثي. هنا لدي كائني الإحداثي، 3 و4. وحتى عندما أطبع هذا الكائن الإحداثي، تخبرني Python بهذه الرسالة التي ما زالت غير مفيدة، أن هذا الكائن من النوع Coordinate عند موقع الذاكرة هذا. حسنًا، لا يهمني.
بدلًا من ذلك، ما أود فعله فعلًا هو القول: أريد تمثيل كائن إحداثي بشيء مثل هذا — قوس زاوي، قيمة الإحداثي x، فاصلة، قيمة الإحداثي y، ثم إغلاق القوس الزاوي. ستكون هذه تعليمة طباعة أكثر إفادة بكثير من موقع الذاكرة الذي يوجد فيه هذا الشيء. فلنفعل ذلك.
ها نحن هنا — كائننا الإحداثي، وdistance، وinit كما كان لدينا من قبل. وهنا أعرّف تابع str عندي، أي __str__، دون أي معاملات أخرى باستثناء self، أي إنني أستدعي هذا التابع على كائن. وماذا سيفعل هذا؟ سيُرجع، لا يطبع، يُرجع سلسلة نصية.
والسلسلة النصية ستمثّل الشيء الذي تريد طباعته. إذًا يُرجع سلسلة نصية، ولا يطبعها. كيفما تريد تكوين السلسلة النصية، فالأمر متروك لك. هنا استخدمت فقط وصل (Concatenation) مجموعة من الأشياء. أصل القوس الزاوي مع قيمة x لكائني الحالي محوّلة إلى سلسلة نصية، موصولة بالفاصلة، موصولة بقيمة y لكائني الحالي محوّلة إلى سلسلة نصية، موصولة بقوس زاوي مغلق. هذا قرار التصميم الذي اتخذناه لكيفية طباعة كائن إحداثي. هل هذا واضح للجميع حتى الآن؟
حسنًا. إذا أردت استخدام سلسلة منسّقة (f-string) لتكوين هذا الشيء الذي ستُرجعه، فهذا مقبول تمامًا. وإذا أردت إنشاء متغير بين الإرجاع والتعريف هنا، تواصل الوصل معه فقط، بحيث يمكنك وصله بأسطر جديدة وما شابه، فهذا مقبول تمامًا أيضًا. في النهاية عليك فقط إرجاع تلك السلسلة النصية التي تمثّل الشيء الذي تريد طباعته.
لنرَ الآن ما يحدث عندما نشغّل الكود فعلًا. هنا أنشئ كائنًا إحداثيًّا، ثم أطبع ذلك الكائن الإحداثي. حسنًا، تقول Python: لقد استدعيت للتو صيغة مختصرة خاصة على كائن من النوع Coordinate. دعني أرَ ما إذا كنت نفّذت تابع str. تنظر في تعريف الصنف.
ترى تابع str منفّذًا هنا. ثم تشغّل الكود بالداخل وتقول: أوه، تريد أخذ قيمة x وقيمة y الخاصتين بـc ووصلهما بهذه الأشياء هنا. بالتأكيد، أستطيع فعل ذلك لك. ثم تذهب وتطبع هذا على الشاشة. حسنًا. رائع جدًّا، صحيح؟ الآن نستطيع تقرير كيفية طباعة الكائنات التي ننشئها.
حسنًا، فلنحاول استيعاب الأنواع هنا. إذا طبعنا c هذا، فإن c مثيل لكائن إحداثي. إنه كائن فعلي نتعامل معه. ليس تعريف الصنف. وليس شيئًا مجردًا من هذا القبيل. إنه كائن فعلي، مثلما العدد الصحيح 3 كائن فعلي. إذا طبعنا ذلك الـc، تستخدم Python تابع str. حسنًا، ماذا لو طبعنا نوع c؟ ليخبرني أحد: ما نوع c؟
طالب:
Coordinate.
صفحة المصدر 13
آنا بيل:
Coordinate. نعم، إنه اسم الصنف الذي عرّفناه. عندما نطبع نوع c، تقول Python: هذا صنف Coordinate، وهو منطقي، لأننا إذا استبدلنا بنوع c هنا ما هو عليه، Coordinate، فسنحصل على تعليمة الطباعة نفسها. إذا طبعنا Coordinate فقط، تقول Python إن هذا صنف من النوع Coordinate. حسنًا، هذان السطران متكافئان.
ثم دعوني أذهلكم قليلًا أكثر. ماذا لو طبعنا نوع Coordinate؟ حسنًا، ما Coordinate؟ إنه نوع. نعرّف نوعًا جديدًا في Python اسمه Coordinate. إذًا Coordinate نوع بيانات جديد في Python. ولذلك فإن نوعه هو type. كل شيء في Python كائن، حتى الأنواع.
حسنًا. شيء آخر — استخدمنا نوع شيء يساوي شيئًا آخر عندما تحقّقنا من أن نوع مركز الدائرة هو Coordinate. هذه طريقة للتحقق من الأنواع. وطريقة أخرى هي استخدام هذه الدالة isinstance، كملاحظة جانبية فقط. يمكنك التحقق من أن c مثيل من النوع Coordinate باستخدام هذا التابع isinstance. وسيخبرك هذا بـTrue. ولمقارنة ذلك فقط، يمكنك القول isinstance مع 3 وint. سيقول ذلك أيضًا True، لأن 3 كائن من النوع عدد صحيح. فهي طريقة أخرى فقط للتحقق من الأنواع.
حسنًا. في بقية هذه المحاضرة، أود العودة إلى صنف الكسر عندنا وتحسينه. بعدما عرفنا توابع دندر، فلننفّذ مجموعة كاملة منها لمساعدتنا ومساعدة من يريدون استخدام صنفنا على استخدامه بطريقة أكثر كفاءة. سننفّذ معامل النجمة ومعامل الزائد. وسننفّذ الطباعة. ثم سننفّذ التحويل إلى عدد فاصلة عائمة.
حسنًا، أول شيء ينبغي على الأرجح إضافته هو تابع str، لأنه سيساعدنا عندئذ في تنقيح الأخطاء عندما نطبع كائنًا من النوع Fraction. فلنعرّف __str__ — مرة أخرى، دون معاملات باستثناء self، لأنه الكائن الذي نستدعي هذا التابع عليه.
ومرة أخرى، أيًّا كانت الطريقة التي تريد تكوين هذه السلسلة النصية بها، فالأمر متروك لك. يمكنك استخدام سلاسل منسّقة أو متغير تواصل الإضافة إليه. فعلتها مباشرة هنا بالوصل فقط. لدي البسط، شرطة مائلة، المقام، بوصفها طريقة معقولة جدًّا لتمثيل سلسلة نصية — أي 3/4 على أنه 3 على 4. حسنًا. شيء ينبغي تتبّعه، على ما أظن، هو أنك إذا كنت تصل، فعليك فقط تذكّر التحويل إلى سلاسل نصية إذا كان عددًا أو شيئًا ليس سلسلة نصية.
فلنجرّب ذلك. لدي ثلاثة كائنات كسور هنا. الأولان رأيناهما بالفعل. لدي كسر يمثّل 3 على 4، وكسر يمثّل 1 على 4. وf3 سيكون الآن كسرًا يمثّل 5 على 1. إذا طبعنا f1، تسأل Python مرة أخرى: هل نفّذت تابع str في تعريف صنفك؟ نعم، فعلت. أحسنت. دعني أستخدمه.
ثم تستخدم هذا. تأخذ بسط f1، شرطة مائلة، بسط — آسفة، مقام f1. سيطبع هذا 3، بسط f1، شرطة مائلة، مقام f1. والشيء نفسه مع f2، إلا أنها ستأخذ الآن بسط f2 ومقامه، 1/4.
لاحظوا أنها الآن لا تجري القسمة كما فعلت من قبل — أو آسفة، لا عليكم. لم نصل إلى هناك بعد. لا يوجد شيء نقسمه. تأخذ البسطين والمقامين فقط وتطبعهما. لا تجري أي قسمة. الآن عندما نطبع 5 — كائن الكسر الذي يمثّل 5 على 1 — يطبع 5/1. لا يعجبني ذلك، لأنه يبدو غريبًا. هل يعجبكم؟ لا.
سأجعلكم تصلحونه إذًا. غيّروا تابع str قليلًا فقط، بحيث إذا كان المقام 1، تجعله يطبع البسط فقط. وإلا يكون التمثيل كما من قبل، البسط، شرطة مائلة، المقام. ينبغي أن يكون هذا في الأسفل عند السطر 140 — أين هو؟ بعيد جدًّا في الأسفل، 265.
صفحة المصدر 14
حسنًا. هل لدى أحد كود لي؟ نعم؟
طالب:
[غير مسموع].
آنا بيل:
نعم.
طالب:
ثم ننفّذ تعليمة else ونضع الأرقام التي كانت لدينا تحت else؟
آنا بيل:
نعم، نستطيع استخدام else. هذه الـelse ليست مطلوبة، كما أظن، لأننا إذا دخلنا if، نُرجع فورًا فقط. وإلا فسننفّذ الشيء المتبقي فقط، لكن لا بأس بها تمامًا. نعم، فلنشغّل ذلك. a كسر يمثّل 1 على 4. فطبعه بصورة لطيفة، 1 على 4. وb، الكسر 3 على 1، يطبع 3 فقط. جيد. هل هناك أسئلة عن هذا الكود؟
حسنًا. لكن في بقية المحاضرة، لن نستخدم تابع str المعدّل هذا، الألطف والأفضل. فلننسَ ما فعلناه للتو، ولنتذكّر فقط أنه يبدو هكذا. حسنًا. لننفّذ الآن توابع دندر للجمع والضرب والقسمة وما شابه.
سأنفّذ الضرب فقط، لأنه ليس طويلًا بالقدر نفسه بالنسبة إلى البسط، فمجرد عامل سهولة هنا. في الجانب الأيسر، لدي كود الكسر البسيط القديم. وفي الجانب الأيمن كود الكسر الجديد عندي. كود SimpleFraction القديم، تذكّروا، كان فيه هذا التابع times الذي يأخذ self وoth، ويحسب بسطًا جديدًا ومقامًا جديدًا، ويُرجع هذا.
كود الكسر الجديد عندي لن يحتاج بعد الآن إلى استدعاء times. لن ننفّذ حتى تابعًا اسمه times. بدلًا من ذلك، سننفّذ التابع وراء الكواليس للصيغة المختصرة، النجمة، لضرب كائني كسر.
نحتاج إلى تنفيذ def __mul__. قائمة المعاملات هي نفسها، لأننا ما زال لدينا شيء قبل النجمة وشيء بعدها، بوصفهما كائني الكسر اللذين نود ضربهما. داخل الكود نفسه، حسابات البسط الجديد والمقام الجديد هي نفسها أيضًا. ما زلنا نأخذ بسطي self وother، ومقامي self وother، ونضربها معًا. المختلف هو قيم الإرجاع، صحيح؟ ما كان نوع الإرجاع لتابع times؟
طالب:
[غير مسموع].
آنا بيل:
عدد عشري. نعم، عدد فاصلة عائمة، بالضبط. ما نوع الإرجاع لتابعي الجديد، mul؟
طالب:
كسر؟
آنا بيل:
كسر، بالضبط. نعم، أجري عمليات بكائنات كسور. لذلك أتوقع أن يكون نوع إرجاع هذا التابع، النجمة، __mul__، أيضًا كائن كسر. فأستطيع عندئذ مواصلة العمل مع كائنات الكسور طوال كودي، دون القلق بشأن ما إذا كان هذا الشيء أصبح الآن عدد فاصلة عائمة أم لا.
حسنًا، كيف ننشئ كائن الكسر هذا؟ تمامًا كما ننشئ كائن كسر عادي في الشرائح السابقة، هنا. هذا مثال على إنشائنا كائن كسر جديدًا — البسط 1، والمقام 4. الأمر نفسه هنا، سيُرجع هذا التابع كائن كسر جديدًا بسطه الشيء الذي حسبته للتو، top. ومقامه الشيء الذي حسبته للتو، bottom. هل هذا واضح؟ حسنًا.
صفحة المصدر 15
هذا إذًا يُرجع عدد فاصلة عائمة. وهذا يُرجع كسرًا. فلنشغّله. a كسر 1 على 4. وb كسر يمثّل 3 على 4. جيد، هذه الأعداد التي عملنا معها. إذا طبعنا a، تقول تعليمة الطباعة: هذا كائن الكسر 1/4، وتمثيله 1/4.
إذا استخدمت الآن معامل النجمة بين a وb، فإن الشيء قبل النجمة يشبه نوعًا ما الشيء قبل النقطة. إنه self. يرتبط بـself في __mul__ عندي. والشيء بعد النجمة، المعامل الثاني إن صح التعبير، هو المعامل الواحد الذي يأخذه تابعي، other.
سيشغّل هذا تابع mul وراء الكواليس. عندما ترى Python تلك النجمة، تسأل: هل لديك تابع mul منفّذ في صنفك Fraction؟ لأن الشيء قبل النجمة كائن كسر. نعم، لدينا. ماذا يُرجع؟ ينفّذ الضرب. وفي النهاية، إرجاع هذا التابع هو هذا الشيء هنا. صنعت هذا حرفيًّا — نسخت هذا فقط من الإرجاع باستخدام أعداد a وb. ينشئ كائن كسر جديدًا بسطه 3 ومقامه 16. إذًا c يساوي Fraction، أقواس، 3 و16، وهو أساسًا كائن كسر آخر فقط.
عندما أطبع c الآن، سيستخدم تابع str لكائن كسر، لأن c كائن كسر هنا مباشرة. هذا بالضبط ما كان عليه c. سيطبع هذا أيضًا بالطريقة التي طلبنا طباعة كائنات الكسور بها: البسط، شرطة مائلة، المقام، 3/16. هل هذا واضح للجميع حتى الآن؟
حسنًا. الأسطر التالية كلها متكافئة. استخدام الصيغة المختصرة — طريقة لطيفة جدًّا وموافقة جدًّا لأسلوب Python لضرب كسرين معًا. لكن وراء الكواليس، هذا يشغّل تابعًا فقط. بالطبع، إذا أردت فعلًا، تستطيع استخدام الطريقة القديمة نفسها لاستدعاء تابع: الشيء قبل النقطة، نقطة، اسم التابع، أقواس، قائمة المعاملات.
هنا، الشيء قبل النقطة هو a، نقطة، اسم تابعي، __mul__، أقواس، وكل قائمة المعاملات باستثناء الشيء الذي أستدعيه عليه. هذان إذًا متكافئان. وبالطبع ذكرت المرة الماضية طريقة آمل أن تزيل غموض تشغيل هذه التوابع، حيث يصبح self هذا الشيء قبل النقطة.
يمكنك استدعاء التابع على اسم نوع بياناتك، النوع الذي تنشئه حاليًّا، Fraction. إذًا Fraction ليس مثيلًا. a كان مثيلًا. كان كائنًا فعليًّا أنشأناه. لكن Fraction مجرد اسم صنفي. إذا استدعيت التابع على اسم صنفك، فستتوقع Python قائمة المعاملات كاملة — شيئًا لـself، وشيئًا لـother، وشيئًا لأي معاملات لديك.
لذلك هناك سنمرّر صراحة كليهما، a وb، ضمن قائمة المعاملات عندي، لأن الشيء قبل النقطة ليس كائنًا. فلا تربطه بـself. لكنني لن أشغّل أبدًا، أبدًا، أبدًا، سطر كود كهذا، هذا الأخير هنا. هذا لمعلوماتكم فقط. إنه غير موافق لأسلوب Python (Non-Pythonic). إنه مطوّل جدًّا فقط.
توابع دندر هذه تساعدنا إذًا على تجريد مجموعة من هذه التفاصيل بعيدًا. كم سيكون مزعجًا استخدام صيغة النقطة دائمًا عندما نريد ضرب عدد صحيح في عدد صحيح آخر؟ هل تتخيلون كتابة 3.__mul__(4) باستمرار؟ سيكون ذلك كودًا سيئًا جدًّا. وستستغرق قراءته وقتًا طويلًا جدًّا.
نجرّد كل تفاصيل استدعاء هذه التوابع إلى هذه الصيغ المختصرة الصغيرة اللطيفة. وكما قلت، توجد هذه الصيغ المختصرة لعمليات مختلفة كثيرة. رأينا الطباعة. يمكنك تنفيذ الطول والمقارنات، مثل المساواة، وحتى الفهرسة في الأشياء. تستطيع دائمًا تجريد كل تلك الأشياء إلى صيغ مختصرة. ووراء الكواليس، ستُشغَّل هذه التوابع.
ملاحظة المترجم: حُفظ المثال المنطوق
3.__mul__(4)كما ورد. عند كتابة استدعاء تابع على عدد صحيح حرفي في Python، يمكن استخدام(3).__mul__(4)لتجنّب التباس النقطة مع صيغة العدد؛ المقصود هنا توضيح الاختصار3 * 4.
صفحة المصدر 16
حسنًا. الفكرة الكبرى، بالضبط ما قلته: كل هذه العمليات الخاصة التي كنّا نستخدمها بالفعل، وراء الكواليس تُشغَّل هذه التوابع. وكُتبت هذه التوابع داخل تعريف الصنف للأنواع التي كنّا نستخدمها. عندما نفهرس قائمة، L، أقواس مربعة، 3 أو أي شيء، يوجد تابع يُستدعى وراء الكواليس في صنف القائمة لأخذ العنصر عند الفهرس 3. لا أتذكّر اسم تابع دندر لذلك، لكنه ربما شيء مثل __index__. لا أعرف. لكن هناك تابعًا ما وراء الكواليس. حسنًا، لنفعل بضعة أشياء أخرى. عذرًا؟
ملاحظة المترجم: الاسم الذي تخمّنه المتحدثة هنا هو
__index__. التابع المرتبط بأخذ عنصر بالصيغةL[3]هو__getitem__؛ أما__index__فيُستخدم للتحويل إلى عدد صحيح صالح للفهرسة. نُقل التخمين والتردد كما وردا.
طالب:
إذا نسينا اسم دندر لشيء، هل هناك طريقة نسأل بها Python؟
آنا بيل:
لا تستطيع سؤال Python، لكن يمكنك النظر إلى التوثيق (Documentation). أظن أنه في python.org. هناك موقع يسرد أساسًا كل ما هو تابع دندر، نعم، تحت فئات مثل جميع أشياء الفهرسة وجميع الأشياء العددية، نعم.
حسنًا. لنفعل شيئًا آخر. لنقل إن — حسنًا، نعمل مع كائنات كسور. وتوابع دندر التي نكتبها تُرجع الآن كائنات كسور أخرى. فلنسمح للمستخدم بفرصة تحويل أحد كائنات الكسور هذه إلى عدد فاصلة عائمة، في حال أراد الحصول على قيمة الفاصلة العائمة لـ3/16. هذا شيء معقول جدًّا قد يريد فعله. فلنسبق حاجته ونضيف ذلك ضمن تعريف صنفنا.
لتحويل الأشياء إلى عدد فاصلة عائمة، في هذه الحالة بالتحديد، تابع دندر لهذا هو __float__. وكل ما سيفعله هو أخذ بسط self وقسمته على مقام self. سينفّذ هذا قسمة فقط. self.num عدد. وself.denom عدد آخر. ينفّذ القسمة. وهذا يُرجع عدد فاصلة عائمة.
هنا، عندما نضرب، c يساوي a في b، تذكّروا أن c أصبح كائن كسر بسطه 3 ومقامه 16. هل تتذكرون ذلك؟ عندما نحوّله بعد ذلك إلى عدد فاصلة عائمة هنا في الأسفل، تقول Python: هل نفّذت تابع دندر __float__؟ أوه، نعم، فعلت. دعني أنفّذ ببساطة الشيء الذي تريد مني فعله بداخله. تأخذ 3 وتقسمه على 16، وتطبع 0.1875.
حسنًا. فلنجرّبه قليلًا أكثر. هنا لدي كائنا كسر — أحدهما يمثّل 1 على 4، والآخر يمثّل 2 على 3. أضرب هذين معًا. ومرة أخرى يعطيني هذا كائن كسر، لأنه يشغّل تابع دندر mul. وتابع دندر mul يعطيني كائن كسر ببسط ومقام جديدين.
عندما أطبع إرجاع ذلك، عندما أطبع c، يطبع هذا البسط الجديد، وهو 2 في 1، مقسومًا على المقام الجديد، وهو 4 في 3. فيطبع 2 على 12. هل يبدو هذا جيدًا لكم؟ أعني، يبدو جيدًا. لكن افترضوا أنكم تجرون حسابات مع مجموعة كبيرة من الأعداد، وعند نقطة ما تحصلون على بسطين كبيرين جدًّا ومقامين كبيرين جدًّا.
ثم تحدّقون فيه مدة كافية وتدركون أن ذلك البسط الكبير مقسومًا على ذلك المقام الكبير هو في الواقع شيء مثل 1 على 4. فهذا غير مختزَل (Not Reduced)، وهو مقبول. كودنا لا ينفّذ الاختزال (Reduction). لكن سيكون لطيفًا كتابة تابع يسمح للمستخدم باختزال كسر. سيكون ذلك لطيفًا حقًّا. هل نستطيع إصلاح هذا؟ نعم، نستطيع. وإلا لما كنّا هنا.
فلنكتب هذا التابع لاختزال كائن كسر. يبدو كثيرًا، لكنه ليس كذلك، ثقوا بي. إنه مجموعة تعليمات if/else فقط. الجزء الأول منه دالة مساعدة (Helper Function) صغيرة، لا تابع. لاحظوا أنه لا يوجد self في دالة gcd هذه. هذه مجرد دالة عادية سأستخدمها لمساعدتي على الحصول على القاسم المشترك الأكبر (Greatest Common Divisor) للمعاملين، n وd.
صفحة المصدر 17
لأنه عندما يكون لدي عددان، إذا أردت اختزالهما، أجد القواسم المشتركة الكبرى. وسأقسم الأعلى والأسفل على ذلك القاسم. وسيساعدني ذلك على اختزاله. فدالة gcd هذه تساعدني على إيجاد هذا القاسم المشترك الأكبر.
حسنًا، هنا أعرّف الدالة فقط. لا أستخدمها فعلًا. ثم لدي if وelif. إذا كان المقام 0، فهناك شيء غريب للغاية. لذلك سأُرجع None فقط، لأن وجود كسر مقامه 0، ربما يعني أن شيئًا ما سار على نحو خاطئ.
وإلا، إذا كان المقام 1، فلا نحتاج إلى تنفيذ أي اختزال. لا حاجة إلى الاختزال. نُرجع البسط فقط. وفي else، لدي عددان فعليّان قد أستطيع ربما اختزالهما. فلنختزلهما فقط. السطر الأول هنا يشغّل هذه الدالة، هذه الدالة المساعدة التي كتبتها، على البسط والمقام لأخذ القاسم المشترك الأكبر. إذا كان 2 على 12، فسيجد 2.
ثم السطر التالي هنا يأخذ البسط ويقسمه على ذلك القاسم المشترك الأكبر، ويحوّله إلى int، لأنني أريد أن يكون بسطي int ومقامي int. سيأخذ البسط ويقسمه على 2 مثلًا.
والشيء نفسه مع المقام؛ سأخذ مقامي وأقسمه على الـgcd نفسه الذي وجدته، مع التحويل إلى int. سيُستخدم بسطي الجديد ومقامي الجديد الآن لإنشاء كائن كسر جديد في الصورة المختزلة (Reduced Form)، أي 1/6 للمثال 2/12.
حسنًا، ها هو. هذا مثالي السابق، حيث ضربت ذلك الشيء الذي أعطاني 2/12. ثم إذا نفّذت c.reduce، ستستدعي Python تابع reduce على c، أي الكائن الذي بسطه 2 ومقامه 12. ثم سيختزله هذا إلى 1 على 6. وسيطبع، أي يستدعي تابع الطباعة str على كائن من النوع Fraction ليعطيني 1 على 6. هل هذا واضح للجميع؟ نعم؟
طالب:
[غير مسموع].
آنا بيل:
يمكنك وضعها خارج reduce. لكن لأنها تُستخدم تحديدًا في reduce، نود إبقاءها بداخله فقط. إذا لم تحتج أشياء أخرى إلى استخدامها، فسنبقيها فقط في النطاق (Scope) الذي تحتاج إلى الوجود فيه. لكنها تستطيع أن تكون خارجه، نعم.
حسنًا. لكن هناك شيء غريب هنا، صحيح؟ هذه الـelif هنا. ما النوع الذي يُرجَع من else؟ أخبروني أنتم. ما هذا النوع هنا الذي يُرجَع في الأسفل في else؟ Fraction. وما النوع الذي يُرجَع في elif؟
طالب:
هل هو int؟
آنا بيل:
نعم، int. إذا صادف أن المقام 1، فإن هذا التابع، reduce، يُرجع عددًا صحيحًا. وإذا لم يكن كذلك، يُرجع كسرًا. إذا كنت عند نقطة ما في المستقبل تخلط — وصادف أنك تختزل شيئًا مقامه 1، فأنت تعمل الآن مع أعداد صحيحة. ثم ربما ستجري عمليات إضافية بخلط ذلك مع كائنات كسور.
كمثال هنا، لدي كائن كسر — a، 4 على 1؛ وb، 3 على 9. اختزال a يعطيني 4. جيد، إنه العدد الصحيح 4. واختزال b يعطيني 1 على 3. إنه كسر، 1 على 3. النوع، فقط لأريكم بالضبط أنني لا أكذب — نوع a بعد الاختزال هو int. هذا ما يفعله الكود. ونوع b بعد الاختزال هو Fraction.
صفحة المصدر 18
عندما ننفّذ بعد ذلك معامل النجمة بين ar وbr، ستقول Python: أنت تحاول ضرب int في Fraction. هل أخبرتني يومًا كيف أفعل ذلك؟ لا، صحيح؟ أخبرناها كيف تضرب كسرًا في كسر آخر، لكن ليس int في Fraction. ولذلك ستفشل Python هنا.
من الأشياء التي يمكنك فعلها لإصلاح ذلك تغيير هذه الـelif هنا. فلنجعل كل شيء متسقًا. أريد منكم تنفيذ هذا التغيير. بدلًا من إرجاع self.num، أرجِعوا لي كائن كسر يمثّل البسط. حسنًا، هل يعرف أحد؟ تغيير صغير فقط. بدلًا من إرجاع self.num، ماذا ينبغي أن أُرجع؟ كيف أنشئ كائن كسر؟ استدعاء اسم Fraction فقط، صحيح؟ ماذا يفترض أن يكون بسط كائن الكسر هذا؟ إنه هناك بالفعل، self.num. وما مقام كائن الكسر هذا؟
طالب:
[غير مسموع].
آنا بيل:
نعم، بالضبط. إنه يُرجع فقط كائن كسر بسطه self.num ومقامه 1، بالضبط. حسنًا، الآن جميع الحالات المختلفة، باستثناء هذا المقام الغريب العارض الذي يساوي 0 — إذا حدث ذلك، فربما سار شيء خطأ — كل الحالات الأخرى تُرجع كائن كسر، وهذا جيد لأنها أصبحت الآن متسقة.
طالب:
[غير مسموع]؟
آنا بيل:
أوه، نعم، بالضبط. قلنا بالفعل إننا لا نريده أن يكون 5 على 1. لكن هذا يستخدم في الواقع تابع str القديم، حيث لم نفعل ذلك التحقق. لذلك سيطبع 5 على 1. لكن إذا نفّذنا التحقق: إذا كان self.denom == 1 فأرجِع str لـself.num — إذا أضفنا ذلك، فلن يفعل ذلك، نعم. لكن هذا يستخدم فقط التابع القديم الذي لا ينفّذ لنا ذلك التنسيق اللطيف. أسئلة؟ حسنًا، عملنا كثيرًا على إرجاع كائنات جديدة من النوع نفسه الذي نكتبه. هذا شيء جديد اليوم.
حسنًا. ما الغرض من هاتين المحاضرتين؟ آمل أنه يوضّح أن جمع البيانات والسلوكيات معًا مفيد جدًّا. الهدف النهائي عندما نكتب البرامج هو كتابة كود مقسّم إلى وحدات (Modular) ومنظّم (Organized)، لأنك قد تريد في المستقبل البناء على هذا الكود.
في المستقبل قد تريد قراءة هذا الكود لاستخدامه لشيء آخر. وفي المستقبل قد يريد أشخاص آخرون قراءة هذا الكود أو استخدام هذا الكود، هذا الصنف الذي كتبته، لبناء أصناف أكثر تعقيدًا، مثلما استخدمنا صنف Coordinate لبناء صنف Circle.
قد يستخدم أشخاص آخرون صنف Circle عندك لبناء — لا أعرف — صنف كرة (Sphere Class) أو شيء كهذا، شيء أكثر تعقيدًا. لذلك من اللطيف حقًّا إنشاء أنواع البيانات الصغيرة هذه التي هي منظّمة ومقسّمة إلى وحدات. نجمع أساسًا هذه البيانات — أي ممَّ يتكوّن كائنك — والسلوكيات معًا، حتى نستطيع استخدام هذه الكائنات بطريقة متسقة بصورة جميلة.
تذكّروا أنه عندما كنّا نتعلّم الدوال، كانت فكرتا التفكيك (Decomposition) والتجريد (Abstraction) مهمتين جدًّا. الدوال أخذت أساسًا جزءًا من الكود وفكّكته إلى وحدة واحدة (Module) نستطيع إعادة استخدامها في مواضع كثيرة، كثيرة، مختلفة. وجرّدنا تفاصيل الدالة عبر سلاسل التوثيق، حتى لا يضطر الناس إلى الخوض بصعوبة في مجموعة كاملة من الكود لمعرفة ما تفعله الدالة. كل ما فعلوه هو قراءة سلسلة التوثيق، وعرفوا بالضبط ما نريده.
صفحة المصدر 19
الآن، البرمجة كائنية التوجه في أصناف Python لها تلك القوة الكبيرة نفسها في التفكيك والتجريد، صحيح؟ فيها مجموعة من الوحدات التي ننشئها هنا، حيث نجمع البيانات والسلوكيات، حتى نستطيع إنشاء مجموعة كاملة من الكائنات التي تتصرف بالطريقة نفسها تمامًا — متسقة بصورة جميلة — بحيث نعرف أنني إذا أنشأت كائنًا إحداثيًّا هنا، فستكون له قيمة x وقيمة y. وإذا أنشأت كائنًا إحداثيًّا آخر، فستكون له أيضًا قيمة x وقيمة y. لن تكون له فجأة قيم x وy وz.
إنشاء هذه الكائنات التي تعمل بطريقة متسقة هو شيء — التفكيك والتجريد يعملان مع أفكار التفكيك والتجريد، تمامًا كما فعلت الدوال. حسنًا. في المحاضرة القادمة سنبدأ بـ — سنقدّم قليلًا أكثر عن هذه الأصناف. ثم سنبدأ بالوراثة (Inheritance)، أي جعل الآباء كائنات أنشأناها. حسنًا.