مقدمة في علوم الحاسوب والبرمجة باستخدام Python › المحاضرة 20: مثال البرمجة كائنية التوجه: متتبّع اللياقة 22 / 30 السابق التالي

النص الكامل للمحاضرة 20: متتبّع اللياقة مثالًا على البرمجة كائنية التوجه

النص الكامل للمحاضرة 20: متتبّع اللياقة مثالًا على البرمجة كائنية التوجه

المصدر الأصلي: النص الإنجليزي الرسمي بصيغة PDF، فيديو المحاضرة الرسمي، صفحة المحاضرة.

النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022 (MIT Fall 2022)، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare (MITOCW). هذه ترجمة عربية غير رسمية وتكييف لغوي، ولا تعني تأييد MIT أو اعتماد الترجمة منه. المواد الأصلية المشمولة وهذه الترجمة تحت رخصة CC BY-NC-SA 4.0: الاستخدام غير التجاري، ونسبة العمل إلى أصحابه، ومشاركة التكييفات بالرخصة نفسها. مواد الأطراف الثالثة المستثناة أو ذات إشعارات مستقلة لا تشملها رخصة OCW الافتراضية؛ تسري عليها إشعاراتها الأصلية. انظر المقرر الأصلي وشروط MIT OCW.

ملاحظة المترجم عن التغطية: تُترجم هنا المحاضرة كاملة وفق content-src/mit-6100l/extracted/1l8mjRn1hi9LyjpsOLlLbZwxdsseNS_va_transcript.txt، مع اتباع حدود الفقرات وفواصل الصفحات في الملف المطابق content-src/mit-6100l/extracted/1l8mjRn1hi9LyjpsOLlLbZwxdsseNS_va_transcript.layout.txt. عناوين «صفحة المصدر» تطابق الصفحات العشرين غير الفارغة المفصولة بعلامة formfeed، ولا يُعدّ الفراغ بعد الفاصل الأخير صفحة إضافية. حُفظ ترتيب جميع الفقرات وأدوار المتحدثين والتكرار والتصحيحات الشفهية والمقاطع غير المسموعة والعلامات الصوتية، دون تلخيص. تبقى أسماء Python والتعبيرات كما في النص، بما فيها تفاوت الأسماء في الكلام؛ وأي ملاحظة للمترجم تظهر اقتباسًا منفصلًا موسومًا بوضوح.

صفحة المصدر 1

MITOCW | المحاضرة 20 من 6.100L، الإصدار 2.mp4

[صرير]

[حفيف]

[نقرات]

آنا بيل (ANA BELL): حسنًا، لنبدأ إذن. ستكون محاضرة اليوم آخر محاضرة لنا عن البرمجة كائنية التوجه (Object-Oriented Programming)، وإنشاء أنواع البيانات الخاصة بنا باستخدام أصناف Python (Classes). لذلك سنستعرض في محاضرة اليوم مثالًا أكثر تفصيلًا. سننشئ كائن متتبّع اللياقة (Fitness Tracker) الخاص بنا. وبالتحديد، سننشئ صنفًا يطبّق فكرة جلسة التمرين (Workout).

وستبدو شرائح اليوم مشابهة جدًا لشرائح محاضرة يوم الاثنين. فكثير منها يعزّز نوعًا ما الأفكار نفسها التي رأيناها في المحاضرة الماضية: إنشاء طرائق الجلب والتعيين (Getters and Setters)، وإنشاء متغيرات الصنف (Class Variables)، وفكرة الوراثة (Inheritance). لكننا سنفعل ذلك فحسب في سياق هذا المثال الأكثر تفصيلًا، متتبّع اللياقة، حسنًا.

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

المنظور الأول هو الموجود هنا في الجانب الأيسر، حيث نتخذ قرارات تصميمية بشأن كيفية تنفيذ هذا الكائن الجديد، وهذا النوع الجديد من البيانات. وعندما نتخذ هذه القرارات التصميمية، نقرر اسم الكائن. ونقرر السمات (Attributes)، وهي إما بيانات وإما إجراءات نريد أن يمتلكها الكائن.

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

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

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

إذن سننشئ أولًا كائن تمرين بسيطًا جدًا، بالروح نفسها التي كنا ننشئ بها الكائنات. ثم سنحسّنه قليلًا بإضافة طرائق أفضل وأشياء من هذا النوع. وبعد ذلك سنستعرض فكرة الوراثة لإنشاء أنواع محددة جدًا من جلسات التمرين.

صفحة المصدر 2

إذا فكرنا في جلسات التمرين، فلدينا أنواع مختلفة كثيرة من التمارين، أليس كذلك؟ لدينا ركوب الدراجة والسباحة والجري، لكن في جوهر هذه التمارين جميعًا، إذا فكرنا في المعلومات المتعلقة بتمرين عام جدًا، لا بتمرين مخصوص للجري أو السباحة أو ركوب الدراجة، بل مجرد تمرين عام، فهناك بعض الخصائص المشتركة التي تمتلكها جميع هذه التمارين. لذلك أدرجتها هنا.

قد تكون للتمرين أيقونة (Icon) مرتبطة به. إذن هذه أو هذه أو هذه، لكن مهما تكن، هناك خاصية أيقونة للتمرين. ونوع التمرين، مثل ركوب الدراجة والسباحة والجري وأشياء من هذا القبيل. وتاريخ، ربما تاريخ بداية وتاريخ نهاية، ووقت بداية ووقت نهاية، وهذا النوع من المعلومات. ومعدل ضربات القلب، ربما متوسط معدل ضربات القلب خلال ذلك النشاط كله، والمسافة، وعدد السعرات الحرارية المحروقة. جميع هذه الخصائص مشتركة بين أي نوع من التمارين قد نرغب في إنشائه.

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

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

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

حسنًا، سيتعين علينا إذن تحديد سمات البيانات (Data Attributes). ذكرنا للتو في الشريحة السابقة أننا حين نتخذ قرارات تصميمية، نحدد السمات التي نود أن يمتلكها نوع الكائن الخاص بنا. أي البيانات والسلوكيات. بالنسبة إلى بيانات تمريننا، قررنا أنها ستكون وقت البداية ووقت النهاية وعدد السعرات الحرارية المحروقة. هذه الأشياء الثلاثة معًا؛ ربما وقت البداية سلسلة نصية (String)، ووقت النهاية سلسلة نصية، والسعرات عدد، إما float أو int أو أي شيء. هذه الأشياء الثلاثة معًا تشكّل الكائن، وهو تمرين.

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

حسنًا، لنبدأ تعريف صنفنا. هذا صنف تمرين بسيط جدًا. سننفّذ إذن المربع رقم واحد هناك قبل أن نحسّنه. وهذا بالروح نفسها التي كنا نعرّف بها الأصناف في المحاضرات الثلاث الماضية، حسنًا. نخبر Python أننا ننشئ كائنًا جديدًا بقولنا إن هذا صنف، واسم هذا الكائن. إذن نوع هذا الكائن هو Workout، وأب هذا الكائن هو كائن Python العام object، وحتى الآن الأمور جيدة، أليس كذلك؟

صفحة المصدر 3

الآن، أول طريقة يجب أن ننفّذها هي طريقة init. إنها تخبر Python كيف تنشئ كائنًا من النوع Workout، أي الباني (Constructor). ولدينا مجموعة من المعاملات (Parameters) هنا لأنها مجرد دالة عادية لها شيء من الخصوصية. المعامل الأول لكل طريقة هو بالطبع self.

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

إذن سنقول: حين ننشئ كائن Workout، سنخبره بوقت البداية ووقت النهاية وعدد السعرات الحرارية المحروقة. هذا هو الهيكل الأولي للدالة (Function Stub)، على ما أظن. هذه هي كيفية إنشاء الكائن. ثم ماذا تفعل هذه الدالة فعلًا؟ ماذا تفعل هذه الطريقة فعلًا؟ إنها تفعل بعض الأشياء المعتادة التي نعرفها في هذه المرحلة. فهي أساسًا تربط كل واحد من معاملات الإدخال هذه بسمات البيانات self.start وself.end وself.calories، حسنًا.

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

