النص الكامل للمحاضرة 19: الوراثة
النص الكامل للمحاضرة 19: الوراثة
المصدر الأصلي: النص الرسمي الإنجليزي، PDF، فيديو المحاضرة الرسمي، صفحة المقرر.
النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة عربية غير رسمية، وليست معتمدة من MIT ولا تعني تأييده لها. المواد الأصلية المشمولة والترجمة خاضعة لرخصة CC BY-NC-SA 4.0: الاستخدام غير التجاري، مع النسبة، ومشاركة التكييفات بالرخصة نفسها. لا تشمل الرخصة الافتراضية مواد الأطراف الثالثة المستثناة.
ملاحظة المترجم عن التغطية: تُرجمت صفحات النص الرسمي الثماني عشرة، من البداية حتى نهاية المحاضرة، فقرةً فقرةً دون تلخيص. تتبع الأقسام فواصل الصفحات المادية في PDF الرسمي، لا تقسيمات موضوعية، وتحافظ على ترتيب الفقرات ومداخلات الحضور والتكرار والإشارات الصوتية. تُحفظ أسماء Python والتعبيرات البرمجية، وتُفصل أي ملاحظة تفسيرية للمترجم عن كلام المصدر. مصدر المقابلة المحلي هو
content-src/mit-6100l/extracted/1jicSeVbkfCJ6PQAOsX0o52Z998OZwDoD_transcript.txt، مع النسخة.layout.txtللتحقق من حدود الفقرات.
صفحة المصدر 1
MITOCW | المحاضرة 19 من 6.100L، النسخة متعددة المقاطع، الإصدار 2.mp4
[صرير]
[حفيف]
[نقرات]
آنا بيل (ANA BELL):
حسنًا، لنبدأ. محاضرة اليوم هي الثالثة من أربع محاضرات عن فكرة البرمجة كائنية التوجه (Object-Oriented Programming)، وإنشاء أنواع الكائنات الخاصة بنا باستخدام أصناف Python (Classes). سيتناول معظم محاضرة اليوم فكرة الوراثة (Inheritance). لكن قبل الوصول إليها أود أن أراجع قليلًا الأمور الرئيسية التي رأيناها بالفعل. وفي أثناء ذلك سنكتب نوع بيانات جديدًا لشيء أكثر تجريدًا، صنف للحيوان، أكثر تجريدًا مما رأيناه من قبل. ثم عندما نصل إلى فكرة الوراثة سنبني على صنف الحيوان هذا بإضافة بعض كائنات الحيوانات الأخرى.
إذًا الفكرة الرئيسية وراء إنشاء أنواع البيانات الخاصة بنا هي أننا نريد محاكاة ما يحدث في الحياة الواقعية، صحيح؟ وفي الحياة الواقعية لدينا أساسًا كل هذه الكائنات المختلفة في العالم، صحيح؟ لكن يمكن تجميع هذه الكائنات وفق بعض الفئات، صحيح؟ في هذه الشريحة بالتحديد لدي ستة كائنات مختلفة. لكن الثلاثة على اليسار يمكن نوعًا ما جمعها معًا، صحيح؟ نعرف أنها نوع من القطط. وعلى هذا الأساس نعرف أنه يمكن وصف كل هذه القطط باستخدام بعض الخصائص المشتركة والسلوكيات المشتركة.
بالنسبة إلى هذه القطط سأقول إن لكل منها اسمًا وعمرًا ولونًا مرتبطًا بها، صحيح؟ إذًا أعرف أن القطط جميعًا سيكون لها عمومًا اسم وعمر ولون، ثم مجموعة متشابهة من السلوكيات. أما الأشياء على اليمين، تلك الكائنات الثلاثة، فأعرف أنه يمكن تصنيفها معًا. لنقل إنها أرانب برية.
ولنقل إنني لا أعطي الأرانب البرية أسماء فعلًا. لذلك سأصنّفها أيضًا باستخدام خصائص مشتركة، عمر ولون فقط، بلا اسم. ثم إن الكائنات الثلاثة على اليمين لها أيضًا مجموعة مشتركة من السلوكيات تختلف عن الكائنات على اليسار. ولذلك نحاول محاكاة فكرة هذه التصنيفات وأنواع البيانات التي نراها في الحياة الواقعية.
حسنًا، مراجعة صغيرة: عندما نعرّف نوع بيانات خاصًّا بنا في Python، نقرر مجموعة من السمات (Attributes). ويمكن أن تكون السمات بيانات أو إجراءات (Procedures). البيانات، فكّروا فيها بوصفها المتغيرات التي يتكون منها الكائن. وهذا شيء تقررونه أنتم. بالنسبة إلى كائن الإحداثي (Coordinate)، رأينا هذا المثال مرات كثيرة، قررنا قيمتي x وy. أما بالنسبة إلى فكرة الحيوان الأكثر تجريدًا، فيمكن أن نقول ببساطة إننا نصف الحيوان بعمره، أي المدة التي عاشها منذ ولادته.
أما السمات الإجرائية فقد نفّذناها باستخدام الأساليب (Methods) داخل أصناف Python. والفكرة وراءها هي ببساطة: كيف يستطيع شخص، أو شخص ينشئ كائنًا من هذا النوع، التعامل مع الكائن؟ ما بعض الطرق للتفاعل مع هذا الكائن؟ في صنف الإحداثي كان من الأشياء الأكثر إثارة للاهتمام إيجاد المسافة بين إحداثي وإحداثي آخر. لكن من الأشياء الأبسط الحصول على قيمة الإحداثي x، والإحداثي y، وأشياء من هذا القبيل.
صفحة المصدر 2
بالنسبة إلى صنف الحيوان الذي ننفّذه اليوم سيكون الأمر أكثر تجريدًا قليلًا. لكن أحد أبسط الأشياء أن نقول: أخبرني كم مضى على حياتك. هذا في الأساس مجرد الحصول على قيمة السمة، الـH. إذًا هنا نعرّف كائن البيانات، صحيح؟ الكلمة المفتاحية class تخبر Python بأننا ننشئ نوع بيانات جديدًا. وهذا اسم نوع البيانات. إذًا نوع هذا الشيء الذي ننشئه هو Animal. وهنا بين القوسين، أب الحيوان هو كائن Python العام، object.
ملاحظة المترجم: يكتب النص الإنجليزي الرسمي في هذا الموضع
the H؛ السياق يتحدث عن سمة العمرage، لا عن سمة برمجية اسمهاH.
وفي وقت لاحق من محاضرة اليوم سنرى ما يحدث عندما نضع شيئًا آخر بين هذين القوسين. بحيث يكون أب الصنف الذي ننشئه شيئًا غير كائن Python العام فقط. ثم أول أسلوب نكتبه دائمًا في تعريف الكائن الجديد هو أسلوب __init__. وهذا يخبر Python: كيف تنشئ كائنًا من هذا النوع؟ إنها معلومات أساسية جدًّا تحتاج Python إلى معرفتها. إذًا أسلوب التهيئة أسلوب خاص من أساليب الشرطة السفلية المزدوجة (Dunder Methods): شرطتان سفليتان، init، شرطتان سفليتان.
والآن أنتم تعرفون هذا: المعامل الأول (Parameter) لكل أسلوب نعرّفه داخل صنف هو هذا الشيء المسمى self. وتذكروا أن self متغير، صحيح؟ إنه اسم متغير يتيح لنا الحديث عن كائن دون أن نكون قد أنشأنا واحدًا بعد؛ لأن كل ما نفعله هنا هو تعريف الصنف، صحيح؟ ليس لدينا كائنات فعلية منشأة. ولذلك فإن هذا الأسلوب، أسلوب __init__، وكل الأساليب الأخرى تُشغّل على كائن من نوع الحيوان. لكن ليس لدينا ذلك الكائن بعد.
إذًا المعامل الأول سيكون ذلك الكائن بهذه الطريقة المجردة نوعًا ما. ثم يمكنكم وضع معاملات أخرى في القائمة. ونقول إذًا إنه عندما ننشئ كائن حيوان جديدًا سنهيّئه بعمره، أي عدد ما. داخل __init__، ماذا نفعل عادة؟ عادة نهيّئ جميع سمات البيانات (Data Attributes)، وتسمى أيضًا متغيرات النسخة (Instance Variables). إذًا كم سمة بيانات لدي هنا لصنف الحيوان؟ اثنتان، بالضبط، نعم، اثنتان.
الأولى، self.age، سمة بيانات. ونعرف أنها سمة بيانات لأن self تظهر مرة أخرى، صحيح؟ لو كان مجرد اسم متغير، مثل age أو years أو time أو شيء من هذا القبيل، لكان مجرد متغير عادي. وبمجرد انتهاء أسلوب __init__ سيختفي ذلك المتغير. لكن كوننا هيّأنا هذا المتغير باستخدام self. يخبر Python: هذا متغير أريد أن يستمر ما دام هذا الكائن موجودًا في الذاكرة. لذلك فهو متغير نسخة.
إذًا self.age = age ينشئ سمة البيانات هذه، age، ويسند إليها قيمة المعامل الممرر، age. والآن self.name أيضًا سمة بيانات. لكنها لا تُمرر في قائمة المعاملات. وهذا مقبول. ليس ضروريًّا أن يُمرر كل شيء في قائمة المعاملات. ما نفعله فعليًّا هنا هو القول: عندما ننشئ كائن حيوان جديدًا يجب أن نخبره بعمره، أي المدة التي عاشها. لكن سمة بيانات الاسم ستكون دائمًا None. إذًا هناك غياب لقيمة الاسم لكل حيوان ننشئه منذ البداية.
حسنًا، هل الجميع معي حتى الآن هنا؟
أحد الحضور (AUDIENCE):
ما الغرض من تعريف self.name = None؟
آنا بيل (ANA BELL):
ما الغرض من تعريف هذه القيمة لتكون None؟ لاحقًا سأضيف بعض الأساليب التي تتيح لكم إعطاء الحيوان اسمًا إذا أردتم. لكن مرة أخرى، هذا خيار تصميمي اتخذته أنا. لذلك نعم، قد لا تتخذون الخيارات التصميمية نفسها. إذًا هذا تعريف صنفي، هذه الأسطر الأربعة من الكود فقط. ثم هنا في الأسفل رأينا في المحاضرتين الماضيتين كيفية إنشاء كائنات جديدة فعلية، صحيح؟ هنا يحدث العمل فعلًا، صحيح؟
صفحة المصدر 3
هنا أنشئ كائن حيوان جديدًا. يرتبط متغير، myanimal، بكائن الحيوان ذاك، صحيح؟ إذًا هذا اسم متغيري. يمكنكم تسميته أي شيء تريدونه. ثم تخبرون Python بأن تنشئ كائن حيوان جديدًا بمجرد استدعاء اسم الصنف، ثم تمرير جميع المعاملات التي نتوقعها هنا باستثناء self؛ لأن self تصبح هذا الشيء. لو رسمت صندوقًا حول Animal(3)، لكان ذلك هو self. إنه هذا الكائن الذي أنشأته للتو.
حسنًا، هذا أسلوب __init__. في المحاضرة الماضية رأينا بعض أساليب الشرطة السفلية المزدوجة. وأعتقد أنني قلت إن ثاني أسلوب تريدون غالبًا تنفيذه لصنف جديد هو أسلوب __str__ هذا. يخبر أسلوب __str__ Python كيف ينبغي لها طباعة كائن من نوع الحيوان، صحيح؟ لأننا في البداية، منذ اللحظة الأولى، لو لم ننفّذ أسلوب __str__ هذا، فستستخدم Python افتراضيًّا أسلوب __str__ لكائن Python العام، الذي لا يخبرنا إلا بموقع الذاكرة الذي أُنشئ فيه هذا الكائن، وهذا ليس مفيدًا جدًّا.
عندما نطبع كائن حيوان، ومرة أخرى، خياري التصميمي هو أن أقول إنني سأطبع animal، نقطتين رأسيتين، اسم ذلك الحيوان، نقطتين رأسيتين، وعمر ذلك الحيوان، مرة أخرى هذا خياري التصميمي، صحيح؟ وتذكروا أن أسلوب __str__ يُرجع السلسلة النصية التي تريدون طباعتها. ولا يطبعها مباشرة. هل الجميع بخير مع ذلك حتى الآن؟ ينبغي أن يكون مراجعة.
حسنًا، ثم الأشياء الأخرى التي نريد تضمينها في صنفنا، وهذا شيء أدرجته مهما كانت اللغة التي تعملون بها، هي هذه الأشياء المسماة أساليب الجلب والضبط (Getters and Setters). أسلوبا الجلب هما هذان هنا. أساليب الجلب في الأساس دوال بسيطة جدًّا تُرجع قيم سمات البيانات التي يمتلكها هذا الكائن. لا يمتلك هذا الكائن إلا اثنتين، عمرًا واسمًا، لأنهما عُرّفتا باستخدام self.age وself.name.
إذًا هذا أسلوب جلب يخبرني بقيمة self.age فقط. وكل ما يفعله هو return self.age. وللاسم، كل ما يفعله هو return self.name، بسيط جدًّا جدًّا. أساليب الضبط لها الفكرة نفسها، لكننا الآن نتيح لمن يستخدم صنفنا ضبط قيم سمات البيانات هذه من خلال هذه الأساليب، صحيح؟ إذًا كل ما يحدث هنا هو أخذ معامل للشيء الذي تريدون تغيير العمر أو الاسم إليه، صحيح؟
وكل ما يفعله هو أن يقول: ستكون self.age مساوية للشيء الذي مرّرتموه، حسنًا؟ هذا هو العمر. إذًا نغيّر هذه القيمة إلى عدد مختلف. ثم أسلوب set_name يغيّر سمة بيانات الاسم إلى سلسلة نصية مختلفة. وأنا هنا أستخدم المعامل الافتراضي (Default Parameter) الذي تحدثنا عنه منذ وقت طويل عندما تحدثنا عن الدوال، صحيح؟ إذًا إن لم تمرروا قيمة نصية فعلية فسنستخدم السلسلة النصية الفارغة افتراضيًّا.
دعوني أريكم كيف يعمل ذلك. هذا صنف الحيوان الخاص بي تمامًا كما في الشرائح. لدي __init__ و__str__ وأسلوبا الجلب وأسلوبا الضبط. ثم لدي حيوانان يُنشآن هنا، صحيح؟ هنا طباعة لحيوان عمره أربع سنوات. وهنا طباعة لحيوان عمره ست سنوات. إذا شغّلت هذين، ينبغي أن يطبع animal، نقطتين رأسيتين، None، لأنني لم أضبط الاسم إلى أي شيء لهذين الاثنين، ثم عمر كل منهما، صحيح؟
إذًا هذا يستخدم أسلوب __str__ على a. وهذا يستخدم أسلوب __str__ على b. حسنًا، ثم يمكننا بالطبع الوصول باستخدام صيغة النقطة (Dot Notation) إلى جميع سمات بياناتنا. هنا أصل إلى العمر مباشرة. لكن بما أن أسلوب الجلب get_age يُرجع لي قيمة سمة البيانات تلك فقط، فسيطبع هذان الشيء نفسه بالفعل. لذلك سأضع هذين في تعليقات. فإذا وصلت إلى عمر a إما من خلال سمة البيانات مباشرة أو من خلال أسلوب الجلب، فسيطبع أربعة في الحالتين، واضح جدًّا.
صفحة المصدر 4
ثم يمكننا فعل أشياء مثل هذه. يمكنني استدعاء أسلوب set_name. وهنا أمرر له اسمًا فعليًّا. ثم يمكنني طباعة الاسم أو استخدام أسلوب الجلب لطباعة الاسم، صحيح؟ إذا شغّلت ذلك فسترون أن الاسم قد تغيّر الآن للكائن a. ثم إن شغّلت الطباعة على a فسيطبع animal، نقطتين رأسيتين، الاسم الجديد الذي ضبطته للتو، fluffy. والعمر لم يتغير.
إذا شغّلت set_name دون معامل، فسيعود إلى المعامل الافتراضي للاسم، وهو السلسلة النصية الفارغة. لذلك سيكون الاسم الجديد لحيواني a مجرد سلسلة نصية فارغة. وستكون النتيجة نقطتين رأسيتين لا شيء بينهما، بلا مسافة أو أي شيء، لا شيء فقط. حسنًا، هل الجميع بخير حتى الآن؟ نأمل أن تكون مراجعة صغيرة.
رأينا إذًا أنه يمكننا الحصول فعلًا على القيمة نفسها بالضبط للعمر، إما بالوصول إلى سمة بيانات العمر مباشرة باستخدام صيغة النقطة، أو باستخدام أسلوب الجلب الذي كتبناه. إحدى الطريقتين أفضل من الأخرى من حيث الأسلوب، والممارسات البرمجية الجيدة، وكتابة كود يسهل قراءته وتعديله ويكون متينًا، وأشياء من هذا القبيل. الأفضل استخدامًا هو ما يصل إلى الأسلوب. كلتاهما تستخدم صيغة النقطة.
لكن الأولى تصل فعليًّا إلى التفاصيل الداخلية لتعريف صنفي، صحيح؟ لكي أعرف قيمة سمة بياناتي بوصفي شخصًا يستخدم هذا الكود لصنف الحيوان فحسب، ينبغي أن أدخل فعلًا وأقرأ أسلوب __init__ لأعرف سمات البيانات هذه التي يجري تهيئتها. لا أعرف عنكم. لكنني فعلًا—لنأخذ مثال القائمة (List)، شيء استخدمناه كثيرًا. هل دخلتم يومًا إلى تعريف صنف القائمة لتروا سمات البيانات التي تُهيّأ؟ أنا لم أفعل، صحيح؟
كل ما فعلناه هو التعامل مع أساليب تتيح لنا إجراء تغييرات على القوائم، وإجراء عمليات على القوائم، وأشياء من هذا القبيل. إذًا طريقة العمل الداخلية لصنف القائمة مخفية عنا. وهذا ما نريده تمامًا، صحيح؟ لا يهمني كيف تُنفّذ القائمة فعلًا. وينبغي أن يحدث الشيء نفسه هنا، صحيح؟ ينبغي ألا أهتم بكيفية تنفيذ صنف الحيوان. ينبغي ألا أهتم بمتغيرات النسخة التي يستخدمونها.
دعوني إذًا أريكم السبب. إذا قرر شخص يكتب صنف الحيوان في المستقبل أن age اسم متغير غريب للاستخدام، وقرر تغيير المتغير المرتبط بالمدة التي عاشها هذا الحيوان إلى، إلى years، صحيح؟ هنا لدي self.years = age. هذا هو التغيير الوحيد الذي أجريته على صنف الحيوان.
إذًا اتخذت قرارًا تصميميًّا بتغيير سمة البيانات هذه لتكون years. ثم بالطبع، بما أنني أكتب هذا الصنف، يجب أن أتأكد من أن جميع أساليب الجلب والضبط وكل شيء ما زال يعمل مع سمة البيانات الجديدة. لذلك سيُرجع get_age لدي self.years، صحيح؟ أُرجع هذا المتغير، سمة بيانات غيّرتها. حسنًا، هذا هو الكود الكامل. يمكنكم رؤية سمة البيانات المتغيرة هنا. أستخدم self.years = age.
ثم سيُرجع أسلوب الجلب self.years. وسيضبط أسلوب الضبط self.years. هذا التنفيذ ينبغي أن يكون مخفيًّا عني، أنا الشخص الذي يحاول مجرد إنشاء مجموعة من الحيوانات في كوده. لذلك سيعمل هذا الكود في الأسفل إن استخدمت أسلوبي، صحيح؟ لأن الأسلوب ينبغي أن يظل يعمل مهما كان اسم سمة البيانات، age أو years أو time أو أيًّا كان. لكن لو كان لدي كود يصل إلى سمة البيانات تلك مباشرة، فلن يعمل بعد الآن. سيطلق خطأ، لأن، يا للمفاجأة، تلك السمة لم تعد موجودة.
لذلك من الأفضل كثيرًا من حيث الأسلوب والمتانة استخدام أساليب الجلب والضبط فقط، الأساليب فقط، لإجراء التغييرات والتعامل مع الكائنات. ينبغي ألا تحتاجوا أبدًا فعلًا إلى استخدام سمات البيانات، صحيح؟ هل هناك أسئلة عن هذا؟ حسنًا، جيد، لأن هذا شيء يجب أن تضعوه في الحسبان في الاختبار القصير يوم الاثنين القادم: عدم استخدام سمات البيانات.
صفحة المصدر 5
حسنًا، بعد قول ذلك، تتيح لكم Python القيام بمجموعة من الأمور المشكوك فيها. أولًا، تتيح لكم، كما رأينا للتو، الوصول إلى سمة البيانات لنسخة محددة تنشئونها. تنشئون كائنًا. إنه حيوان محدد جدًّا بعمر محدد. يمكنكم استخدام صيغة النقطة للوصول إلى قيمة جميع سمات البيانات هذه. حسنًا، سنوقع أنفسنا في مشكلة في المستقبل، لأن هذا قد لا يعمل. لكنه ليس سيئًا جدًّا.
إلا أن Python تتيح لكم أيضًا تغيير قيمة سمة بيانات خارج تعريف الصنف. إذًا هذا كود نكتبه لا داخل الصنف. إنه كود نكتبه بوصفنا أشخاصًا يستخدمون الصنف. ماذا يعني هذا؟ الآن سأضبط سمة بيانات العمر إلى أي شيء أريده خارج تعريف الصنف، صحيح؟ يمكنني حتى ضبطها إلى قاموس (Dictionary) إن أردت.
في هذه الحالة بالتحديد أضبطها إلى السلسلة النصية infinite. لكن إن فعلت ذلك فأنا أخاطر بأن كودًا يعتمد على صنف الحيوان هذا لن يعمل لاحقًا، لأنهم ربما يفترضون أن العمر دائمًا عدد. لذلك قد لا يعود أسلوب مختلف أشغّله يعمل إذا صادف أن ضبطته إلى السلسلة النصية. ثم هناك شيء آخر تتيح لكم Python فعله فعلًا، وهو إضافة سمات بيانات إلى النسخ.
المشكلة الآن هي أنني، لنقل، أنشئ مجموعة كبيرة من نسخ الحيوانات، صحيح؟ هذا الحيوان عمره أربع سنوات. وهذا عمره ست سنوات. وهذا عمره خمس سنوات. ثم أقرر إضافة سمة بيانات جديدة إلى أحد هذه الحيوانات. أي إن واحدة فقط من هذه النسخ أصبحت الآن ذات ثلاث سمات بيانات مرتبطة بها، اسم وعمر والآن حجم. أما جميع نسخ الحيوانات الأخرى التي أنشأتها فليس مرتبطًا بها إلا اسم وعمر.
هذه الواحدة وحدها صادف أن لديها سمة البيانات الإضافية. والآن السبب الكامل وراء إنشاء أنواع البيانات الخاصة بنا كان الاتساق. فكرة جمع مجموعة محددة من البيانات ومجموعة محددة من السلوكيات معًا تضيع؛ لأن لدي الآن نسخة واحدة أصبحت مرتبطة بسمة البيانات الإضافية هذه. ولا أحد غيرها يمتلكها، صحيح؟ إذًا ضاع كل ذلك الاتساق. لذلك لا تفعلوا أيًّا من هذه الأشياء أبدًا خارج تعريف الصنف.
لا بأس إطلاقًا بالوصول إلى سمات البيانات أثناء تعريف صنف، لكن لا يجوز القيام بأي من هذه الأشياء خارج تعريف الصنف، رغم أن Python تسمح لكم بذلك. حسنًا، أحد الأشياء التي أردت أن أريكم إياها في هذه المحاضرة هو شيء لم نره فعلًا حتى الآن، وهو مجرد التعامل مع الكائنات التي ننشئها. نعم، عندما أنشأنا الكسور (Fractions) والإحداثيات، أنشأنا فقط مجموعة من الكائنات ثم طبعنا البسوط أو طبعنا الكائن أو ضربناها معًا.
لكننا لم نكتب فعلًا دوال لطيفة تعمل على كائنات من نوعنا. لذلك أحد الأشياء التي أردت أن أريكم إياها هو كيفية فعل ذلك. هذه دالة تنشئ قاموسًا من قائمة. إذًا المدخل هنا سيكون قائمة من أي شيء أريده. وما أود أن تفعله الدالة هو أن تنتقي من القائمة الأعداد غير السالبة فقط، والأعداد الصحيحة (Integers) فقط.
في هذه الحالة بالتحديد أود أن تنتقي دالتي 2 و5 و0، وتتجاهل كل ما عداها. وأود إنشاء قاموس من هذه الأعداد. وما ينبغي أن يفعله القاموس هو ربط كل واحد من هذه الأعداد، 2 و5 و0، هذه ستكون مفاتيحي (Keys)، بكائنات حيوانات بهذه الأعمار. إذًا هذا حيوان عمره اثنان. وهذا حيوان عمره خمسة. وهذا ينبغي أن يكون حيوانًا عمره صفر، صحيح؟
صفحة المصدر 6
إذًا أنواع مفاتيحي هي int. والقيم المرتبطة بالمفاتيح ينبغي أن يكون نوعها Animal، هذا الكائن الذي أنشأته للتو، حسنًا؟ الكود إذًا واضح جدًّا. لدينا حلقة صغيرة تمر على كل عنصر في قائمتي واحدًا في كل مرة. هذا هو for n in L. ثم سأفعل شيئًا فقط للعناصر التي هي أعداد صحيحة وأكبر من أو تساوي الصفر، أي غير سالبة. وهذا سيستخرج 2 و5 و0 فقط بينما نمر في الحلقة على عناصر L.
والسطر الرئيسي هنا هو هذا باللون الأحمر. سأقول: هذا السطر يضيف إدخالًا إلى قاموسي فقط، صحيح؟ هذه صيغة وضع شيء في قاموس، صحيح؟ لا يوجد append أو جمع في القاموس أو شيء من هذا القبيل. إنها فهرسة (Indexing) مباشرة فقط. المفتاح الذي تريدونه هو n، أي إما 2 أو 5 أو 0. والقيمة التي أريد ربطها بذلك المفتاح هي حيوان عمره أيًّا كانت هذه القيمة، 2 أو 5 أو 0، أي ما كتبته هنا تمامًا.
هل الجميع بخير حتى الآن؟ حسنًا، تستمر الحلقة حتى نهاية القائمة. ثم نكون قد أنشأنا قاموسنا. وانتهينا. أثناء كتابة هذا الكود كيف ننقّحه (Debug)، أو كيف نفحص هل عمل؟ غريزتنا أن نقول: حسنًا، دعوني أفحص هل عملت هذه الدالة. هنا هذا السطر، animals = animal_dict(L)، سيشغّل هذه الدالة. ويشغّلها على هذه L. وفي النهاية تُرجع قاموسًا، شيئًا يبدو هكذا.
إذًا غريزتنا أن نطبع ذلك القاموس المُرجع فقط. لكن لو طبعناه، ويمكنكم فعلًا تشغيل الكود في Python، فإن طبعتموه فستحصلون على شيء كهذا. وذلك لأن Python لا تتعمق في عناصر القواميس، أو حتى عناصر القوائم، لتشغّل الطباعة بصورة عودية (Recursively). إنها تشغّل الطباعة عند المستوى الأعلى فقط. والمشكلة أنها تعرف كيف تطبع الأعداد الصحيحة بلا مشكلة. لكنها لا تعرف كيف تطبع قاموسًا قيمه كائنات حيوانات.
وهكذا نصادف المشكلة نفسها؛ القيمة المرتبطة بالمفتاح 2 الآن هي كائن الحيوان هذا في موقع الذاكرة ذاك. لكن كيف أعرف أنني لم أخطئ في—أنشأت حيوانًا عمره خمسة حيث كان ينبغي أن يكون اثنين، صحيح؟ إذًا الحل، وربما تصادفونه في الاختبار القصير القادم إذا كنتم تنقّحون الكود، الحل أن نمر على القاموس بطريقة تشغّل عبارة الطباعة مباشرة على كائن من نوع الحيوان.
Python تعرف كيف تفعل ذلك، صحيح؟ أخبرناها بأسلوب __str__، صحيح؟ لدينا أسلوب __str__ هنا. لذلك تعرف كيف تشغّل الطباعة مباشرة على كائن حيوان. لكنها لا تعرف كيف تشغّل الطباعة عندما تكون قيمة في قاموس كائن حيوان. دعونا إذًا نستبدل طباعة القاموس هذه بحلقة صغيرة. تمر على عناصر القاموس، items، صحيح؟ ستكون n مفتاحي. وستكون a القيمة المرتبطة بذلك المفتاح.
ولدي عبارة الطباعة هنا فقط. أستخدم هنا سلسلة منسقة (F-String) تطبع المفتاح وأيًّا كانت قيمة ذلك المفتاح، مع val، وأيًّا كانت تلك القيمة، صحيح؟ إذًا الآن تُشغّل عبارة الطباعة مباشرة على كائن من نوع الحيوان. والآن ستكون نتيجة هذه الحلقة هذا: المفتاح 2 مع القيمة. ثم تستخدم عبارة الطباعة على كائن الحيوان الخاص بي. هل هذا مفهوم؟ هل الجميع بخير حتى الآن؟
أحد الحضور (AUDIENCE):
[غير مسموع]
آنا بيل (ANA BELL):
نعم، بالضبط. إنها تحوّل الأشياء في القاموس إلى سلاسل نصية لأن عبارة الطباعة عندي تُشغّل مباشرة على ذلك الكائن من نوع الحيوان. وهي تعرف كيف تفعل ذلك. نفّذت __str__. هل الجميع بخير؟ حسنًا، دعونا نجعلكم تجرّبون هذا. دعونا نجعلكم تكتبون قليلًا من الكود. ستكون هذه الدالة مشابهة جدًّا. لن ننشئ قواميس. ستنشئون قائمة. لكنكم ستصادفون المشكلة نفسها.
صفحة المصدر 7
المدخل هنا سيكون قائمتين بالطول نفسه. إحدى القائمتين فيها أعداد. والأخرى فيها سلاسل نصية. وما أود أن تفعلوه هو أن تنشئوا لي قائمة جديدة. وستحتوي القائمة الجديدة على كائنات حيوانات، حيث تطابقون فهرسًا بفهرس. إذًا كائن الحيوان الناتج عند الفهرس 0 سينشئ لي أساسًا حيوانًا جديدًا عمره اثنان واسمه blobfish، صحيح؟ وكائن الحيوان في القائمة الناتجة عند الفهرس 1 سيكون عمره خمسة واسمه crazyant. ثم كائن الحيوان عند الفهرس 2 سيكون عمره واحد واسمه parafox.
إذًا نفعل الشيء نفسه فهرسًا بفهرس، حيث تنشئون كائن حيوان جديدًا بالعمر، هذه القيمة، واحدة في كل مرة. وتضبطون الاسم إلى هذه القيمة واحدة في كل مرة، ثم تُرجعون تلك القائمة. ينبغي أن يكون ذلك السطر 79. حسنًا، من لديه بداية لي؟ حسنًا، هل نسميها L3؟ حسنًا.
أحد الحضور (AUDIENCE):
لكن بعد ذلك for n in L1، [غير مسموع] نجعله يمر على L2 عند n.
آنا بيل (ANA BELL):
نعم، لكن إذا كنتم تستخدمون L2[n]، فينبغي أن يكون هذا هو الفهرس، صحيح؟ كيف أجعل هذا الفهرس بدلًا من العنصر مباشرة؟ نعم، نعم.
أحد الحضور (AUDIENCE):
ربما n، على عمود المدى (Range)؟
آنا بيل (ANA BELL):
نعم، بالضبط، صحيح؟ بدلًا من النظر إلى العنصر مباشرة، دعونا ننظر إلى المدى فقط. إذًا for i in range، ثم نحتاج إلى len. اختاروا واحدة من القائمتين لأنهما بالطول نفسه. الآن i هي 0، 1، 2، 3، 4، 5، كل قيم الفهارس.
أحد الحضور (AUDIENCE):
ويمكن جعل L3 [غير مسموع]، L1 عند الفهرس i تساوي L2 عند الفهرس i.
آنا بيل (ANA BELL):
إذًا L1 عند الفهرس i، أحتاج إلى إنشاء حيوان بذلك العمر، صحيح؟ دعونا نفعل هذا: age = L1[i]، فقط لحفظها في متغير. وname = L2[i]. هل نتفق؟ الآن بعد أن خزنت العمر والاسم في هذين المتغيرين، كيف أنشئ كائن حيوان بذلك العمر؟ نعم.
أحد الحضور (AUDIENCE):
يمكن أن نقول L3 عند i تساوي—حسنًا، أعرف أنك ستسمينها animal.h.
آنا بيل (ANA BELL):
أسلوب __init__ ينشئ لي حيوانًا بذلك العمر، صحيح؟ فعندما ننشئ فقط كائن حيوان جديدًا، نمرر ذلك العمر فحسب، صحيح؟ مثلًا، الباني (Constructor) يتطلب عمر الحيوان، صحيح؟ إذًا عندما نبني كائن حيوان جديدًا نستدعي فقط اسم حيواننا. أين هو؟ هنا، صحيح؟ أو—آسفة، نوع الحيوان، صنف الحيوان. ثم نمرر العمر الذي نريد إنشاء هذا الحيوان به، صحيح؟
وذلك، وفق أسلوب __init__، يجعل self.age أيًّا كانت القيمة الممررة، والاسم None. إذًا وصلنا إلى منتصف الطريق. أنشأنا كائن حيوان بالعمر الذي نريده. لكن سمة بيانات الاسم لهذا الكائن هي None. هل الجميع معي حتى الآن؟ كيف نجعل اسم كائن الحيوان هذا هو الذي حفظناه من قائمة L2؟
أحد الحضور (AUDIENCE):
يمكن استخدام دالة الضبط.
آنا بيل (ANA BELL):
نعم، بالضبط. يمكننا استخدام دالة الضبط، نعم، set_name هنا، صحيح؟ لا تصلوا إلى السمة مباشرة. لكن نعم، يمكننا استخدام دالة ضبط. إذًا هذا أنشأ لي ذلك الحيوان الجديد، صحيح؟ لكن عليَّ حفظ ذلك الحيوان فعلًا بطريقة ما، صحيح؟ لأنني أحتاج إلى الإشارة إليه لاحقًا. دعوني أفعل هذا: a تساوي حيوانًا بذلك العمر. ثم نشغّل دالة الضبط على هذا الكائن، a، صحيح؟ set_name—إنها مجرد دالة.
صفحة المصدر 8
وما الاسم الذي نريد ضبطه إليه، هذا الشيء هنا؟ إذًا name هنا هو هذا المتغير الذي استخرجناه من قائمة L2. هل الجميع بخير حتى الآن؟ الآن ما لدي هو أن الكائن a متغير مرتبط بكائن حيوان. ضُبط العمر عندما أنشأناه أول مرة. والاسم ضبطناه للتو باستخدام دالة الضبط. والآن ينبغي أن نضعه في قائمتي فقط. قائمتي فارغة أصلًا، صحيح؟
إذًا ليس لدي مجموعة من العناصر لأضيفه إليها. دعونا نلحقه بـL3 باستخدام append، هكذا، صحيح؟ أعني، نظريًّا كان يمكنني إنشاء قائمة فارغة بطول ثلاثة عناصر. ثم يمكنني تنفيذ L3.i. لكن هذا يعمل أيضًا. وفي النهاية دعونا نُرجع L3، صحيح؟ هل هناك أسئلة عن هذا؟ هل هذا جيد؟
حسنًا، إذا شغّلناها وطبعنا القائمة فقط مع كائنات الحيوانات هذه، فسنواجه المشكلة نفسها التي واجهناها مع القاموس، صحيح؟ ترون أن لدي مجموعة من مواقع الذاكرة هنا؟ لكي أختبر أنني فعلت ذلك بشكل صحيح، بدلًا من طباعة القائمة، دعونا نمر على قائمتنا باستخدام حلقة for الصغيرة هذه ونشغّل الطباعة مباشرة على كائني، صحيح؟ الآن إن شغّلت ذلك فينبغي أن يشغّل عبارة الطباعة مباشرة على كل حيوان من هذه الحيوانات، صحيح؟ هذا صحيح، على ما أعتقد. هل هذا مفهوم؟ نعم.
أحد الحضور (AUDIENCE):
ماذا غيّرتِ لجعله أكبر؟
آنا بيل (ANA BELL):
آه، بدلًا من طباعة القائمة، هذا الشيء، مررت في حلقة على قائمتي وطبعت العناصر.
أحد الحضور (AUDIENCE):
آه. إذًا هذا ليس داخل الدالة.
آنا بيل (ANA BELL):
ليس داخل الدالة، لا. إنه فقط، نعم، خارجها. لكن هذا شيء شائع جدًّا ستصادفونه. ستنشئون قائمة أو قاموسًا أو بنية أو صفًّا (Tuple) أو شيئًا من هذا القبيل، فيه كائنات من نوعكم. وعندما تشغّلون عبارة الطباعة مباشرة على تلك البنية، لا تتعمق إلى ما بعد المستوى الأعلى. ولذلك تطبع تلك الأشياء غير المفيدة. حسنًا، في هذا المثال رأينا أنه من الأفضل الوصول إلى السمات من خلال أساليب الجلب والضبط.
إذًا، بالإضافة إلى __init__ وأسلوب __str__، فإن كتابة أساليب الجلب والضبط لتوفير طريقة متسقة للوصول إلى سمات البيانات هذه وتعديلها أمر مهم جدًّا. ثم يمكنكم حتى فرض قيود، مثل أن الأنواع يجب أن تكون كذا. أو ربما لا يمكن أن يكون العمر عددًا سالبًا أو شيء من هذا القبيل. وهذا يتيح استخدامًا أكثر اتساقًا بكثير للكائن.
دعونا الآن ننتقل إلى التسلسلات الهرمية (Hierarchies)، حسنًا؟ وهنا سنتحدث عن الوراثة. هناك ما يقرب من 28 كائنًا في هذه الشريحة، صحيح؟ هناك الستة الذين صادفناهم في بداية هذه المحاضرة و22 في الأعلى. إذًا هناك 28 كائنًا منفصلًا في هذه الشريحة. ويمكن أن نقول إن كل هذه الكائنات من نوع الحيوان، صحيح؟ لأن سمات الحيوان، بحسب تعريفنا، هي المدة التي عاشها. وكل هذه كائنات عاشت مدة ما.
لكن إضافة إلى امتلاك سمة المدة التي عاشتها واسم غير معروف، يمكننا فعلًا إنشاء فئات منفصلة، صحيح؟ وكل صندوق من هذه الصناديق التي أنشأتها هو مجموعة فرعية مختلفة من الحيوان، صحيح؟ سنسميه صنفًا فرعيًّا (Subclass)، أو ابنًا (Child) لصنف الحيوان. وهذا لأنها ستجلب سمات بيانات مختلفة إضافة إلى سمات بيانات الحيوان.
صفحة المصدر 9
وستجلب سلوكيات مختلفة إضافة إلى سلوكيات كائن الحيوان العام جدًّا لدينا، صحيح؟ فالأشياء التي تستطيع القطة فعلها قد لا يستطيع الأرنب فعلها. والأشياء التي يستطيع الشخص فعلها لن تفعلها القطة ولا يستطيع الأرنب فعلها، صحيح؟ إذًا كلها حيوانات. لكن سيكون لها جميعًا سمات بيانات إضافية وسلوكيات إضافية مختلفة في هذه الفئات الثلاث، صحيح؟ لذلك قد أقول شيئًا مثل إن للقطة اسمًا وعمرًا ونمطًا أو لونًا.
الأرنب، مرة أخرى قلت إنها أرانب برية. لذلك ربما لا يحصل على اسم. لكنه سيكون له لون أو نمط. ثم العمر بالطبع من الحيوان. والناس بالطبع لديهم الـ—كائن الشخص لديه العمر الذي يأتي من الحيوان. لكن بالإضافة إلى ذلك قد تكون له قائمة أصدقاء أو شيء مرتبط به، شيء من هذا القبيل، صحيح، وقائمة الأصدقاء شيء ليست للقطة، وليست للأرنب. إذًا تفهمون ما أعنيه.
ويمكننا المضي أبعد من ذلك. يمكن أن نقول: إذا أخذت كائن الشخص لدي، أستطيع الآن تقسيمه إلى فئات فرعية أيضًا، والقول إن هذا صنف طالب (Student). ثم أقول إن الطالب شخص. لذلك جميع سمات البيانات وجميع السلوكيات التي يمتلكها الشخص يمتلكها الطالب أيضًا، وبالطبع كل أشياء الحيوان، لأن الشخص حيوان. فمثلًا، لنقل إن الحيوان كائن عام. لا يتكلم. لكن لنقل إن الشخص يحصل على سلوك الكلام، صحيح؟ وبالنسبة إلى الكلام قد أطبع Hello على الشاشة فقط أو شيئًا بسيطًا كهذا.
الطالب شخص. لذلك ربما يحصل أيضًا على أشياء مثل عمره، والاسم، وربما قائمة أصدقاء مرتبطة به. لكن الطالب قد يكون له أيضًا تخصص (Major) أو مادة مفضلة في المدرسة مرتبطة به، شيء ليست للشخص، صحيح؟ إذًا هذه سمة بيانات جديدة مرتبطة بالطالب وغير مرتبطة بالشخص. وقد تكون للطالب أيضًا سلوكيات مختلفة، مثل: أخبرني بمادتك المفضلة في المدرسة، وأشياء من هذا القبيل.
أو قد يعيد تعريف سلوكيات الشخص (Override). فإذا تكلم الشخص وقال مرحبًا، وطبع hello على الشاشة، يمكن أن نقول: إذا طلبت من الطالب أن يتكلم، فقد يقول بدلًا من ذلك إن لديه واجبًا منزليًّا أو شيء من هذا القبيل، صحيح؟ إذًا ما نحاول فعله هو أخذ تلك العلاقات وتنفيذها في الكود. هنا لدي صنف الحيوان وهو صنفي الأساسي (Base Class). سيكون أيضًا ما يسمى الصنف الأب (Parent Class) أو الصنف الأعلى (Superclass). ثم كل شيء يمتلكه الحيوان، جميع سمات البيانات وجميع سلوكيات ذلك الحيوان، سيرثه الشخص والقطة والأرنب.
إذًا أي شيء—الشخص حيوان. والقطة حيوان. والأرنب حيوان. لذلك كل ما يمتلكه، ستمتلكه أيضًا هذه الأنواع الفرعية الثلاثة. لكن جميع هذه الأنواع الفرعية ستختلف فيما بينها، صحيح؟ الشخص ستكون له القدرة على الكلام، وربما طباعة hello على الشاشة. والقطة يمكن أن تكون لها أيضًا القدرة على الكلام. لكنها ربما تطبع meow على الشاشة. والأرنب لن تكون له القدرة على الكلام إطلاقًا.
قد يكون للشخص قائمة أصدقاء، بينما ليست للقطة، ولا للأرنب، أشياء من هذا القبيل. إذًا يمكننا إما إضافة مزيد من المعلومات، مثلًا كانت قائمة الأصدقاء مثالًا على ذلك. ويمكننا إضافة مزيد من السلوك، مثلًا القدرة على الكلام مثال على ذلك. ومثال إعادة تعريف السلوك، كما ذكرت، هو أن يكون لدينا صنف الطالب الفرعي من الشخص. إذا قال أسلوب الكلام للشخص أن نطبع Hello على الشاشة، يمكننا إعادة تعريف ذلك السلوك من خلال أسلوب الكلام داخل الطالب، حيث لا نطبع hello على الشاشة فقط. يمكن أن نطبع أن لدي واجبًا منزليًّا.
صفحة المصدر 10
دعونا نحاول البدء بتنفيذ هذه العلاقة. هذا صنف الحيوان الخاص بنا فقط. لا شيء جديدًا هنا. أقوم فقط بتذكير بسيط بما يبدو عليه هذا الصنف. إذًا لدينا __init__، حيث نهيّئ عمرًا واسمًا قيمته None. ولدينا أسلوبا جلب، وأسلوبا ضبط، وأسلوب __str__ الذي يطبع animal، نقطتين رأسيتين، الاسم، نقطتين رأسيتين، العمر. إذًا نعم، حسنًا، صنف الحيوان هذا يرث من object، أي كائن Python العام.
والآن دعونا نعمل على الصنف الفرعي، Cat. عندما أنشئ صنفي الفرعي Cat، فالطريقة التي أخبر بها Python بأن هذه القطة حيوان هي أن أضع هنا بين القوسين اسم النوع الذي أريد لهذا الصنف أن يرث منه. القطة حيوان. وأحد الأشياء التي ظللت أعود إليها هو أنه في كل مرة تنشئون فيها نوع بيانات جديدًا، يجب أن يكون لديكم أسلوب __init__. وهذا لا يحتوي على أسلوب __init__ صريح، صحيح؟ لدي هنا أسلوبان آخران فقط.
لذلك قد تظنون أنه مفقود. لكنه في الحقيقة ليس كذلك، لأنه بمجرد وضع نوع بيانات آخر هنا بين القوسين، بحيث تكون القطة حيوانًا، فكروا في الأمر كما لو أن Python تدخل إلى صنف الحيوان وتنسخ وتلصق كل ما هو جزء من صنف الحيوان، أو تنسخ كل ما هو جزء من صنف الحيوان ذاك وتلصقه داخل القطة. لذلك بما أنه ليس لدي أسلوب __init__ معرّف صراحة داخل القطة، ستقول Python: سنستخدم أسلوب __init__ لأبيك، الحيوان، فقط.
إذًا الطريقة التي ننشئ بها قطة ستكون الطريقة نفسها تمامًا التي ننشئ بها حيوانًا، لكن الاسم سيكون Cat بصفته نوع الكائن بدلًا من Animal. لكننا نمرر له شيئًا واحدًا فقط، وهو عمر هذه القطة. إذًا بما أننا ننسخ ونلصق كل شيء—نعم، سؤال.
أحد الحضور (AUDIENCE):
الصنف الأب للحيوان [غير مسموع]، هل هذا أيضًا [غير مسموع]؟
آنا بيل (ANA BELL):
نعم، بالضبط. الصنف الأب للحيوان هو object. لذلك ستكون القطة أيضًا كائن Python. لكن هذه أشياء عامة جدًّا، مثل ربط اسم متغير بهذا الكائن، وأشياء من هذا القبيل. إذًا لا يقتصر النسخ على __init__، بل يشمل كل سمة بيانات، العمر والاسم، وكل طريقة تُنشأ بها سمة البيانات تلك. لذلك ستكون self.age سمة بيانات للقطة. وستُضبط إلى أي قيمة تُمرر كمعامل. وستُهيّأ self.name إلى None تمامًا كما للحيوان.
لدي أسلوبا الجلب وأسلوبا الضبط اللذان يعملان أيضًا مع القطط. ثم سيُورث أسلوب __str__ للحيوان هنا أيضًا. لكننا نلاحظ الآن شيئًا واحدًا: لدينا أسلوب __str__ معرّف في صنف الحيوان. لكنني في صنف القطة أعرّف أيضًا أسلوب __str__، صحيح؟ يُسمى ذلك إعادة تعريف ما في صنف أبيك. وعندما ننشئ كائنًا من نوع القطة، إذا كان لهذا الكائن أسلوب له اسم أسلوب أبيه نفسه، نستخدم هذا الأسلوب. ليس هناك سبب للصعود إلى الأب طلبًا لأسلوبه. نستخدم الأسلوب الخاص بهذا الكائن.
والقطة، بالإضافة إلى امتلاكها كل ما يمتلكه الحيوان، تنفّذ سلوكًا جديدًا، وهو القدرة على الكلام. وكل ما يفعله هو طباعة meow على الشاشة. حسنًا، دعونا ننظر إلى بعض الكود. هذه قطتي. أنشأت إذًا كائن قطة جديدًا بالطريقة نفسها التي أنشئ بها حيوانًا. لكنني أستدعي اسم هذا الصنف، Cat. والطريقة التي أنشئ بها حيوانًا هي فقط تمرير عمر هذا الشيء، صحيح؟
هنا أنشئ قطة عمرها خمسة. اسم هذه القطة None، صحيح؟ لأن هذا ما يفعله أسلوب __init__ للحيوان. لكن يمكنني تشغيل أساليب الحيوان على كائن قطتي، لأن القطة حيوان. لذلك كل الأساليب التي تعمل مع الحيوانات ستعمل مع كائن من نوع القطة. هنا يمكنني تشغيل أسلوب set_name على كائن قطتي فحسب. رغم أن الأسلوب غير معرّف صراحة هنا، فإنه معرّف في أبي.
صفحة المصدر 11
إذا ضبطت الاسم إلى fluffy ثم طبعت كائن القطة، فسيطبع أنها قطة، نقطتين رأسيتين، الاسم، نقطتين رأسيتين، العمر. وسيطبع speak فقط meow على الشاشة. يمكننا أيضًا تنفيذ أساليب الجلب. إذًا جميع هذه الأساليب التي نُفّذت مع الحيوان تعمل أيضًا مع القطط. والآن هنا، أُنشئ الكائن a في الأعلى عندما تحدثنا عن الحيوانات. إنه كائن حيوان لأنه أُنشئ باستخدام استدعاء الحيوان هنا.
هل يمتلك صنف الحيوان أسلوبًا للكلام؟ لا. لذلك إذا شغّلت هذا فعلًا، فسيعطيني خطأ، صحيح؟ يقول فقط إنه لا توجد سمة speak، وهذا منطقي. لم أعرّف ذلك قط. عرّفته في الابن، لا في الأب. هل هناك أسئلة عن القطط؟ حسنًا، أريد أن أتناول بإيجاز إعادة تعريف الأساليب لأنها قد تصبح مربكة قليلًا. تلاحظون أن أسلوب __str__ نُفّذ في كل من هذين الكائنين.
أسلوب __str__ موجود في القطة، وهو يعيد تعريف أسلوب الحيوان ليطبع cat، نقطتين رأسيتين، الاسم، نقطتين رأسيتين، العمر. وأسلوب الحيوان، أسلوب __str__، يطبع animal، نقطتين رأسيتين، الاسم، نقطتين رأسيتين، العمر. إذًا القاعدة هي أنه عندما تشغّلون أسلوبًا تعرفون أنه موجود في مجموعة من هذه الكائنات المرتبطة بالوراثة، تنظرون إلى—أي واحد هو؟ إنه __str__، صحيح؟ إذًا سيكون الطباعة، أو أي أسلوب. لا يهم ما هو. تنظرون إلى الكائن الذي تستدعون الأسلوب عليه، صحيح؟
فإذا كانت صيغة النقطة، تنظرون إلى الشيء قبل النقطة. وإن كان أحد هذه الأساليب الخاصة، فما الكائن الذي تشغّلون هذا الأسلوب عليه؟ هنا لدي الطباعة على الكائن c. تسأل Python: ما نوعك؟ آه، أنت قطة؟ هل لديك أسلوب __str__ معرّف؟ نعم، لديك. إذًا تستخدم ما تجده فورًا. لكن إذا لم يكن لدى الكائن الحالي ذلك الأسلوب لسبب ما، فمثال ذلك set_name، صحيح؟
set_name ليس أسلوبًا معرّفًا في القطة. وc كائن من نوع القطة. ليس لديه ذلك الأسلوب. تقول Python: ليس لديك ذلك الأسلوب. دعيني أنظر إلى أبيك. هل يمتلك أبوك ذلك الأسلوب؟ ثم تبحث هنا وتجده. جيد، إذا وجدته تستخدمه. وإن لم تجده، تنظر إلى أبي أبيك. إذا كان لدى أبي أبيك تستخدمه. وإذا لم يكن لديه، تنظر إلى أبي أبي أبيك.
إلى أن تصل إلى كائن Python العام، هذا هنا. إن كان لديه تستخدمه. وإذا لم يكن لديه، تطلق خطأ. ومثال شيء يمتلكه كائن Python العام هو أسلوب __str__، صحيح؟ إنه يطبع موقع الذاكرة فقط. ولهذا، عندما لا ننفّذ أسلوب __str__ في صنفنا، تستخدم Python افتراضيًّا كائن Python العام. هل هناك أسئلة؟
حسنًا، دعونا ننظر إلى الشخص. دعونا ننشئ كائن شخص (Person). كائن الشخص هذا سيرث مرة أخرى من الحيوان؛ لأن الأشياء الوحيدة التي عرّفنا الحيوان بها هي أنه عاش مدة معينة. وليس له اسم. الاسم None. لذلك لا نمرر ذلك أصلًا. لنقل إذًا إن الصنف الأب للشخص هو الحيوان. لكن هذا خياري التصميمي، وأيضًا لإبراز مجموعة من الأمور. لنقل إنني في صنف الشخص هذا، عندما أنشئ كائن شخص جديدًا، أود تمرير عمر واسم.
إذًا لا أريد إنشاء شخص بعمر فقط. أريد فعلًا إنشاءه باستخدام اسم في قائمة المعاملات. مثلًا هنا في الكود عندما أنشئ شخصًا أود تمرير اسمه، فاصلة، والعمر، معاملين لإنشاء شخص. لا يمكنني استخدام أسلوب __init__ للحيوان، صحيح؟ كان يمكنني ذلك للقطة، لأن القطة اكتفت بأن تُنشأ باستخدام العمر فقط. لكن لا أستطيع فعل ذلك للشخص، لأنني أود إنشاء شخص بتمرير معاملين عند إنشائه.
صفحة المصدر 12
إذًا ما يجب أن أفعله هو فعليًّا إعادة تعريف أسلوب __init__ للحيوان بتنفيذه في تعريف صنفي، صحيح؟ هنا يجب أن أعرّف أسلوب __init__ الخاص بي. وأفعل ذلك لأنني الآن لا أمرر العمر فقط. أريد تمرير اسم وعمر في قائمة المعاملات. ثم ماذا أفعل داخل أسلوب __init__ بعد ذلك؟ أعرف أن هذا الشخص حيوان. لذلك ما سأفعله لتبسيط حياتي هو استدعاء أسلوب __init__ للحيوان.
هنا نستخدم صيغة النقطة على اسم الصنف، على نحو يشبه كيف أريتكم تلك الطريقة الطويلة لاستدعاء الأساليب. هذا اسم الصنف، ثم نقطة، ثم اسم الأسلوب __init__. والآن أمرر جميع المعاملات، self وage. سأستدعي أسلوب __init__ للحيوان، الذي سينشئ self.age، ويضبطها إلى age، وينشئ self.name ويضبطها إلى None. لذلك أستفيد من كون أسلوب __init__ ذاك ينفّذ بالفعل هذين السطرين لي، صحيح؟
حوّلت إذًا هذين السطرين إلى سطر واحد هنا. ثم سأقول: أود ضبط اسم الشخص. لذلك سأستدعي أسلوب set_name بالمعامل الممرر. ثم سأهيّئ أيضًا سمة بيانات أخرى للشخص، وهي قائمة أصدقاء، فارغة في البداية. وما هو جميل في ذلك، وعندما ننفّذ صنف الطالب سيبدو أجمل، الجميل في ذلك هو أننا نستفيد من كون أسلوب __init__ للحيوان ينجز لنا بالفعل بعض العمل.
لكن في الوقت نفسه يمكننا أن نرى بوضوح في هذا الصنف الفرعي ما يجلبه كائن الشخص إضافة إلى كائن الحيوان، صحيح؟ فبالإضافة إلى كونه حيوانًا فقط، نعطيه اسمًا ونحصل على قائمة أصدقاء، صحيح؟ لذلك من الجميل جدًّا رؤية سمات البيانات الإضافية، أو ما تحتاجون إلى تغييره بالنسبة إلى الحيوان لإنشاء شخص. ثم بعد ذلك—أعتقد أن هذا ما قلته. آسفة، لم أتتبع ذلك أثناء قوله.
ثم بعد ذلك لدي بعض—يمكننا إضافة بعض أساليب الجلب والضبط. نفّذت عددًا مختارًا فقط. لكن ينبغي أن تضيفوها لكل السمات. get_friends يُرجع فقط نسخة من قائمتي، لأنني ربما أريد الاحتفاظ بترتيبي الأصلي أو شيء من هذا القبيل. لذلك من حسن الأسلوب إرجاع نسخة من القائمة. والقدرة على إضافة صديق إلى قائمتي تضيف فقط اسم الصديق كسلسلة نصية، إذا لم يكن موجودًا في القائمة بالفعل. لذلك لا يمكن أن يكون لدي اسمان Ana في قائمتي. أعتبرهما الشيء نفسه.
القدرة على الكلام تطبع فقط hello على الشاشة. ثم أضفت هذه الدالة الصغيرة اللطيفة لتخبرني بفرق العمر بين هذا الكائن الذي أستدعي فرق العمر عليه وشخص آخر، صحيح؟ وكل ما تفعله هو الحصول على العمرين، وأخذ القيمة المطلقة (Absolute Value) للفرق، وطباعتها على الشاشة. وأخيرًا سنعيد تعريف أسلوب __str__ للحيوان، بدلًا من قول animal، نقطتين رأسيتين، الاسم، نقطتين رأسيتين، العمر، ليقول person، نقطتين رأسيتين، الاسم، نقطتين رأسيتين، العمر. وهكذا يساعدني أيضًا على معرفة النوع.
في الكود هنا لدي شخصان، p1 وp2. هذا Jack، عمره 30. وهذه Jill، عمرها 25. إذا شغّلت get_name وget_age على كليهما، فسيشغّل ذلك get_age وget_name للحيوان. لم أعرّفهما هنا، وهذا مقبول. نرث من الحيوان. والحيوان يعرف كيف يحصل على العمر والاسم. ها هما إذًا.
إذا طبعت p1 فسيطبع person، نقطتين رأسيتين، الاسم، العمر. إذا طلبت من p1 أن يتكلم فسيطبع hello فقط. وإذا طلبت فرق العمر بين p1 وp2، ففي جميع الأحوال يأخذ القيمة المطلقة فقط، ويطبع فرق خمس سنوات. ثم دعونا نضف بعض الأصدقاء إلى p1. هنا لدي اسمان Bob. لكنها مجرد القائمة التي تحتفظ بأسماء غير مكررة.
صفحة المصدر 13
حسنًا، دعونا نجعلكم تجرّبون هذا قليلًا. إنه مرة أخرى تعامل قليل مع كائنات من هذا النوع. إنها دالة تأخذ قاموسًا. سأخبركم بما يبدو عليه القاموس. إنه يربط كائن شخص بكائن قطة، حسنًا؟ هذا قاموسي. إذًا هذا المفتاح. وهذه القيمة. لدي كل كائنات الأشخاص هذه مرتبطة بكائنات قطط.
مثلًا، هذا قاموس مدخل. p1 هو هذا الشخص هنا. وp2 هو هذا الشخص هنا، صحيح؟ إذًا مفتاحاي، p1 وp2، كائنا شخص، صحيح؟ ليسا أعدادًا صحيحة ولا أعدادًا عائمة (Floats) ولا سلاسل نصية. إنهما كائنا شخص. ثم القيم المرتبطة بهما كائنات قطط. هنا كائن من نوع القطة باسمه. شغّلت set_name على تلك القطة بعد أن أنشأتها فقط، وكذلك هنا. هنا الاسم المضبوط لكائن القطة الجديد هذا.
ربطت إذًا p1 بـc1، وp2 بـc2. إذا شغّلت هذه الدالة، فما أود فعله هو ألا تُرجع هذه الدالة شيئًا. إنها تطبع شيئًا فقط. في كل سطر، أثناء المرور على جميع العناصر في القاموس، تطبع فقط اسم ذلك المفتاح، نقطتين رأسيتين، اسم القيمة. إذًا كل ما أود فعله هو كتابة كود يستخرج الاسم من كائن الشخص ومن كائن القطة، حسنًا؟
أعرف ما تفكرون فيه. أبدو صغيرة السن جدًّا بالنسبة إلى 86. لكن السبب هو الحمية، والتمارين، وقضاء الوقت معكم، والحلوى بالتأكيد. هنا إذًا دعونا نكتب هذا الكود عند 178. حسنًا، هل لدى أحد بداية؟ نعم. d.items، نعم. دعونا نكتب ملاحظة لأنفسنا: k شخص. وv قطة، نعم.
أحد الحضور (AUDIENCE):
[غير مسموع] k.get_name.
آنا بيل (ANA BELL):
نعم، إذًا k.get_name، هل تريد حفظه في متغير؟
أحد الحضور (AUDIENCE):
[غير مسموع]
آنا بيل (ANA BELL):
أم لا؟
أحد الحضور (AUDIENCE):
[غير مسموع]
آنا بيل (ANA BELL):
آه، تريد وضعه في السطر الواحد. لا بأس، نعم. print، k.get_name.
أحد الحضور (AUDIENCE):
[غير مسموع]
آنا بيل (ANA BELL):
نعم، v.get_name، بالضبط، نعم، ممتاز. ونعم، لا شيء نُرجعه. دعونا نشغّل ذلك، رائع. هل لدى أحد أسئلة عن ذلك؟ حسنًا، نتعامل فقط مع أنواع الكائنات هذه. ومرة أخرى، إذا كان هذا مربكًا، أوصي بشدة، في الاختبارات القصيرة وأشياء من هذا القبيل، الآن ونحن نعمل مع أنواع كائنات، أن تكتبوا ملاحظات صغيرة فقط، صحيح؟ أعرف أننا نمر على قاموس. ومن المعتاد نوعًا ما، صحيح، أن تكون المفاتيح أعدادًا صحيحة وأشياء من هذا القبيل.
لكن في هذه الحالة بالتحديد، مجرد ملاحظة صغيرة بأن k شخص ستساعدكم على تذكر أنكم تحتاجون إلى تشغيل أسلوب على متغير k هذا، كما فعلنا هنا، k.get_name ثم v.get_name. حسنًا، نعم.
أحد الحضور (AUDIENCE):
كيف نضمن أن get_name، أن الـ[غير مسموع]؟ و—
صفحة المصدر 14
آنا بيل (ANA BELL):
كيف تضمنون أن المفاتيح أشخاص؟ لا يمكنكم ضمان ذلك في هذه الحالة بالتحديد. أعني، يمكن أن تقولوا: إذا كان نوع k يساوي، يساوي، Person، بحرف P كبير، Person، فنفّذوا الكود، وإلا ربما تتجاوزونه فقط، أو تطلقون خطأ قيمة (Value Error) أو شيئًا من هذا القبيل. يمكنكم فرض ذلك بهذه الطريقة. لكن في هذه الحالة بالتحديد نفترض فقط أن المختبِر سيُنشئ كائنات أشخاص مرتبطة بكائنات قطط، نعم. لكن نعم، بالتأكيد، إذا كنتم تصنعون برنامجًا لشيء أكثر تعقيدًا، فينبغي على الأرجح أن تتحققوا من ذلك وتفرضوه.
حسنًا، الفكرة الرئيسية في الوراثة هي أنه الآن، وقد أصبحت لدينا أصناف فرعية، تعرف أيضًا بالأصناف الأبناء (Child Classes)، تستخدم تلك الأصناف الفرعية سمات الأب. لذلك كل شيء يمتلكه الأب ويستطيع فعله يمتلكه الابن ويستطيع فعله أيضًا. لكن ذلك الابن يمكن أن يعيد تعريف بعض سلوكيات الأب. ويمكن للابن إضافة سلوكيات جديدة أو سمات جديدة إضافة إلى الأب. دعونا ننظر إلى صنف فرعي آخر، الطالب، قبل الانتقال إلى شيء أخير.
إذًا الطالب هنا، وفق صورنا ورسومنا، يرث من الشخص، لا من الحيوان، لكنه يرث بصورة غير مباشرة من الحيوان، صحيح؟ الطالب شخص. وعندما أنشئ شخصًا، أود جدًّا إنشاءه باستخدام اسم وعمر وتخصص. لكن يمكننا استخدام معامل افتراضي لذلك التخصص ليكون None إن لم نرغب فعلًا في تمريره. لكن أود إنشاءه مع ضبط تخصصه أيضًا.
الآن لا يمكنني استخدام أسلوب __init__ للأب لأن لدي ثلاثة معاملات أود تهيئة طالبي بها. لذلك أود إنشاء أسلوب __init__ خاص بي داخل الشخص. ها أنا أعرّف أسلوب __init__ الخاص بي. والآن يتضح لماذا من الجميل استدعاء أسلوب __init__ للأب، لأنه إذا قلت إن الطالب شخص، فكل ما أحتاج إليه لتهيئة نوع الشخص، جميع السمات المرتبطة بالشخص وأسلوب __init__ للشخص. لذلك استدعوا أسلوب __init__ للشخص فحسب.
ملاحظة المترجم: ورد في الكلام الرسمي في الفقرة السابقة «شخص» و«داخل الشخص» أثناء شرح إنشاء الطالب وتعريف
__init__في صنفStudent. أُبقيت زلة التسمية في الترجمة بدلًا من تصحيح كلام المتحدثة صامتًا.
سيُنشئ ذلك اسمي وعمري، ويضبط اسمي، وينشئ قائمة أصدقائي، وكل تلك الأشياء. إذًا تُختصر تلك الأسطر الخمسة في هذا السطر الواحد. ثم يصبح من السهل جدًّا أيضًا رؤية ما لدى الطالب إضافة إلى الشخص. ليس لديه إلا سمة بيانات التخصص. تُضبط self.major إلى أي شيء يُمرر. ثم بعد ذلك توجد فقط أساليب هنا وهناك لفعل أشياء. هنا لدي أسلوب change_major.
إنه يضبط التخصص إلى شيء فقط. ينبغي على الأرجح أن أضيف أسلوب جلب هنا أيضًا. لكن المساحة نفدت. وهذا أسلوب speak الذي يُعاد تعريفه من أسلوب الشخص. جعلت أسلوب speak للطالب أكثر تعقيدًا قليلًا مما لدى الأب. هنا أستخدم مكتبة random، لا مكتبة عشوائية وجدتها، مكتبة اعتباطية؛ إنها مكتبة اسمها random. ولديها مجموعة من الدوال التي تتيح لكم التعامل مع الأعداد العشوائية (Random Numbers).
إذًا إحدى الدوال التي تمتلكها مكتبة random هذه تعطيكم عددًا بين 0 و1 عشوائيًّا، أي عددًا عائمًا عشوائيًّا. وما أفعله في أسلوب الكلام للطلاب هو طباعة واحدة من أربع سلاسل نصية عشوائيًّا، وفق الموقع الذي يقع فيه ذلك العدد العشوائي الذي حصلنا عليه بين 0 و1. ثم—عفوًا، ليس بعد. ثم هنا أعيد تعريف أسلوب __str__.
إذًا يمكننا أن نرى في صنف الطالب هنا، هنا أنشأت طالبين. هذا لديه تخصص فعلًا. وهذا سيُضبط تخصصه إلى None، القيمة الافتراضية فقط. ثم إذا شغّلت هذا الكود، يمكنكم أن تروا أن الطالب الأول يقول شيئًا مختلفًا في كل مرة أشغّله فيها. والطالب الثاني يقول شيئًا مختلفًا. لذلك إنه فقط يولّد هذا العدد العشوائي، ثم يختار ما يطبعه. ربما ينبغي أن أرجّح شيئًا أكثر من غيره.
صفحة المصدر 15
حسنًا، هناك صنف آخر أود الحديث عنه، الأرنب (Rabbit). هذا ما لم نتحدث عنه فعلًا من تلك الفئات الفرعية الصغيرة. وأثناء الحديث عن صنف الأرنب هذا، أود تقديم فكرة أخرى للمتغير. حتى الآن لم يكن لدينا إلا متغيرات عادية تختفي بمجرد اختفاء البيئة (Environment). وتحدثنا عن متغيرات النسخة، وتعرف أيضًا بسمات البيانات، وهي متسقة للكائنات التي تنشئونها من نوع معين، لكن لها قيم مختلفة لنسخ مختلفة.
آخر متغير أود الحديث عنه هو متغير الصنف (Class Variable). الجميل في متغير الصنف هو أن تفكروا فيه بوصفه موردًا مشتركًا (Shared Resource). إنه متغير تستطيع أي نسخة من هذا النوع بالتحديد الوصول إليه وتعديله. وإذا عُدّل، فسترى جميع النسخ الأخرى هذه القيمة المعدّلة، صحيح؟ إذًا هو مشترك عبر جميع النسخ من نوع الأرنب في هذه الحالة بالتحديد. وهناك طرق مختلفة كثيرة لاستخدام متغيرات الصنف. إنها مفيدة جدًّا في البرمجة كائنية التوجه.
الطريقة التي سأستخدمه بها هنا هي أن يمنحني القدرة على عدّ نسخ هذا النوع، الأرنب، التي أنشأتها في برنامجي. إذًا عندما أشغّل البرنامج، تذكروا أنني أستطيع إنشاء مجموعة كبيرة من النسخ. سأحاول استخدام متغير الصنف هذا وسيلة للاحتفاظ أساسًا بعدّاد (Counter) لعدد هذه النسخ التي أنشأتها. حسنًا، دعونا ننظر إلى الكود.
إذًا أول شيء سأفعله هو الوراثة من الحيوان فقط. يحصل على اسم وعمر. وهذا تقريبًا كل شيء، جميع أساليب الجلب والضبط تلك وأسلوب __str__. والآن لإنشاء متغير الصنف لدي، لاحظوا أنني أعرّف هذا المتغير، مجرد متغير عادي، خارج أي أساليب داخل تعريف الصنف، صحيح؟ هنا tag = 1. أول متغير—أول نسخة من الأرنب أنشئها ستأخذ قيمة أي شيء موجود هنا.
لكن إذا غيّرت أي نسخة هذه القيمة، فسترى النسخ الأخرى القيمة المتغيرة، حسنًا؟ ما سنفعله هو تنفيذ أرقام تعريف (ID Numbers) لهذه الأرانب، مثل وضع بطاقات لها لتتبّع عددها. لذلك في أسلوب __init__ للحيوان، أو للأرنب، سأنشئ أرنبًا جديدًا باستخدام عمر وأبوين. مرة أخرى، هذا مختلف عن الحيوان، لذلك سأضطر إلى تنفيذ أسلوب __init__ الخاص بي.
لكنني سأستدعي أسلوب __init__ للحيوان لأنه ينجز بعض العمل لي. ثم سأضيف سمتي بيانات للأبوين لتكونا أي شيء يُمرر. ثم هنا في الأسفل المكان الذي سأستخدم فيه متغير الصنف، المورد المشترك، هذين السطرين. أول شيء سأفعله هو إضافة سمة بيانات أخيرة لأرنبي، وهي قيمة rid. إنها معرّف الأرنب (Rabbit ID). وستكون هذه قيمة فريدة لكل أرنب أنشئه.
أول أرنب أنشئه في برنامجي ستكون له القيمة 1. ثاني أرنب أنشئه ستكون له القيمة 2، وهكذا. إلى ماذا أضبطه؟ سأضبطه إلى أي قيمة لـtag. إذًا أول أرنب أنشئه ستكون قيمة rid لديه 1. هذه القيمة المضبوطة لـtag في البداية. لكن قبل أن أنهي أسلوب __init__ هناك سطر آخر من الكود، Rabbit.tag += 1. هذه النسخة، قبل أن تنتهي من إنشاء نفسها مباشرة، ستأخذ tag ذاك وتزيده بمقدار 1.
لذلك الأرنب التالي الذي أنشئه سيأخذ قيمة tag التي تغيرت للتو، حسنًا؟ دعونا نصوّر ذلك. سنفعله باستخدام أرانب فعلية. حسنًا، أولًا سأ—سيكون هناك ثلاثة أسطر من الكود. وهذا البرنامج الذي سأشغّله. أول شيء سأفعله هو إنشاء أرنبي الأول، صحيح؟ ستكون قيمة rid لديه أيًّا كانت قيمة tag أصلًا، صحيح؟ وفي الأصل قلنا إن tag يساوي 1.
صفحة المصدر 16
إذًا خلف الكواليس ستقول Python: أنت أول نسخة من صنف الأرنب. لذلك هُيّئ tag إلى 1. وستكون قيمة rid لديك أيًّا كانت تلك القيمة، 1. لدي هذا الأرنب، عمره 8. الأبوان None. وrid يساوي 1. لكن قبل أن أنتهي من إنشاء هذا الأرنب يقول السطر الأخير من أسلوب __init__: خذ tag وزده بمقدار 1.
حسنًا، السطر التالي في الكود يقول: دعني أنشئ أرنبًا آخر. في هذا سأمرر العمر 6 كمعامل. إذًا هذا العمر 6. والأبوان None افتراضيًّا. تقول Python: حسنًا، هذا كائن أرنب جديد. عمره 6. والأبوان None. السطر الذي يقول self.rid، أي إن rid لـr2 ستكون أيًّا كانت قيمة tag الآن. الأرنب السابق زادها إلى 2. لذلك rid لهذا الأرنب التالي تساوي 2، صحيح؟
حسنًا، السطر الأخير من الكود قبل أن ينتهي هذا الأرنب من إنشاء نفسه هو زيادة tag إلى 3. الآن إذا كان لدي سطر آخر من الكود، فإنني أنشئ أرنبًا آخر. هذا عمره 10، صحيح؟ إذًا خلف الكواليس تنشئ Python هذا المتغير المسمى r3. وهو مرتبط بكائن، كائن أرنب، عمره 10. الأبوان None بالطبع لأننا لم نمرر أي أبوين. وقيمة rid أيًّا كانت قيمة tag الآن، 3، حسنًا؟
هذا إذًا هو صاحب rid التي تساوي 3. وقبل أن ننتهي من الإنشاء، دعونا نزيد tag فقط حتى نجهّزها للأرنب التالي، حسنًا؟ هل الجميع بخير حتى الآن؟
أحد الحضور (AUDIENCE):
[غير مسموع] إنشاء السطر الأول فقط [غير مسموع]؟
آنا بيل (ANA BELL):
نعم، نعم، نعم، يعطيكم اثنين؛ لأنكم عندما تشغّلون هذا السطر، Rabbit(8)، يجب أن يشغّل __init__ حتى النهاية. والسطر الأخير من __init__ يزيدها دائمًا لتكون أكبر بواحد مما بدأت به. لا يمكنكم، على ما أظن، إيقاف تشغيل الدالة في المنتصف للفحص. حسنًا، دعونا ننظر إلى بعض الأساليب الأخرى التي يمكننا تنفيذها—آسفة، أسئلة أخرى عن هذه الطريقة الرائعة جدًّا لإنشاء الأرانب؟ نعم.
أحد الحضور (AUDIENCE):
أعتقد أنني أردت فقط معرفة المزيد عن المساحة فوق التعريفات، إذًا الـ[غير مسموع].
آنا بيل (ANA BELL):
نعم، دعونا نرجع، هنا.
أحد الحضور (AUDIENCE):
نعم، فوق تعريف __init__، تلك المساحة هناك، ماذا أيضًا—مثلًا، ماذا يحدث هناك؟
آنا بيل (ANA BELL):
هذا فقط في الغالب. نعم، غالبًا تريدون أن تكون للكائن أشياء مرتبطة به. لذلك فعلًا الأشياء المشتركة لطيفة. لكن استخدامها حساس قليلًا، فقط لأن—ينبغي أن تستخدموها لحالات محددة جدًّا، صحيح؟ لا تريدون مجرد تعريف مجموعة كبيرة من المتغيرات التي يستطيع الجميع الوصول إليها هنا وهناك، بل في حالات محددة فقط. نعم، في معظم الوقت لديكم أساليب فقط في التعريف، نعم. لكن ربما هناك أشياء أخرى. أنا لا أعرف عنها الآن فقط، نعم.
حسنًا، دعونا ننظر إلى بضعة أساليب أخرى للأرنب. هنا لدي أسلوب جلب، ثلاثة أساليب جلب فقط. ينبغي على الأرجح أن أضع—لا أريد وضع أسلوب ضبط لـrid، لأن هذا سيفسد العدّ لدي. وعلى الأرجح لا أريد أساليب ضبط للأبوين أيضًا. لكن ربما قد نريدها. لا أعرف. الشيء الوحيد الذي يبدو غريبًا قليلًا في get_rid هو هذا zfill.
وأضفته بوصفه شيئًا صغيرًا لطيفًا لجعل المعرّف يبدو مثل رقم تعريف. فهو يملأ البداية مسبقًا بأصفار. مثلًا، يحشو البداية بأصفار. لذلك للمعرّف 1 يمكنكم أن تروا أنه 00001. وللمعرّف 123 سيكون 00123، صحيح؟ إذًا هو فقط يجعل شكله جميلًا عندما نطبعه، عندما نطبع المعرّف. وخلاف ذلك، أسلوبا الأبوين يُرجعان كائني الأبوين فقط.
صفحة المصدر 17
أحد الأساليب المثيرة للاهتمام التي أود إضافتها، وسنستفيد هنا من كون الأرانب تتزاوج، هو جمع أرنبين معًا. إذًا ننفّذ أسلوب الشرطة السفلية المزدوجة، شرطتين سفليتين، add، شرطتين سفليتين، لكي تكون لدينا القدرة على جمع أرنبين معًا في الكود، حسنًا؟ مرة أخرى، هذا قرار تصميمي اتخذته أنا. عندما أجمع أرنبين معًا سأنشئ كائن أرنب جديدًا. وهذا بالضبط ما يفعله الكود هنا في الداخل.
سأشغّل إذًا أسلوب __add__ هذا على self وother، صحيح؟ ثم خلف الكواليس، أو أمامها على ما أظن، سيكون معامل الجمع هذا +. إذًا self هي الشيء قبل علامة الجمع. وother هي الشيء بعد علامة الجمع، صحيح؟ تمامًا كما رأينا في المحاضرة الماضية. عندما نجمع r1 + r2، أود أن تكون النتيجة كائن أرنب آخر، أحد أبويه r1، والآخر r2. هذان الشيئان اللذان جمعناهما معًا. ولنقل إن عمر كائن الأرنب الجديد هذا 0، صحيح؟
إنه مولود جديد. لتنفيذ ذلك لدينا فقط—نُرجع كائن أرنب جديدًا هنا، صحيح؟ إذًا ننشئ كائن أرنب جديدًا في أثناء التنفيذ داخل هذا الأسلوب. كيف ننشئ كائن أرنب؟ نحتاج إلى إعطائه عمرًا وأبوين. أصلًا عندما أنشأنا الثلاثة، r1 وr2 وr3، لم يكن لهم أبوان، صحيح؟ كانا فقط غير معروفين أو شيء من هذا القبيل. لكن في هذه الحالة بالتحديد نريد أن نعرف من أبواهما. الأبوان هما الشيء قبل علامة الجمع والشيء بعدها.
إذًا أحد الأبوين سيكون self. والآخر سيكون other، الشيء الموجود في قائمة المعاملات. دعونا نواصل برنامجنا هنا. كان لدينا هذه الأسطر الثلاثة من الكود التي شُغّلت. وأنشأت هذه الأرانب الثلاثة بهذه المعرّفات، 1 و2 و3. إذا جمعت أرنبين معًا، r1 + r2، ليعطياني متغير كائن أرنب، e4، تقول Python: حسنًا، دعيني أشغّل أسلوب الشرطة السفلية المزدوجة هذا خلف كواليس علامة الجمع.
إذًا ماذا تصبح r4 فعليًّا؟ نعوّض، في الشريحة السابقة هنا، القيمة المُرجعة هي أرنب عمره 0، أحد الأبوين، فاصلة، الأب الآخر. عندما ننفّذ هذا الجمع، لدينا أرنب عمره 0، فاصلة، أحد الأبوين، الشيء قبل النقطة، فاصلة، الأب الآخر، أو الشيء قبل علامة الجمع ثم الشيء بعدها. لذلك تصبح r4 لدي نتيجة جمع r1 + r2. وهذان هما أبواها.
ملاحظة المترجم: يرد
e4في النص الإنجليزي المستخرج في الفقرة السابقة؛ يتابع المصدر مباشرة باسمr4. أُبقي الاسمان مع بيان الاختلاف، لا باعتبارهما متغيرين مقصودين مختلفين. الوصف الشفهي لإنشاء الأرنب يشير إلى تمرير العمر والأبوين للباني، أيRabbit(0, self, other).
والآن كيف يُنشأ هذا الأرنب، صحيح؟ إنه كائن أرنب جديد. لذلك نشغّل أسلوب __init__ لكائن الأرنب، ما يعني أن هذا متغير. إنه مرتبط بكائن أرنب. عمره 0. وله هذان الأبوان اللذان يرتبط فيهما كائننا بكائني أرنب آخرين، في الأعلى، r1 وr2. والمعرّف، كما من قبل تمامًا، هو أيًّا كانت قيمة tag الآن. سبق أن أنشأنا ثلاثة كائنات أرانب قبل هذا. لذلك ستكون قيمة tag لهذا 4.
ثم قبل أن ننتهي مباشرة نزيد tag إلى 5. لذلك مهما كانت طريقة إنشاء كائنات الأرانب هذه، إما بالطريقة العادية في برنامجنا مباشرة أو من خلال أسلوب غير مباشر، في هذه الحالة الجمع، فنحن ما زلنا ننشئ كائنات أرانب في برنامجنا، صحيح؟ لذلك ما زال ذلك العدّاد، ذلك المتغير المشترك tag، يؤدي دوره، صحيح؟ ما زلنا نعدّ جميع كائنات الأرانب التي تُنشأ.
هل هذا مفهوم؟ حسنًا، جيد. إذًا نعم، لا بأس. أسلوب أخير: هذا أسلوب يفحص المساواة (Equality) بين أرنبين. ومرة أخرى خياري التصميمي هو القول إن أرنبين متساويان. فإذا قلت r1 == r2، فسيخبرني ذلك بـTrue أو False. وخياري التصميمي هو أن أرنبين متساويان إذا كان لهما الأبوان نفسهما. لذلك إذا أنشأت كائن أرنب آخر، كان 4 هو r1 + r2.
صفحة المصدر 18
لكن إذا كان 5 هو r2 + r1، فأريد القول إن 5 و4 متساويان لأن لهما الأبوين نفسهما، صحيح؟ لا أهتم هل كان r1 + r2 أو r2 + r1. لهما الأبوان نفسهما. إنه ترتيب معكوس فقط. وهذا ما يفعله أسلوب __eq__ هذا. إنه أسلوب شرطة سفلية مزدوجة لتنفيذ المساواة بين أرنبين. إذًا parents_same هنا قيمة منطقية (Boolean) تفحص rid فقط. هذه القيمة المنطقية، parents_same، ستفحص أن الجمع أُجري r1 + r2، وr1 + r2، صحيح؟
وparents_opposite أيضًا قيمة منطقية، إما True أو False، تفحص ما إذا أنشأت الأرانب باستخدام r1 + r2 ثم r2 + r1، أي بترتيب معكوس للأبوين. لكن ما زال لهما الأبوان نفسهما. والسبب في فحصي المعرّفات هو أنها فريدة. فأصلًا عندما كتبت هذا الكود منذ وقت طويل، انتهى بي الأمر فعلًا في المحاولة الأولى إلى فحص قيم الأبوين مباشرة، صحيح؟ فكان يقارن أساسًا كائنات الأرانب بعضها ببعض.
لكن المشكلة في ذلك الكود هي أنه في مرحلة ما حاول مقارنة None، فقد يكون لبعض الأرانب None بصفته أحد أبويها، بكائن أرنب فعلي. ثم تعطل الكود. وأدركت بعد ذلك أنني أستطيع مقارنة قيم المعرّفات مباشرة؛ لأنها، أولًا، مجرد أعداد، لذلك تسهل مقارنتها جدًّا. وثانيًا هي فريدة. لذلك أعرف أنه لن يكون لدي أرنبان بالمعرّف نفسه. في هذه الحالة بالتحديد لدي هذان الأرنبان. ينبغي أن أقول إنهما متساويان. لكن إذا جمعت r2 + r3، أي r6، فلن يساوي هذا أيًّا من أرانبي الأخرى.
إذًا هذا الكود الخاص بي. هنا لدي أرانبي الثلاثة، صحيح؟ أعتقد أننا طبعنا هذا بالفعل. هذا r1. إنه أرنب بهذا المعرّف، وأرنب بهذا المعرّف، وأرنب بهذا المعرّف. ثم أبوا r1 وأبوا r2 وأبوا r3 جميعهم None، هم None. لكن عندما أضيف r4 بصفته r1 + r2، أستطيع طباعة—r4 أرنب معرّفه 4. ثم r1 وr2 كما المعتاد، ما رأيناه للتو.
ثم عندما نحصل على أبوي r4 سيكونان r1، وهو هذا الأرنب بهذا المعرّف، وr2، وهو هذا الأرنب بهذا المعرّف، صحيح؟ ثم يمكننا فحص المساواة. هنا يمكنني إنشاء r4 وr5 وr6. إذًا r3 + r4 وr4 + r3 ينبغي أن يكونا متكافئين، صحيح؟ هنا لدي r5 وr6 في الأسفل. ترون أنني أشغّل فقط علامة المساواة المزدوجة على كائنات من نوع الأرنب، وهذا رائع جدًّا.
وهما متماثلان، صحيح؟ لأن لهما الأبوين نفسهما. لا يهمني أنهما بترتيب معكوس. لكن r4 وr6 لهما أبوان مختلفان، صحيح؟ كان لـr4 الرقمان 1 و2. وكان لـr6 الرقمان 3 و4. هل هناك أسئلة عن هذا الكود؟ حسنًا، متغيرات الصنف رائعة جدًّا. تشتركون فيها عبر جميع النسخ. لذلك إذا عدّلتها نسخة واحدة، فستكون معدّلة لجميع النسخ الأخرى.
إذًا لدينا مثال آخر ننظر إليه في المحاضرة القادمة. سننفّذ فعلًا صنف متتبّع لياقة (Fitness Tracker) خاصًّا بنا. سيكون أكثر تعقيدًا قليلًا. لكننا سنرى كثيرًا من الأفكار نفسها التي رأيناها اليوم، في هذا السياق الأكثر تعقيدًا قليلًا لتنفيذ متتبّع اللياقة الخاص بنا. إذًا ما زال شيئًا مجردًا نوعًا ما، لكنه أكثر فائدة من أصناف الحيوان والأرنب والشخص والطالب.