سنجعلهما فقط سلسلتين نصيتين افتراضيًا. ستكون الأيقونة هي السلسلة النصية التي تحتوي على هذا الرمز التعبيري للشخص المتعرّق. ويمكن أن تكون لديك رموز تعبيرية (Emojis) داخل السلاسل النصية، وهذا رائع في الواقع. وسيكون النوع هو مجرد نوع التمرين، لذلك سنعيّنه فقط ليكون السلسلة workout. عندما ننشئ تمارين الجري، ستكون مجرد السلسلة running. وعندما ننشئ تمارين ركوب الدراجة، ستكون سلسلة biking. أيًّا يكن ما تريدونه. وسنرى أين تظهر هذه الأشياء لاحقًا.

حسنًا، هذا هو إذن تعريف صنفي Workout، وفي الوقت الحالي هذا كل شيء. هذا كل ما لدينا من حيث تعريف الصنف. الآن، ماذا يحدث حين ننشئ كائن Workout فعليًا؟ نستدعي اسم الصنف. فنقول هنا Workout. وسنحفظ هذا الكائن الموجود إلى يمين علامة المساواة في متغير my_workout، وحتى الآن هذه مراجعة.

نمرّر المعاملات التي يتوقعها كائن Workout. إذن هذه سلسلة تمثل وقت البداية، وسلسلة تمثل وقت النهاية، والسعرات الحرارية المحروقة في هذا التمرين بالتحديد، وهي 200. نعم، جيد.

حسنًا، يمكننا إذن إضافة مجموعة كبيرة أخرى من الطرائق إلى صنفنا. كانت تلك مجرد طريقة init. لكنني ذكرت في المحاضرة الماضية أنه من المهم إضافة طرائق الجلب والتعيين كي نتيح للمستخدم الحصول على سمات البيانات المختلفة أو تعيينها. لذلك لدي هنا ثلاث طرائق جلب للحصول على السعرات ووقت البداية ووقت النهاية، وثلاث طرائق تعيين لتعيين السعرات ووقت البداية ووقت النهاية.

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

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

صفحة المصدر 4

وهذا يُسمّى قاموس حالة الصنف (Class State Dictionary). إنه قاموس يحتفظ أساسًا بحالة كائنك. لذلك أردت أن أريكم كيف يبدو ذلك في الشيفرة. هذا هو صنفي Workout. وطريقة الوصول إلى قاموس الحالة هي باستدعاء اسم صنفك. أي ليس نسخة، بل اسم الصنف الفعلي، ثم نقطة، ثم طريقة داندر (Dunder) هذه: شرطتان سفليتان، dict، شرطتان سفليتان، أي __dict__. فهذا يحتفظ بقاموس الحالة.

وإذا وصلنا فقط إلى المفاتيح، سنرى هنا كل طريقة عرّفناها في صنفنا. ترون هنا، هذا هو قاموسي. يمكنني تحويله إلى قائمة إذا أردت، لكن ذلك غير ضروري. لكن يمكنكم رؤية كل طريقة عرّفناها. أي جميع طرائق الجلب، وجميع طرائق التعيين، وطريقة init، و__doc__، التي تجلب لنا في الواقع سلسلة التوثيق (Docstring) هذه التي وضعتموها مباشرة تحت تعريف الصنف. فهذا لطيف نوعًا ما.

إذن هذه مفاتيح القاموس، وبالطبع، كما نعرف، للمفاتيح قيم مرتبطة بها. لذلك ستكون القيم المرتبطة بكل واحد من هذه المفاتيح، ويمكننا رؤية ذلك هنا، مثلًا القيمة المرتبطة بسلسلة التوثيق، ستكون حرفيًا الشيء الذي طبعته: سلسلة التوثيق الصغيرة التي وضعتها مباشرة تحت تعريف صنفي.

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

حسنًا، هذا إذن قاموس الحالة لتعريفي، أي تنفيذ صنفي. الآن، ماذا يحدث حين أنشئ نسخة فعلية؟ هنا لدي my_workout يساوي، وأصبح لدي الآن هذه النسخة الفعلية من هذا الصنف ذي النوع Workout.

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

والآن سيكون لهذا الكائن قاموس الحالة الخاص به. في قاموس حالة الكائن (Object State Dictionary)، لا نخزّن طرائق أو أشياء من هذا النوع. بل نخزّن سمات البيانات الفعلية المرتبطة بهذا الكائن، حسنًا. إذن هذا الكائن، جميع سمات بياناته هي كل الأشياء التي تصلون إليها عبر الكلمة self ثم نقطة: self.icon وself.kind وself.start وself.end وself.calories.

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

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

صفحة المصدر 5

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

في المحاضرة الماضية قلت إنكم تستطيعون استخدام صيغة النقطة للوصول إلى سمات البيانات. هنا نصل إلى قيمة السعرات. وهذا مقبول، لكن المفضّل هو استخدام طرائق الجلب. لذلك ستُرجع get_calories، في هذه الحالة بالتحديد، القيمة نفسها تمامًا التي نحصل عليها بمجرد الوصول إلى سمة البيانات calories.

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

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

إذن الفكرة وراء استخدام هذه الطرائق المتسقة، بدلًا من الوصول باستخدام سمات البيانات، هي أنك تريد إبقاء المعلومات مخفية. لا تريد أن تبدأ العبث بالنظر في كيفية تنفيذ شيء ما. لأن ذلك يتعارض مع مبدأ التجريد (Abstraction)، والوحدوية (Modularity)، وإخفاء المعلومات (Information Hiding). تريد إبقاء الأشياء مخفية لأنك تريد استخدام الكائنات التي أنشأها شخص آخر بطريقة متسقة ولطيفة.

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

وبهذا نكون قد أنهينا صنف Workout البسيط. والآن سنغيّر التنفيذ قليلًا فقط. وما سنفعله هو أننا سنُحدث تغييرًا في الطريقة التي نخزّن بها المعلومات. سنستخدم متغير صنف. وسأذكّركم بما هو متغير الصنف في الشريحة التالية، وسنُحدث تغييرًا في طريقة get_calories.

وسنسمح للمستخدم بأن يقول: مهلًا، سأُنشئ كائن Workout هذا. لكن لا أعرف عنكم، أما أنا فلا أعرف عدد السعرات التي أحرقها حين أؤدي تمرينًا مدة 40 دقيقة. لا أعرف ذلك فورًا.

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

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

حسنًا، هذا هو التنفيذ الجديد لصنفي Workout. أول شيء ستلاحظونه هو أننا نستخدم متغير الصنف هذا. تحدثنا عن هذا في المحاضرة الماضية حين نفّذنا مثال الأرانب. في مثال الأرانب، جعلنا كل أرنب يغيّر قيمة متغير الصنف هذا.

صفحة المصدر 6

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

إذن سيمثل متغير الصنف هذا عدد السعرات التي تُحرق في الساعة، أي مجرد عدد. ثم طريقة init، ومرة أخرى، سننشئ طريقة init مختلفة عما رأيناه في الشرائح السابقة، ستكون طريقة init جديدة ومحسّنة. سنستقبل العدد نفسه من المعاملات، لكن السعرات ستكون لها قيمة افتراضية (Default Value).

إذا مرّر المستخدم عدد السعرات فعلًا، فستكون قيمة السعرات هنا، self.calories، هي أي قيمة مرّرها المستخدم. لكن إذا لم يمرّر المستخدم عدد السعرات، فسيكون هذا المعامل هنا، self.calories، هو None. ويُستخدم None لتمثيل غياب قيمة. إذن لدينا خياران هنا عند إنشاء الكائن.

ومن الأشياء الأخرى التي قد تلاحظونها أن self.start، أي وقت البداية ووقت النهاية، لم يعودا مجرد start وend. سأتحدث عن هذا في الشريحة التالية. لكن ما أفعله هنا أساسًا هو أنني أقول إن البداية والنهاية ستُمرّران بوصفهما سلسلتين نصيتين، تمامًا كما كنا نفعل في الماضي، مثل September 1, 2022 1:35 PM. هذا مقبول. ما زال بإمكاننا تمرير وقت البداية بوصفه سلسلة نصية.

لكن حين أخزّنه داخل كائني، سأخزّنه في الواقع بوصفه أي شيء يعطيني إياه هذا الشيء. وهذا الشيء سيُرجع في الواقع السلسلة أو يحلّلها بوصفها نوع بيانات جديدًا، شيئًا لم نعمل معه من قبل، يُسمّى كائن datetime. سننظر في هذا في الشريحة التالية بتفصيل أكثر قليلًا. لكن حاليًا، كل ما نحتاج إلى معرفته هو أن self.start وself.end سيكونان من نوع بيانات جديد، وهو كائن datetime.

إذن هذه طريقة init لدي، مع بعض التغييرات. والآن ستبدو طريقة get_calories لدي مختلفة قليلًا أيضًا. لم نعد نُرجع self.calories فقط كما فعلنا في صنف Workout البسيط ذاك. سنُجري تبديلًا صغيرًا.

إذا قدّم المستخدم عدد السعرات، أي إذا كانت السعرات هنا قد مُرّرت بالفعل، فلن نلجأ إلى كون السعرات None. ستكون السعرات 100 أو 450 أو أي قيمة كانت. وعندئذ يكون شرط if هذا غير متحقق، فندخل إلى else، ونُرجع تلك القيمة فقط. إنه إذن السلوك نفسه تمامًا الذي كان في صنف Workout البسيط لدي هناك من قبل.

لكن إذا لم يقدّم المستخدم عدد السعرات حين يُنشئ كائنًا، فستكون السعرات None هنا. حين أنشئ كائني، ستكون سمة البيانات self.calories هي None هنا. لذلك حين أطلب من التمرين أن يخبرني بعدد السعرات التي أحرقتها، سندخل إلى عبارة if. وسنفعل شيئًا ما.

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

وهو مجرد نوع جديد من الكائنات لم نعمل معه حقًا من قبل، لكنه نوع كائن يمكننا تشغيل طريقة عليه. وستكون الطريقة هي total_seconds. إذن بالنسبة إلى كائن timedelta هذا، وليكن 10 دقائق أو 18 دقيقة أو أيًّا كان، إذا شغّلنا هذه الطريقة المسماة total_seconds، فستخبرنا بعدد الثواني في كائن timedelta هذا. نقسم على 3,600 كي يخبرنا بعدد الساعات، ثم نضرب في متغير الصنف cal_per_hour، فيخبرنا بعدد السعرات التي حُرقت خلال ذلك الوقت المنقضي. نعم.

صفحة المصدر 7

أحد الحضور (AUDIENCE): إذا كان بإمكاننا أن نفعل مثلًا workout ثم نقطة، وبعدها كل ذلك، مثل—

آنا بيل: أوه، Workout ثم نقطة هو مجرد هذا الشيء هنا. Workout.cal_per_hour، إنه هذا فقط، ثم نضرب في ذلك العدد. هل هناك أسئلة عن ذلك؟ حسنًا، إذن هذا سيُجري لنا أساسًا تقدير عدد السعرات التي أحرقناها في عدد ما من الساعات أو عدد ما من الدقائق.

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

في هذه الحالة بالتحديد، توجد دالة داخل تلك المكتبة، دالة parser.parse هذه، التي تستقبل سلسلة نصية ويمكنها تحليلها إلى كائن datetime هذا. وإذا طبعنا نوع تاريخ البداية ونوع تاريخ النهاية، فسيُظهر لنا أنه هذا الشيء من النوع datetime. إذن إنه نوع كائن جديد لم نعمل معه بعد، لكنه نوع كائن مثل القائمة، ومثل القاموس، ومثل التمرين الخاص بنا.

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

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

أحد الحضور: هل تُستورد تلك الثواني الإجمالية من أجل المحلّل parser؟

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

أحد الحضور: إذن هناك مثل إجمالي الساعات، وإجمالي الدقائق.

آنا بيل: أظن أنه قد يكون هناك إجمالي الدقائق وإجمالي الساعات أيضًا، نعم، نعم.

أحد الحضور: إذن هذا الصنف parser، وبعده نقطة، parser ثم نقطة، total_seconds، هي—

آنا بيل: نعم، بالضبط، نعم، بالضبط. نعم، إذن، نعم.

أحد الحضور: هل ينبغي أن تكون الشيفرة، هل ينبغي استيراد ذلك الشيء؟

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

صفحة المصدر 8

أردت إذن أن أريكم هنا في الأسفل. هنا، كان ينبغي ألا أستورد مرة أخرى، لكنه مجرد جزء من هذا التمرين. هنا لدي استيراد parser. ولدي وقت البداية. هذه مجرد سلاسل نصية، لا شيء مميز فيها. ويمكننا تحليلها. إذن لدي هذه السلاسل وقد حُلّلت.

وأنواع هذه الكائنات، مرة أخرى، لم تعد سلاسل نصية الآن بعد أن حلّلتها. أصبح start_date وend_date الآن كائني datetime هذين، أي datetime.datetime. ثم يمكننا إجراء عمليات مثل هذه. فإذا طرحت ببساطة وقتًا من الآخر وطبعت كائن timedelta ذاك، تضعه Python في هذا التنسيق الصغير اللطيف من أجلي. ينبغي أن أحوّل هذه الأسطر إلى تعليقات فقط. من الصعب رؤيته. إنها تضعه في هذا التنسيق الصغير اللطيف من أجلي.

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

لكن الشيء الرائع في المحلّل، وهذا سيكون رائعًا حقًا حقًا، هو أنه يمكنكم في الواقع كتابة الوقت والتاريخ بأي تنسيق. لا يجب أن يكون شهرًا، ثم شرطة مائلة، ثم يومًا، ثم شرطة مائلة، ثم سنة، ثم مسافة، ثم هذا. هذا نوعًا ما ما كتبته في هذا المثال. يمكننا في الواقع أن نكتبه شيئًا مثل Sept 30, 2021 بهذه الطريقة. وهو يعرف كيف يقرأ ذلك.

أو يمكننا كتابة September كاملة، ووضع الفاصلة هناك، ووضع الفاصلة هناك، وكتابة pm بأحرف صغيرة وأقرب إلى الوقت، وهو يعرف كيف يقرأ ذلك أيضًا. إذن يعرف كيف يحلّل جميع هذه الطرق المختلفة لكتابة التواريخ، ويحفظها بوصفها كائنات datetime هذه لاستخدامها بهذه الطريقة اللطيفة جدًا والسلسة جدًا في القراءة. أليس ذلك رائعًا؟ حسنًا، هذا مفيد جدًا إذا أردتم يومًا العمل مع أنواع التواريخ.

إذن هذا الآن قاموس حالتنا، أي لما وصلنا إليه في صنف Workout البسيط. لكن ما التغييرات التي أجريناها لتحسينه؟ حسنًا، أضفت في قاموس حالتي متغير الصنف calories_per_hour. فأصبح calories_per_hour هذا متاحًا الآن لأي نسخة أنشئها. كنا نعرف ذلك بالفعل، لكن هذا نوع من تمثيل ذلك. ولم نُضف شيئًا إلى النسخ. تلك لم تتغير.

إذن استطراد صغير عن متغيرات الصنف: cal_per_hour هذا هنا متاح لجميع هذه النسخ. ومتغير الصنف يشبه متغير النسخة (Instance Variable) تمامًا. يمكننا الوصول إليه من داخل تعريف الصنف، وهكذا ينبغي أن يُفعل ذلك دائمًا. لكن Python، كعادتها، تتيح لكم الوصول إلى متغير الصنف هذا من خارج تعريف الصنف أيضًا.

يمكننا إذن أن نفعل شيئًا كهذا. يمكننا استدعاء متغير الصنف cal_per_hour على اسم هذا الصنف خارج التعريف. هذا هو تعريف صنفي. انتهى هنا. وهذه مجرد شيفرة خارج التعريف، وستكون Python سعيدة بأن تخبركم بقيمة ذلك المتغير.

وستكون Python سعيدة أيضًا بأن تخبركم بقيمة ذلك المتغير إذا وصلتم إليه من خلال نسخة. أنشأت هنا نسخة فعلية من النوع Workout، ولذلك لا أستدعي cal_per_hour على اسم صنفي، وإنما أحصل عليه من خلال إحدى نسخه. وإذا طبعت النسخة ثم نقطة ثم cal_per_hour، فستخبرني Python بسرور أيضًا بقيمة ذلك المتغير.

صفحة المصدر 9

وPython، كعادتها، ستتيح لكم تغيير قيمة cal_per_hour هذا خارج تعريف الصنف أيضًا. هنا، خارج تعريف الصنف، سأقول Workout.cal_per_hour = 250. والآن تغيّر cal_per_hour نهائيًا إلى 250، مهما كانت كيفية وصولي إليه، سواء باستدعاء اسم صنفي مباشرة، أو باستدعاء متغير الصنف من خلال إحدى النسخ.

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

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

الأول الذي أود منكم إنشاءه، سمّوا متغيره w_one. بدأ هذا التمرين في 1 يناير 2001، الساعة 3:30، واستمر حتى 4:00 مساءً. وتريدون تقدير السعرات من هذا التمرين. لا تعرفون عدد السعرات التي أحرقتموها، ثم اطبعوا قيمة تلك السعرات. ثم الكائن الثاني، تاريخ البداية نفسه وتاريخ النهاية نفسه، لكنكم تعرفون أنكم أحرقتم 300 سعرة. أنشئوا إذن هذين الكائنين، ثم اطبعوا عدد السعرات الحرارية المحروقة.

إذن هذا في السطر 199. ولا بأس بالتمرير إلى الأعلى مجددًا إلى طريقة init الخاصة بـWorkout، فقط لتروا كيف نُفّذت. لا يوجد سبب يدعوكم إلى حفظها الآن.

حسنًا، كيف ننشئ هذين الكائنين؟ ما الذي يساويه w_one؟ نعم.

أحد الحضور: [غير مسموع]

آنا بيل: نعم.

أحد الحضور: هل يأتي التاريخ أولًا؟

آنا بيل: نعم، سيأتي start_date أولًا. لذلك يمكنني فقط أن—

أحد الحضور: [غير مسموع] ضع ذلك بوصفه سلسلة نصية.

آنا بيل: بوصفه سلسلة نصية، ممتاز، نعم.

أحد الحضور: [غير مسموع]

آنا بيل: نعم. أنا لا أقول. لا أظن أنني سأكتب at. لا أعرف إن كان ذلك يعمل. ثم end_date، صحيح، هو التالي. إذن هذا هو 4:00 مساءً، صحيح، هكذا. يمكننا فعل ذلك. وبعد ذلك ماذا أضع أيضًا؟ أم هل أضع أي شيء آخر؟

أحد الحضور: إذا أردت [غير مسموع] السعرات [غير مسموع].

آنا بيل: بالضبط، نعم، في هذه الحالة بالتحديد، سأتركه يأخذ القيمة الافتراضية None. ثم كيف أحصل على عدد السعرات الحرارية المحروقة لهذا الكائن، لكائن Workout هذا؟ كيف أطبع ذلك؟

أحد الحضور: ألا تريدين ذلك [غير مسموع]؟

آنا بيل: نعم، إذن أستدعي فقط طريقة get_calories على w_one، ولنضع عبارة print حول ذلك، هكذا. نعم.

صفحة المصدر 10

أحد الحضور: عندي، حين أفعل ذلك— أوه، لحظة. دعيني أفعل ذلك.

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

أحد الحضور: ألا تمرّرين عدد السعرات [غير مسموع]؟

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

حسنًا، هل الجميع مرتاحون مع هذا؟ كلنا نفهم طريقة get_calories، ممتاز. إذن انتهينا هنا من طريقتنا المحسّنة. رأينا طريقة get_calories الأفضل هذه، وهي طريقة لطيفة جدًا تسمح بالتقدير. ورأينا قليلًا عن استخدام كائنات datetime هذه.

حسنًا، المحطة التالية إذن، ستكون بقية هذه المحاضرة تنفيذ صنف فرعي (Subclass) واحد من هذه الحالة، من كائن التمرين هذا، يُسمّى صنف RunWorkout. وسنستخدم فكرة التسلسلات الهرمية (Hierarchies) ووراثة الأصناف للقيام بذلك. فلنتذكر قليلًا عن التسلسلات الهرمية في سياق Python.

حين ننشئ صنفًا نعرف أنه سيكون هذا الصنف الأب (Parent Class)، فهو صنف أساسي (Base Class). إنه الشيء الأكثر أساسية الذي نود الانطلاق منه. نسمّي ذلك الصنف الأب أو الصنف الأعلى (Superclass)، ويمكن أن يكون لهذا الصنف الأب الواحد كثير من الأصناف الفرعية المرتبطة به.

في هذه الحالة بالتحديد، على سبيل المثال فقط، يمكن أن يكون لدينا نوعان من التمارين، تمرين في الخارج، ونوع واحد هو تمرين في الخارج، والنوع الآخر تمرين في الداخل. وكلاهما من Workout. إذن كل ما يمتلكه Workout وكل ما يمكننا فعله باستخدام Workout سيوجد في التمرين الخارجي والتمرين الداخلي، وسنكون قادرين على فعله باستخدام التمرين الخارجي والتمرين الداخلي.

إذن الصنف الابن (Child Class) هو صنف أب. والصنف الفرعي هو صنف أعلى. لكن هذه الأصناف الفرعية يمكن أن تجلب بعض «الأفكار» الخاصة بها، بين علامتي اقتباس، أي سماتها الخاصة. بالنسبة إلى تمرين خارجي، قد نضيف معلومات أكثر، أي نضيف سمات أكثر، ربما الموقع، شيء من هذا النوع. وبالنسبة إلى تمرين داخلي، قد لا تحتاجون إلى موقع. وقد لا تضيفون أي سمات بيانات إضافية.

قد نضيف سلوكيات أكثر. بالنسبة إلى تمرين خارجي، لا أعرف، تضيفون سلوكًا مختلفًا عن مجرد تمرين عادي. والأمر نفسه للتمرين الداخلي، أو قد تعيدون تعريف السلوكيات (Override Behaviors). أي قد تغيّرون شيئًا يفعله Workout ليكون مخصوصًا بتمرين خارجي.

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

صفحة المصدر 11

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

حسنًا، لنتذكر هذا المثال هنا عن الخصائص المشتركة التي تمتلكها جميع تماريننا. هذا أساسًا تنفيذنا للصنف الأعلى Workout. أعرف أننا نفّذنا كل هذه، لكن نظريًا، يمكننا تنفيذ جميع الأشياء المظللة بالأصفر في صنفنا الأب. وهذه مشتركة مهما كانت أنواع التمارين التي ننشئها.

الآن، في ملف Python، لدي في الواقع صنف فرعي للسباحة. لن نستعرض الصنف الفرعي للسباحة في المحاضرة، لكن لا تترددوا في استعراضه في ملف Python الخاص بهذه المحاضرة. وأظن أنكم ستعملون معه أيضًا في حصة المراجعة (Recitation) يوم الجمعة.

في المحاضرة سننشئ صنفًا فرعيًا مخصوصًا للجري. وسيرث صنف الجري هذا بالطبع جميع هذه الخصائص التي يمتلكها صنف التمرين الأب لدينا، لكننا سنضيف أيضًا بياناتنا الخاصة. وسنعيد تعريف بعض البيانات التي يمتلكها صنف التمرين وأشياء من هذا النوع.

بالتحديد، الشيء الوحيد الذي سننفّذه فعلًا ويختلف عن Workout عادي، هو إضافة سمة الارتفاع (Elevation). إذن بالإضافة إلى ذلك، ستوجد start_time وend_time والسعرات والأيقونة أيضًا، وسيوجد نوع التمرين أيضًا. تلك هي سمات البيانات الخمس لدينا من Workout. وسنضيف الارتفاع لتمرين الجري ليصبح العدد ستًا.

حسنًا، كان هذا صنفنا الأب، وللتذكير فقط، هذا ما كان يبدو عليه. كانت لدينا سمة الصنف هنا، متغير الصنف، وطريقة init هذه هنا. الصنف Workout، كان أبوه هو كائن Python العام object. الآن، حين ننشئ RunWorkout، سيكون أبونا هو الصنف Workout. إذن لا نريده أن يكون مجرد كائن Python. نريده أن يكون كائن Workout.

وبمجرد أن نفعل ذلك داخل شيفرتنا، تقول Python: حسنًا، دعوني أحصل على كل هذه الأشياء، كل ما هو معرّف داخل صنفكم الأب هذا، Workout. و«أنسخه وألصقه في هذا الصنف»، بين علامتي اقتباس. إذن من البداية مباشرة، لدينا جميع الأشياء التي يمتلكها Workout.

لكن ذلك لا يعمل تمامًا مع RunWorkout لدينا. لأننا حين ننشئ RunWorkout، ومرة أخرى، هذا قرار تصميمي، نود أن يكون المستخدم قادرًا على تمرير معامل إضافي هنا، وهو قيمة الارتفاع. لذلك بالإضافة إلى start_time وend_time وcalories_burned، سأُدرج قيمة الارتفاع هذه مباشرة قبل معامل calories_burned.

إذن حين أهيّئ RunWorkout، يمكنني نظريًا أن أمرّر أربع قيم: سلسلة، سلسلة، عدد، عدد. أو، بما أن للارتفاع معاملًا افتراضيًا، وللسعرات معاملًا افتراضيًا، يمكنني تمرير سلسلة، سلسلة فقط، وسيأخذ الآخران القيم الافتراضية.

ماذا تفعل طريقة init هذه إذن؟ إنها تستدعي الدالة super(). أعرف أننا لم نفعل هذا من قبل، لكنني أردت فقط أن أريكم هذا. هذه طريقة أخرى لتطلبوا من Python أن تخبركم من هو أبوكم. لذلك حين تشغّلون الدالة super() هذه داخل تعريف صنف، فإن Python تُرجع فعليًا— أي البديل الذي يحل محل هذه الدالة، هذه مجرد دالة لها قيمة إرجاع. ستكون قيمة الإرجاع هي الشيء الموجود بين القوسين هنا.

صفحة المصدر 12

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

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

ثم الشيء الذي يمتلكه RunWorkout ولم يكن Workout يمتلكه إطلاقًا هو سمة بيانات الارتفاع هذه. لذلك أصبحت self.elev الآن سمة بيانات جديدة لم تكن موجودة في Workout العادي.

ثم لدي هاتان الطريقتان هنا في الأسفل، طريقة جلب للارتفاع وطريقة تعيين للارتفاع. لا شيء معقد فيهما، إنهما فقط تُرجعان وتعيّنان. نعم.

أحد الحضور: هل يمكننا إعادة كتابة super وinit [غير مسموع]؟

آنا بيل: نعم، إذن super وinit يستدعيان طريقة init الخاصة بالأب. والسبب الذي يجعلني أفعل ذلك هو أنني أعرف أن الأب يستطيع فقط تهيئة جميع تلك الأشياء من أجلي. لذلك أنا أستفيد فقط من حقيقة أنه يفعل كل تلك الأشياء من أجلي. يمكنكم تخيّل أن طريقة init ربما كانت تتحقق أيضًا من الأنواع، لتتأكد أن الشخص الذي أنشأ هذا، ليفرض أن start سلسلة نصية. وأن end سلسلة نصية. كل تلك الشيفرة الإضافية التي ستُنفّذ في طريقة init الخاصة بـWorkout، سنتركها تعمل باستخدام هذا السطر هنا. ولذلك لا يتعين علينا نسخها ولصقها. نعم.

أحد الحضور: هل علينا أيضًا أن نأخذ ذلك بترك السطر الذي في الأعلى موجودًا في [غير مسموع]؟

آنا بيل: نعم، تقصد هذين؟

أحد الحضور: الـ[غير مسموع] بحيث [غير مسموع]—

آنا بيل: نعم.

أحد الحضور: —يحتاج إلى أن يحتوي على start والسعرات. وsuper يحتاج إلى أن يحتوي على الأقل على start وend والسعرات؟

آنا بيل: نعم، بالضبط، في الواقع، نعم، أنت محق. إذن سبب عمل هذا هو أن طريقة init الخاصة بـWorkout لدي تستقبل البداية والنهاية والسعرات. إذا قلت إن طريقة init الخاصة بـRunWorkout لا تحتاج إلى start وend أو شيء كهذا، فلن أتمكن من تشغيل طريقة init هذه، بالضبط. أو ربما سأشغّلها باستخدام بعض القيم الافتراضية أو شيء من هذا القبيل، مثل، إذا أردت تشغيلها بالفعل، فعليك أن تمرّر لها— ما زال عليك اتباع الهيكل الأولي لطريقة init تلك، صحيح، نعم، هذه نقطة ممتازة. نعم.

أحد الحضور: [غير مسموع] قلتِ، أشعر أنكِ سابقًا كنتِ تمرّرين init الخاصة بالأب إلى هذه دون الحاجة إلى كتابة شيء super.

آنا بيل: نعم، إذن أنت محق. لم أكن أكتب شيء super. كنت أسمّي الأب مباشرة فقط. في مثال الحيوان قلت Animal.__init__. وفي هذه الحالة بالتحديد، أنا أريكم فقط طريقة مختلفة للقيام بذلك. ربما لا تعرف من هو أبوك؟ في تلك الحالة، يمكنك تشغيل هذه الدالة فحسب، فتخبرك من هو أبوك. لكن هذا السطر سيعمل بالقدر نفسه إذا قلت Workout، اكتب هذا الشيء، ثم نقطة ثم شرطتين سفليتين ثم init ثم شرطتين سفليتين. وسيكون ذلك مطابقًا تمامًا لما فعلته في المحاضرة الماضية.

صفحة المصدر 13

لننظر إذن إلى قاموس الحالة لهذا، لهذه الإضافة الجديدة هنا. هذا هو قاموس حالة صنفي Workout القديم العادي فقط. رأينا هذا من قبل. إنه جميع طرائق الجلب، وجميع طرائق التعيين، وinit، وcal_per_hour هذا من صنف Workout الجديد والمحسّن.

والآن، صنفي الفرعي، RunWorkout، تقول طريقة super() أساسًا: مهلًا، أنت Workout. طريقة super()، سيكون في قاموس الحالة الخاص به أيضًا طريقة الجلب هذه وطريقة التعيين هذه، أي هاتان الطريقتان. نحن لا ننسخ جميع هذه الأشياء من جديد هنا في الأسفل داخل قاموس حالة RunWorkout، لأن ذلك موجود بالفعل هنا في الأعلى. لكن بالإضافة إلى ذلك، يمتلك RunWorkout طريقة get_elevation وطريقة set_elevation هاتين. وهذه هي الأشياء الوحيدة التي عرّفناها في هذا الصنف.

ثم حين أنشئ نسخة من RunWorkout، هذه إذن نسخة من RunWorkout وليست من Workout. تشير Python إلى صنف RunWorkout هذا. وستكون سمات البيانات لنسخة RunWorkout بالطبع جميع سمات البيانات الخاصة بـWorkout عادي، أي تلك الخمس، بالإضافة إلى الارتفاع. سمة البيانات الجديدة التي أضفتها للتو.

إذن نستخدم الوراثة في هذه الحالة بالتحديد بروح الوحدوية، وبروح التجريد، وبروح كتابة شيفرة يمكن إعادة استخدامها، ويمكن قراءتها، ويمكن فهمها في المستقبل. لو كنا نكتب RunWorkout أساسًا بنسخ كل شيء ولصقه فيه من جديد، لكان صنفًا طويلًا جدًا، يكون معظمه مجرد نسخ ولصق من Workout.

والآن، إذا عرّفناه بهذه الطريقة، يمكننا بسهولة رؤية الوظائف الجديدة وسمات البيانات الجديدة التي يمتلكها RunWorkout، بالإضافة إلى مجرد كونه كائن Workout. إذن جميع تلك الأشياء الجيدة لكتابة شيفرة لطيفة وواضحة جدًا.

حسنًا، سننظر الآن في طريقة يُعاد استخدامها من أبينا. وهي طريقة الشرطتين السفليتين وstr هذه، أي __str__. طريقة str هذه تبدو كأنها وحش. إنها طويلة جدًا جدًا، لكنني أعدكم أنها ليست سيئة إلى هذا الحد.

إذن طريقة str هذه، لنتذكر ما تفعله. إنها تخبر Python بما يجب أن تفعله حين تطبع كائنًا من النوع Workout، لأنها معرّفة داخل صنف Workout هنا. سأريكم إذن كيف تبدو فعلًا في صنف Workout الفعلي. هذا هو Workout لدي. هناك متغير الصنف، وinit، وطرائق الجلب وطرائق التعيين، وكل ذلك، ثم هنا طريقة str. إنها طويلة، أليس كذلك؟ تشغل صفحة كاملة.

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

وستفعل الآتي. يجب على طريقة str أن تُرجع سلسلة نصية. إنها لا تطبع السلسلة. هذا تمييز مهم جدًا. إنها تُرجع سلسلة ستُطبع في النهاية حين تستدعي طريقة print. إذن الشيء الذي أفعله خلال هذه الطريقة كلها هو أساسًا بناء السلسلة الخاصة بي لإرجاعها. سجل الإرجاع هو سلسلة الإرجاع. وأنا أبنيها فقط عن طريق وصلها (Concatenation) قطعة قطعة بمزيد ومزيد من الأشياء التي أريد طباعتها في النهاية.

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

صفحة المصدر 14

الجزء التالي هنا، تلاحظون أنه يجلب سمة بيانات الأيقونة، ويضعها هنا على سطر مع خط رأسي، ومجموعة من المسافات، وخط رأسي. الجزء التالي هنا يطبع نوع التمرين بالوصول إلى سمة البيانات kind. أي workout أو running أو swimming، مهما تكن تلك السلسلة. يطبعها مباشرة تحت الرمز التعبيري.

الجزء التالي هنا يركّب المدة. تذكروا إذن حين عملنا مع كائن datetime، هنا بالضبط، عندما كنا نطبع المدة، حيث طرحنا ببساطة وقت البداية من وقت النهاية، كان يبدو هكذا. أنا راضية تمامًا عن ذلك. هذا يبدو لطيفًا جدًا. فلنستخدم ذلك فقط. إذن get_duration تُجري عملية الطرح فقط. إنها طريقة داخل صنفي Workout. ثم نواصل فقط تركيب ذلك في سلسلة الإرجاع الخاصة بنا.

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

يمكننا بعد ذلك إنشاء أي نوع من التمارين، لأن جميع الأصناف الأبناء ترث جميع الطرائق من الصنف الأب. لذلك بالطبع سترث جميع هذه الأصناف الأبناء طريقة str الخاصة بـWorkout. إذن مهما كان نوع التمرين الذي أنشئه، هنا أنشئ مجرد تمرين عادي، وهنا تمرين جري، وهنا تمرين سباحة. مهما كانت كيفية إنشائه، ستستخدم طريقة str نفسها.

إذن جميع هذه ستطبعه بهذا التنسيق اللطيف جدًا. سيكون الجزء الأول مخصوصًا بنوع التمرين الذي لدينا. ستختلف الرموز التعبيرية الصغيرة لأنني عيّنتها بشكل منفصل داخل الأصناف الفرعية. والنوع كذلك، أي التسمية workout أو running أو swimming. ستُحسب calories_burned والمدة باستخدام طريقة get_calories ثم طريقة get_duration. إذن مرة أخرى، بطريقة متسقة ولطيفة.

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

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

وهذا يطرح السؤال: متى يمكننا استخدام نسخة من صنف، من صنف فرعي؟ يمكنكم استخدام نسخة من RunWorkout هذا في أي مكان يمكنكم فيه استخدام Workout، لأن طريقة تفكيري في الأمر، مرة أخرى، هي أن تقولوا: حسنًا، RunWorkout هو Workout، لذلك كل ما يمكنني فعله باستخدام Workout، ينبغي أن أكون قادرًا على فعله باستخدام RunWorkout أو SwimWorkout أو أي من الأصناف الفرعية.

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

صفحة المصدر 15

فلنفكر إذن في هاتين الدالتين المساعدتين (Helper Functions). هذه تحسب إجمالي السعرات إذا أُعطيت قائمة من التمارين. وهذه تحسب إجمالي الارتفاع إذا أُعطيت قائمة من التمارين. تبدو الشيفرة مشابهة جدًا في الحالتين. نحن فقط نكرّر عبر الحلقة، ونحصل على كل كائن Workout، ثم نستدعي get_calories أو get_elevation على كائن Workout ذاك.

إذن سيعطيني هذا عددًا، ثم أحتفظ فقط بمجموع تراكمي لإجمالي الارتفاع وإجمالي السعرات، وفي النهاية أُرجعه. ومرة أخرى، القائمة هنا مهمة. هذه كائنات Workout، وكائنات Workout.

ماذا لو كان لدي مجموعة من— لدي هنا كائنان من Workout وكائنان لتمرين الجري. كائنا Workout هذان مدتهما 30 دقيقة. لذلك باستخدام 200 سعرة في الساعة، ستكون هذه 100 سعرة لكل واحد منهما.

تمارين الجري هذه مدتها ساعتان، ولذلك ستكون— لا يهم ذلك في الواقع. عفوًا، عفوًا. تمارين الجري هذه مدتها ساعتان، ولذلك ستكون 400 سعرة. لأن هذه المعاملات هنا تقابل الارتفاع.

وهي تقابل الارتفاع لأنه إذا نظرنا إلى طريقة تعريف تمرين الجري هنا بالضبط، فهذا هو ترتيب المعاملات: وقت البداية، وقت النهاية، قيمة الارتفاع، السعرات. لذلك حين أمرّر معاملات، يجب أن تكون بذلك الترتيب. ولا أستطيع القفز بينها. إذا أردت أن يكون أحدها المتغير الافتراضي، فيجب أن يكون ذلك في النهاية.

في هذه الحالة بالتحديد، لدي تمرينا الجري هذان بقيمة 400 سعرة، لأنني افتراضيًا لم أمرّر عدد السعرات فعلًا. ثم الارتفاع هو 100 و200. نعم.

أحد الحضور: ماذا لو أردنا الارتفاع الافتراضي، والسعرات، هل نضع فاصلة، فاصلة؟

آنا بيل: إذا أردت أن يأخذ كلاهما القيمة الافتراضية، فلن تضع شيئًا فقط. لا يمكنك ترك فاصلة فارغة فحسب.

أحد الحضور: إذا أردت الارتفاع الافتراضي و[غير مسموع]

آنا بيل: نعم، عندئذ سيتعين عليك أن تقول فعلًا وبشكل صريح، مثلًا calories يساوي أي قيمة تريدها. لذلك عند تلك النقطة— نعم، الآن بعد أن أصبحنا نعمل مع المتغيرات الافتراضية، تصبح الأمور أصعب قليلًا. لن تخطئ إذا قلت فقط مثلًا elev يساوي ما تريد أن يكون عليه، وcalories يساوي ما تريد. ثم يمكنك فعل ما ترغب فيه في تلك الحالة.

لكن في هذه الحالة بالتحديد، نعرف أن تماريننا تساوي 400، والارتفاع هو تلك القيم. لذلك حين نشغّل إجمالي السعرات على جميع التمارين، مهما كان نوع التمرين الذي لدي، لا يهم ذلك في الواقع لأن Python ستحصل ببساطة على السعرات لجميع أنواع التمارين هذه. إذن تجمع ذلك فقط.

أما الارتفاع، فإذا شغّلته على تمارين الجري فقط، فستعرف Python ما يجب أن تفعله. هنا 100 و200، لأن تمارين الجري تلك تمتلك سمة بيانات للارتفاع. لكن إذا طلبت الارتفاع لتمرين جري وتمرين عادي فحسب، فستُخرج Python خطأ، لأنها ما إن ترى هذا التمرين الأول، حتى تقول: حسنًا، ما هي طريقة workout.get_elev؟ وستقول: ليس لدي طريقة get_elev لتمرين عادي. هذا ليس شيئًا أعرفه. لأن ذلك شيء نفّذناه في الصنف الابن.

لنستعرض هذه إذن معًا. وليس هناك في الواقع شيء لنبرمجه، بل فقط لنشغّله. إنه هنا في الأسفل فقط. هذا مجرد نوع من التأكد من أنكم تفهمون ترتيب العمليات. وأظن أن أحد الأمثلة الذي كان محل سؤال هنا، حيث مرّرنا عدد السعرات فعلًا، موجود في النهاية.

صفحة المصدر 16

إذن حين أنشئ تمرينًا عاديًا— أوه، دعوني فقط أُزل ذلك هنا— ما هي القيمة حين أطلب من Python أن تخبرني بالسعرات لهذا التمرين؟ عند 200 سعرة في الساعة، ما القيمة هنا؟ قولوا الإجابة بصوت عالٍ فقط.

أحد الحضور: [غير مسموع]

آنا بيل: 30 دقيقة؟ 100، صحيح؟ ما الارتفاع حين أطلب من Python أن تخبرني بالارتفاع لهذا الكائن؟

أحد الحضور: خطأ.

آنا بيل: خطأ، بالضبط، نعم. نعم، لأن كائن Workout ليس لديه سمة. وليس لديه طريقة get_elev. هذا شيء مخصوص بتمرين الجري. حسنًا، ماذا عن هذا، RunWorkout هنا؟ هنا أنا في الواقع— أوه، لم أقصد فعل ذلك. ماذا يحدث إذا حصلت على السعرات لـw2؟

أحد الحضور: 450.

آنا بيل: نعم، 450، نعم، إنه يحصل فقط على أي قيمة مُرّرت، ولا يقدّر. ماذا عن الارتفاع؟

أحد الحضور: 70.

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

أحد الحضور: الارتفاع.

آنا بيل: نعم، الارتفاع، تذكروا قائمة معاملاتنا. يأتي الارتفاع قبل السعرات. إذن الارتفاع هو 250، وستُقدّر السعرات بناءً على ما هو هذا. إذن السعرات أولًا تساوي 100. والارتفاع 250. ماذا عن تمرين الجري الثاني؟ لدي هنا 450 و700 بذلك الترتيب. أيهما الارتفاع؟

أحد الحضور: 450.

آنا بيل: نعم، 450، والسعرات 700. لذلك حين نطبع، طبعتهما بالعكس فقط لأربكنا جميعًا. وأخيرًا، ماذا عن RunWorkout الثالث هذا؟ هنا، للإجابة عن سؤال: ماذا لو أردت أن يأخذ الارتفاع القيمة الافتراضية، لكنني أردت تمرير السعرات؟ أقول هنا فقط اسم المعامل هناك، وأعطيه قيمة فعلية. إذن من الواضح هنا أن السعرات ستكون 300، وأن الارتفاع يأخذ افتراضيًا صفرًا. مجرد تدريب صغير على قراءة المواصفات (Specifications).

حسنًا، بهذا ننتهي من إعادة استخدام طريقة str من الأب. والآن لنعِد تعريف صنفنا الأعلى. تذكروا أن صنف Workout المحسّن لديه طريقة get_calories التي تقدّر السعرات بناءً على الوقت الذي استغرقه أداؤكم لهذا التمرين، سواء كان تمرين جري أم تمرينًا عاديًا.

لكن ما أفعله في هذه الطريقة هو أنني سأنفّذ في الواقع طريقة get_calories الخاصة بي داخل تعريف الصنف RunWorkout. هذا إذن تعريف صنفي RunWorkout. ولدي طريقة get_calories الخاصة بي. لذلك حين أشغّل get_calories على RunWorkout، ستستخدم Python هذه. ماذا ستفعل هذه؟

صفحة المصدر 17

سنفعل إذن شيئًا رائعًا حقًا. سنقدّر عدد السعرات الحرارية المحروقة لتمرين جري اعتمادًا على مجموعة من النقاط، نقاط خط العرض وخط الطول (Latitude and Longitude). ما يمكننا فعله في الواقع هو أننا سنمرّر قائمة من الصفوف (Tuples) مثل هذه، تمثل المسار الذي نسلكه. في هذه الحالة بالتحديد، لدي أربعة أماكن هرولت بينها. إذن هذه هي نقاط خط العرض وخط الطول الأربع لدي. وكل صف هو خط عرض، ثم فاصلة، ثم خط طول.

يمكنني إذن جعل هذا دقيقًا بالقدر الذي أريده. لكن ما أريد أن تفعله هذه الطريقة هو، ربما، إذا أعطاني المستخدم بالفعل مجموعة من نقاط خط العرض وخط الطول التي مر بها فعلًا، أن تحسب السعرات الحرارية المحروقة اعتمادًا على متغير صنف يُسمّى calories_per_kilometer.

إذا أُعطيت مجموعة من هذه النقاط، فما أود فعله هو حساب إجمالي الكيلومترات التي قُطعت بين جميع نقاط خط العرض وخط الطول هذه، وضرب تلك المسافة، تلك المسافة بالكيلومترات، في calories_per_kilometer. واستخدام ذلك بوصفه تقديرًا للسعرات الحرارية المحروقة في RunWorkout هذا بالتحديد.

إذن هذه هي كيفية تحقيق الشيفرة لذلك. لدي متغير صنف آخر مخصوص بـRunWorkout هذا وحده. إذن Workout لا يعرف عن هذا. Calories_per_kilometer يساوي 100. والآن لدي طريقة get_calories الخاصة بي هنا. أُعيد تعريفها. لذلك إذا شغّلنا get_calories هذه على RunWorkout، فسيستخدم هذه. وماذا تفعل؟

إذا لم نعطه أي نقاط GPS، إذا لم نعطه قائمة من الصفوف هناك، فستنتقل Python افتراضيًا إلى else. ماذا تفعل else؟ تقول: مهلًا، من أبوك؟ شغّل طريقة get_calories الخاصة به. وهذا مجرد تقدير اعتمادًا على إجمالي الوقت المنقضي في هذا التمرين. هذا هو أبونا الافتراضي.

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

دعوني أريكم إذن كيف يبدو هذا فعلًا، لأن الشيء الوحيد الذي ما زال غامضًا نوعًا ما هنا هو مسافة GPS هذه. ومسافة GPS هذه هي في الواقع دالة موجودة في ملف مساعدات هذه المحاضرة، الذي يتضمنه ملف Python المضغوط بصيغة zip الخاص باليوم.

وهي فقط من الإنترنت. إنها طريقة لحساب المسافة المقطوعة بين زوجين من خط العرض وخط الطول. هذا كل ما هي عليه. تقوم ببعض الأشياء المتقدمة باستخدام الجيب وجيب التمام (Sines and Cosines) وأشياء من هذا النوع لمعرفة المسافة بين زوجي خط العرض وخط الطول هذين. هذا كل ما هي عليه.

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

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

صفحة المصدر 18

لكن إذا أنشأت تمرين جري هنا مع وقت البداية ووقت النهاية وقيمة الارتفاع، والآن أمرّر نقاط GPS للمسار، فهذا جزء آخر من طريقة init لدي. نسيت أن أريكم ذلك. آسفة على ذلك. إذن هنا طريقة init الخاصة بـRunWorkout. تجاوزت ذلك الجزء الصغير. المعامل الأخير هنا هو لتمرير بعض نقاط GPS للمسار فعلًا.

وإذا مرّرت نقاط GPS تلك للمسار بالفعل، فعندما أشغّل طريقة get_calories، تخبرني أنني أحرقت هذا المقدار. وتحسبه اعتمادًا على تلك المسافة بين بوسطن ونيوتن.

في المثال الثاني هنا، لا أمرّر قيمة نقاط GPS في الواقع، لذلك نعود افتراضيًا إلى دالة السعرات العادية لدينا من Workout، التي تحسبها بناءً على وقت البداية ووقت النهاية. أي من 1:35 إلى 3:57. لهذا يكون عدد السعرات غريبًا وغير مستدير. لذلك أظن أن هذا أيضًا رائع حقًا، يا جماعة، هذه الدالة هنا.

حسنًا، بالنسبة إلى هذه الطرائق المعاد تعريفها، فقط لأريكم كيف يبدو صنف RunWorkout هذا من باب الاكتمال، فكل شيء هو نفسه كما وصلنا إليه من قبل. لكن الآن سأعيد تنفيذ طريقة get_calories لدي، فأصبح RunWorkout يعرف الآن طريقة للسعرات. ولدي أيضًا سمة البيانات هذه. عفوًا، متغير الصنف، عفوًا، دائمًا ما أخلط بينهما. متغير الصنف هذا cals_per_kilometer. وستعرف أي نسخة من RunWorkout بالطبع cals_per_kilometer، وكذلك cal_per_hour من أبينا. هل هناك أسئلة عن ذلك؟

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

إذا كان النوع— مثلًا get_calories— إذا كان النوع هو الجري، تنظرون لتروا ما إذا كان تعريف ذلك الصنف يمتلك طريقة get_calories. إذا كان يمتلكها، تستخدمون تلك. وإذا لم يكن يمتلكها، وفقط إذا لم يكن يمتلكها، تنظرون إلى أبيكم وتقولون: هل يمتلك أبوكم طريقة get_calories؟ إذا كان يمتلكها، تستخدمون تلك.

وإذا لم يكن يمتلكها، تنظرون إلى أب الأب. هل يمتلك أب الأب طريقة get_calories؟ إذا كان يمتلكها، تستخدمون تلك. وإذا لم يكن يمتلكها، تنظرون إلى أب أب الأب، صعودًا إلى الأعلى، وتواصلون الصعود إلى أعلى السلسلة حتى تصلوا إلى كائن Python العام object. إذا كان نوع كائن Python يمتلك طريقة باسم ما تودّون استدعاءه، تستخدمون تلك، وإلا فخطأ، لم يُعثر على طريقة كهذه في أي مكان ضمن سلسلة التسلسلات الهرمية لدينا حتى كائن Python.

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

إذن الصنف Workout، لنقل إننا نريد مقارنة تمرينين معًا. للقيام بذلك، سننفّذ طريقة داندر: شرطتان سفليتان، eq، شرطتان سفليتان، أي __eq__. وهذا سيتيح لنا مقارنة كائني تمرين جري، أو كائني Workout، أو كائن جري وكائن Workout، باستخدام علامة المساواة المزدوجة. أي w1 == run w2 أو أي شيء.

يمكننا إذن استخدام علامة المساواة المزدوجة لمقارنة كائنات من نوعنا. ومرة أخرى، قراري بشأن مقارنة هذين الكائنين، كائني Workout، هو أن أقول: حسنًا، أولًا، لنقارن الأنواع. إذا كنت أقارن تمرينًا بتمرين جري، فلن يكونا متساويين منذ البداية مباشرة.

صفحة المصدر 19

إذن أولًا، ينبغي أن يكون نوع هذا الكائن هو نفسه. يجب أن أقارن تمارين بتمارين، وتمارين جري بتمارين جري، أو تمارين سباحة بتمارين سباحة. وأريد أيضًا أن تكون كل واحدة من سمات البيانات الأخرى هي نفسها. أي وقت البداية، وأوقات النهاية، والنوع، وجلب السعرات. ما دامت كل هذه الأشياء هي نفسها، سأقول إن هذه التمارين هي نفسها أو متكافئة.

إذن هذه طريقة المساواة في تمريني، ثم في صنفي Workout، يمكنني في الواقع إعادة تعريف تلك الطريقة. إذن ينبغي أن يكون هذا في الواقع «إضافة، إعادة تعريف»، تمامًا مثل الأخرى. ثم في RunWorkout، سأعيد تعريف طريقة المساواة، لكنني سأفعل ذلك بطريقة وحدوية جدًا وعلى أسلوب Python (Pythonic).

سأقول إن RunWorkout سيكون مثل Workout آخر إذا كان كل شيء في أبي متساويًا. هنا أستدعي فقط طريقة super()، وأقول Workout ثم نقطة ثم شرطتين سفليتين ثم مساواة ثم شرطتين سفليتين، ثم other. إذن بهذا الجزء الصغير هنا، هذا السطر هنا، مجرد super() ثم نقطة ثم شرطتين سفليتين ثم مساواة ثم شرطتين سفليتين ثم other، يقارن هذا كل هذه الأشياء. لذلك لا أحتاج إلى إعادة كتابة ذلك في طريقة eq الخاصة بـRunWorkout.

ويمكنني أن أرى بوضوح ما الذي أحتاج إلى حدوثه أيضًا، بالإضافة إلى مقارنة Workout العادية، لكي يكونا متساويين. أريد أيضًا أن تكون قيمتا الارتفاع متساويتين. تلك هي سمة البيانات الأخرى.

يمكنكم الآن أن تروا كم تبدو هذه الشيفرة وحدوية بطريقة لطيفة. من الواضح جدًا في هذه الشريحة ما الذي يميّز RunWorkout عن Workout عادي. هل هناك أسئلة عن هذا؟ حسنًا.

أحد الحضور: هل ينبغي [غير مسموع] أن يستمر إلى النهاية.

آنا بيل: نعم، بالضبط، نعم، إذن ينبغي أن يكون كل هذا في سطر واحد. لكن الشرطة المائلة العكسية (Backslash) تقسّم السطر في الواقع إلى أسطر متعددة لتسهيل الرؤية. في الشيفرة إذن، هذه مجموعة من التمارين. ويمكننا تشغيل بعض منها. لكن يمكنكم أن تروا لماذا تكون True أو False.

هنا إذن، w1 وw2 ليسا متساويين لأن calories_burned مختلفة. كلاهما تمرينان عاديان. ولهما وقتا البداية والنهاية نفسهما، لكن calories_burned مختلفة. لذلك يطبع هذا False، مجرد المساواة على هذه التمارين.

ثم هنا حالة تكون نتيجتها True. w2 مكافئ لـw3 لأن وقتي البداية والنهاية هما نفسهما، والمدة هي نفسها، وcalories_burned هي نفسها، w1 وw3. أو عفوًا، w2 وw3، هذان الاثنان، حسنًا. هذا استخدم القيمة الافتراضية فقط، لكن تلك القيمة الافتراضية المحسوبة حُسبت لتكون 100، لأنه تمرين مدته 30 دقيقة على أي حال. إذن يمكنكم استعراض بعض الأمثلة الأخرى بأنفسكم.

أظن أن المثال المثير للاهتمام الآخر هو هذا، w1 مع rw1. كل شيء في هذا هو نفسه، السعرات المحروقة. كل شيء هو نفسه، باستثناء حقيقة أنهما نوعان مختلفان من التمارين. أحدهما جري. والآخر عادي. لذلك نشغّل ذلك فتكون النتيجة False. أسئلة أخرى، أو أي أسئلة؟

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

أضفنا طريقة لتقدير السعرات. وأضفنا طريقة لتقدير السعرات باستخدام نقاط GPS، وفعلنا ذلك تدريجيًا فقط. إذن لا تريدون فعل ذلك فورًا من البداية. اكتبوا قليلًا في كل مرة فقط، وفي النهاية يمكنكم كتابة نوع كائن رائع حقًا.

صفحة المصدر 20

الآن بعد أن عرفتم كيف تنشئون أنواع كائناتكم الخاصة، يمكنكم إنشاء كائنات باستخدام كائنات أخرى. إذن بعض سمات البيانات لشيء أكثر تعقيدًا يمكن أن تكون كائن Workout، شيئًا من هذا النوع. لكن من الممكن المبالغة في ذلك. خصوصًا الآن، بما أننا لا نكتب أصنافًا فائقة التعقيد، فمن الممكن الإفراط في الهندسة (Overengineering).

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

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

حسنًا، في المجموعة التالية من المحاضرات، سنترك البرمجة قليلًا. وسننظر في معرفة كيفية كتابة برامج كفؤة (Efficient Programs)، وكيفية معرفة ما إذا كانت برامجنا كفؤة أم لا، وأشياء من هذا النوع. إذن سننتقل إلى جانب أكثر نظرية من علوم الحاسوب.