النص الكامل للمحاضرة 17: أصناف Python
النص الكامل للمحاضرة 17: أصناف Python
المصدر الأصلي: النص الرسمي الإنجليزي، PDF، صفحة المحاضرة، صفحة الفيديو الرسمي.
النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة وتكييف عربي غير رسمي، لا تعتمده MIT ولا يعني نشره تأييدها له. الاستخدام غير تجاري، وتُنشر الترجمة والمواد الأصلية المشمولة وفق رخصة CC BY-NC-SA 4.0، مع وجوب المشاركة بالمثل. لا تشمل الرخصة الافتراضية مواد الأطراف الثالثة المستثناة.
منهج الترجمة: تتبع الترجمة فقرات النص المستخرج من PDF الرسمي بالترتيب، لا ملخصات الشرائح، مع التكرار والتردد والتصحيحات الشفهية ومداخلات الحضور والإشارات الصوتية. تتبع عناوين «صفحة المصدر» الصفحات غير الفارغة المفصولة بمحرف فصل الصفحات في الاستخراج. تُحفظ أسماء الكود والتعبيرات؛ أما توضيحات المترجم فموسومة ومنفصلة، ولا يُصحَّح كلام المصدر صامتًا.
صفحة المصدر 1
MITOCW | المحاضرة 17 من 6.100L، الإصدار 2.mp4
[صرير] [حفيف] [نقرات]
آنا بيل (ANA BELL):
حسنًا، لنبدأ. اليوم سنبدأ مجموعة جديدة تمامًا من الموضوعات. وسنتحدث عن هذه الموضوعات في المحاضرات الأربع القادمة. وهو موضوع كبير. الفكرة الكبيرة التي نحاول تحقيقها في هذه المحاضرات الأربع القادمة هي أن نبدأ تعريف أنواع الكائنات (Object Types) الخاصة بنا. وسنعرّف هذه الأنواع عبر أشياء تسمّى أصناف Python (Python Classes). في محاضرة اليوم سنعطيكم فقط نوعًا — سنعرّف فقط نوع كائن بسيطًا حقًّا. ثم سنبني عليه بعد ذلك.
فلنرجع خطوة إلى الوراء ونفكّر في كائنات معينة، كائنات محددة جدًّا كنّا نعمل معها. مثلًا، ربما عملنا مع العدد 1234. وعملنا مع عدد الفاصلة العائمة (Float) باي (Pi)، 3.14159. وعملنا مع تسلسلات من المحارف مثل Hello، ومع قوائم من الأعداد. هذه قائمة فيها تلك العناصر المحددة. وعملنا مع القواميس (Dictionaries). وهذا قاموس محدد فيه هذه المدخلات.
كل واحد من هذه الأشياء هنا في الأعلى كائن (Object). لدينا في برنامجنا. يمكننا التعامل معه. ويمكننا إضافته إلى أشياء أخرى. ويمكننا فهرسته (Index). ويمكننا فعل كل هذه الأشياء. لكن كل واحد من هذه الكائنات له أساسًا نوع معين (Type). تحدثنا عن هذا في المحاضرة 1 عندما قدّمت أنواع الكائنات.
ماذا يعني هذا؟ في تلك المحاضرة، قلت إن نوع الكائن يخبرنا أساسًا بأنواع الأشياء التي نستطيع فعلها به. الأشياء التي يمكنكم فعلها بعدد ستكون مختلفة عن أنواع الأشياء التي يمكنكم فعلها بالسلاسل النصية (Strings). وقد رأينا هذا منذ تلك المحاضرة وحتى اليوم.
سنرى اليوم كيف نستطيع إنشاء أنواع الكائنات الخاصة بنا. ولفعل ذلك، علينا فهم الشيء التالي. وهذا شيء سأواصل تكراره اليوم. بمجرد أن تقرر إنشاء نوع كائن — كل واحد من هذه الكائنات، مثلًا، أُنشئ باستخدام مخطط (Blueprint) ما.
وعندما تنشئ هذه الكائنات، تحتاج إلى التفكير في شيئين. الأول: ما البيانات التي ستمثّل هذا الكائن؟ والثاني: ما السلوكيات (Behaviors) التي ستكون لهذا الكائن؟ الكائنات هنا في الأعلى بسيطة إلى حدٍّ ما، صحيح؟ أنواع البيانات التي تمثّل هذا العدد الصحيح (Integer) هي — حسنًا، لا توجد بيانات فعلًا. إنه العدد نفسه فقط. لكن لديه بعض العمليات (Operations)، بعض الأشياء التي يمكنك فعلها بهذا العدد الصحيح.
أما البيانات التي تمثّل قائمة (List)، فستكون مختلفة عن البيانات التي تمثّل عددًا صحيحًا، لأن القائمة مكوّنة نوعًا ما من تسلسل أعداد أو كائنات. ثم البيانات التي يتكوّن منها قاموس مختلفة تمامًا عن البيانات التي تتكوّن منها القائمة، لأن القاموس فيه مدخلات (Entries)، ولكل مدخل زوج من مفتاح وقيمة (Key-Value Pair). ثم لديك مجموعة من هذه المدخلات.
إذًا البيانات التي تمثّل كل واحد من هذه الكائنات مختلفة. وسنقرر ما البيانات التي تمثّل الكائنات الجديدة التي نريد إنشاءها. وبالطبع هذا شيء عرفناه منذ المحاضرة الأولى. أنواع الطرق التي يمكننا بها التعامل مع هذه الكائنات مختلفة أيضًا بين كل أنواع الكائنات المختلفة هذه.
ومن حيث المصطلحات، عندما ننشئ كائنًا فعليًّا نريد التعامل معه، نسمّيه مثيلًا لنوع (Instance of a Type). هذا العدد المحدد، 1234، مثيل لعدد صحيح. وهذا التسلسل المحدد من المحارف — hello بأحرف صغيرة — مثيل لسلسلة نصية.
صفحة المصدر 2
حسنًا، فكرة البرمجة كائنية التوجه (Object-Oriented Programming) أساسًا هي أن كل شيء في Python كائن. وقد تحدثنا عن هذا عندما قدّمنا الدوال (Functions). تعاملنا مع الدوال بوصفها كائنات. وما يعنيه ذلك هو أننا نستطيع إنشاء كائنات جديدة لها نوع ما. فننشئ بالفعل هذه الكائنات المحددة جدًّا التي يمكننا التعامل معها. ويمكننا أيضًا إتلاف الكائنات (Destroy). تستطيع إنشاءها والتعامل معها وإتلافها كما تريد. لكن لكل كائن منها نوع محدد.
لنتحدث قليلًا أكثر عن تجريد البيانات (Data Abstraction). بمجرد أن يكون لديك كائن تريد إنشاءه — فكّر في أي شيء في العالم، شيء ما — فالشيئان اللذان تحتاج إلى التفكير فيهما هما: ما الطريقة التي ستمثّل بها الكائن من حيث البيانات؟ والشيء الآخر: ما سلوكيات هذا الكائن؟ كيف يستطيع المبرمجون الآخرون أو الكائنات الأخرى التعامل مع هذا الشيء الذي تنشئه؟
عندما ننشئ نوع الكائن الخاص بنا، علينا التفكير في تجريدات البيانات هذه. فلنأخذ مثالًا أقرب إلى الحياة الواقعية. لنقل إن لدي هاتين السيارتين المحددتين جدًّا، الموجودتين فعلًا في العالم الحقيقي. يمكننا قيادة هاتين السيارتين بالفعل. ويمكننا التعامل معهما. لقد أُنشئتا بالفعل. إنهما كائنان فعليّان.
حسنًا، وراء الكواليس، أُنشئت هذه الكائنات باستخدام مخطط ما. هذا المخطط ليس شيئًا ملموسًا فعليًّا. إنه أساسًا تصور مجرد لكيفية إنشاء تلك الكائنات المحددة، تلك السيارات المحددة. وبينما نفكّر في إنشاء أنواع الكائنات الخاصة بنا، علينا التفكير في قرارات التصميم (Design Decisions).
إذا أردت إنشاء مخطط لسيارة يستطيع شخص بعد ذلك استخدامه لإنشاء سيارة فعلية في الحياة الواقعية، فكيف أجرّد السيارة؟ ونحن ننشئ هذه الكائنات بأنفسنا، نملك اتخاذ قرارات التصميم هذه، وهذا رائع إلى حدٍّ ما. لو كنت أنشئ هذه السيارة، مخطط سيارة، فسأقول: حسنًا، سأستخدم ربما طول السيارة وعرضها وارتفاعها ولونها. وهذه السمات الأربع للبيانات (Data Attributes) ستمثّل كائن سيارة.
لكن بالطبع هذا قرار التصميم الخاص بي. إذا كنت أكثر إلمامًا بالسيارات، أو أردت الدخول في وصف أو تمثيل أكثر تفصيلًا، فسيكون لديك أيضًا عدد يعبّر عن قوة محركها بالحصان، وعدد الأبواب، وربما عدد الأشخاص الذين تتسع لهم، وأشياء أخرى كهذه. لكن تجريد بيانات بسيطًا جدًّا للسيارة هو الطول والعرض والارتفاع واللون.
هذا هو تجريد البيانات، أي ما البيانات التي تمثّل هذا الكائن الذي تحاول إنشاءه. والآن، ماذا عن الواجهة (Interface)؟ من حيث الواجهة، نقرر ما بعض الطرق التي يستطيع المبرمجون بها التعامل مع الكائن، أو تستطيع الكائنات الأخرى بها التعامل مع هذا الكائن.
يمكننا أن نقول إننا نسمح للمستخدمين بتغيير لون السيارة. ويمكننا أن نقول إننا نسمح للسيارة بإصدار صوت. فقد يكون إطلاق البوق شيئًا واحدًا، دالة واحدة تستطيع هذه السيارة تنفيذها. وإذا قلنا أطلق البوق، فربما تطبع شيئًا على الشاشة، أو نحو ذلك.
ثم يمكننا جعل السيارة تسير من النقطة A إلى النقطة B. أو يمكننا جعلها تسير في دائرة. ويمكنك جعل السيارة تصطدم بسيارة أخرى. وكل هذه السلوكيات جزء من هذه الواجهة لهذه السيارة بالتحديد. لكننا سنعرّفها بحيث تكون لكل سيارة ننشئها من الآن فصاعدًا، لكل كائن فعلي ننشئه، كل هذه السلوكيات وكل هذه السمات البيانية.
صفحة المصدر 3
مثال أقرب إلينا قليلًا هو القائمة. كنّا نعمل مع القوائم حتى الآن. وراء الكواليس، أنشأ شخص نوع البيانات (Data Type) المسمّى قائمة. يوجد بعض الكود في Python يعرّف أساسًا البيانات التي تتكوّن منها القائمة، سمات البيانات — كيف تُوصَف القائمة — والسلوكيات والإجراءات (Procedures) والدوال التي تستطيع القائمة تنفيذها.
من حيث سمات البيانات، هناك قرارات تصميم كثيرة كان يستطيع من قرر إنشاء صنف القائمة هذا اتخاذها. كيف كان يمكن أن يمثّل القائمة؟ كان يمكنه أن يقول: سأخصّص نوعًا من كتل الذاكرة المتجاورة (Contiguous Block of Memory). وستأتي عناصرك بهذا الترتيب، من أصغر قيمة ذاكرة إلى أكبر قيمة ذاكرة. هذا قرار تصميم واحد.
وقد يكون قرار آخر هو أنه، بدلًا من تخصيص كتلة ذاكرة متجاورة، يمكنك القول: أستطيع تخصيص مواقع ذاكرة هنا وهناك. لا بأس بذلك. لكن كل عنصر في قائمتي سيمثَّل عندئذ بشيئين — الأول هو القيمة في ذلك الموقع، والثاني ربما عدد صحيح آخر أو شيء يخبر Python بأي موقع ذاكرة يذهب إليه للحصول على العنصر التالي في القائمة.
كلاهما إذًا قرار تصميم صالح — أظن أن Python اتبعت الثاني. هذه طريقة تمثيل البيانات التي تمثّل القائمة. ومن حيث السلوكيات، حسنًا، عملنا بالفعل مع القوائم. فنعرف مجموعة من السلوكيات التي تتمتع بها. يمكنك فهرستها. ويمكنك ترتيب قائمة (Sort). ويمكنك إلحاق عنصر بنهاية القائمة (Append).
ملاحظة المترجم: تقول المتحدثة إنها تظن أن Python تستخدم التمثيل الثاني. هذا تصور مذكور في المحاضرة، لا حقيقة نُضيفها هنا عن التنفيذ. في CPython، القائمة مصفوفة ديناميكية من مراجع الكائنات، وليست قائمة مترابطة بالشكل الثاني الموصوف.
يمكنك الحصول على أكبر عنصر داخل القائمة. كل هذه الإجراءات والدوال المختلفة أشياء تستطيع فعلها بالقوائم. وعملنا معها. وعملنا مع القوائم دون معرفة التمثيل فعلًا — كيف قرر شخص تمثيل هذا الصنف — وهذا رائع إلى حدٍّ ما.
إذًا، بضعة أمثلة أخرى من الحياة الواقعية — إذا فكّرنا في تمثيل كل واحد من هذه — إذا فكّرنا في كائن هو مصعد (Elevator)، فمرة أخرى يعود إلينا اتخاذ قرار التصميم. إنه أساسًا صندوق يستطيع تغيير الطوابق، صحيح؟ يمكننا تمثيله باستخدام الطول والعرض والارتفاع، وهي كلها أعداد فاصلة عائمة أو نحو ذلك. ويمكننا أيضًا تمثيله باستخدام سعته القصوى (Max Capacity) والطابق الحالي الذي يوجد فيه.
كل هذه المتغيرات الخمسة معًا، القيم معًا، تمثّل مصعدي. ومرة أخرى، هذا قرار التصميم الخاص بي في فعل ذلك. قد يكون قراركم مختلفًا. ومن حيث الأشياء التي يستطيع المصعد فعلها، يمكننا تغيير طابقه الحالي، وهو أساسًا القول: غيّر قيمة متغير الطابق الحالي إلى شيء آخر. وإضافة أشخاص إليه — ربما التحقق مما إذا كنتم عند السعة القصوى أم لا، وربما طباعة تحذير إذا كنتم فوقها — وإزالة أشخاص، وما شابه.
الموظف (Employee) أيضًا مثال شائع إلى حدٍّ ما لشيء يُنفَّذ عادة في مجموعة من لغات البرمجة. الموظف أساسًا شخص له راتب، ربما يعمل لدى الشركة X. يمكنكم تمثيل هذا الموظف باستخدام اسمه، ربما سلسلة نصية للاسم الأول، وسلسلة نصية لاسم العائلة، ثم تاريخ ميلاده ربما، ثم راتبه، وهو عدد فاصلة عائمة أو نحو ذلك.
ومن حيث السلوكيات، ماذا يستطيع الموظفون فعلُه؟ يمكنك تغيير اسمهم. ويمكنك تغيير راتبهم. ويمكنك ربما تفعيلهم أو تعطيلهم بوصفهم موظفين حاليين، وما شابه. طابور في متجر (Queue) مثال لطيف حقًّا أيضًا. وهو يسير نوعًا ما جنبًا إلى جنب مع رصّة فطائر (Stack of Pancakes).
صفحة المصدر 4
كيف تمثّلون طابورًا في متجر؟ حسنًا، لن يكون التمثيل مجموعة من الأشياء. قد يكون التمثيل شيئًا بسيطًا حقًّا، مثل قائمة فقط، وهذا مقبول. ربما تحتوي القائمة بعض السلاسل النصية بأسماء الأشخاص الموجودين حاليًّا في الطابور في متجر.
لكن ما سيجعل الطابور مميزًا نوعًا ما هو الطريقة التي سنستخدمه بها. فالتمثيل ليس فريدًا جدًّا. إنه قائمة فقط. لكن طريقة عمل الطابور ستكون مميزة، لأنك إذا فكّرت في الطابور، فأول شخص يدخل الطابور سيكون أول شخص يخرج منه — وضع من نوع الداخل أولًا يخرج أولًا (First In, First Out).
يعني هذا أنه إذا اتخذتم قرار التصميم بإضافة الأشخاص الجدد إلى نهاية الطابور — فإذا كان لدي شخص جديد يُضاف هنا، فهو أحدث شخص دخل. وهذا يعني أنه إذا كنت أُخرج شخصًا من الطابور، فالأفضل أن أُخرج الأقدم، الذي سيكون هناك في الأول، في بداية قائمتي.
طريقة استخدامكم للطابور ستكون متسقة مع هذه الفكرة. وبعد ذلك يمكنكم أساسًا محاكاة (Simulate) الطابور. ورصّة الفطائر مشابهة جدًّا. إذا فكّرتم في الفطائر، فإن أول واحدة صنعتموها هي آخر واحدة تأكلونها. فهو وضع من نوع الداخل أولًا يخرج أخيرًا (First In, Last Out). وهذا يعني أننا ما زلنا نستطيع تمثيل رصّة فطائر باستخدام قائمة.
التمثيل، تمثيل البيانات لرصّة فطائر، سيكون مثل الطابور، إلا أن السلوك سيكون مختلفًا، لأنني إذا صنعت فطيرة جديدة للتو وذهبت إلى النهاية هنا، فإن أحدث واحدة صنعتها هي أول واحدة سآكلها. إذا أضفت فطيرة إلى نهاية قائمتي، فسأُخرج الفطيرة التي أريد أكلها من نهاية قائمتي أيضًا.
حسنًا. فكرة البرمجة كائنية التوجه والسبب الذي يجعلنا نفعل هذا هو أننا نجمع الآن أساسًا البيانات والسلوكيات في شيء واحد. ونستطيع إنشاء كل هذه الكائنات التي لها النوع نفسه، والتي ستعمل كلها بالطريقة نفسها. نعرف أنها ستكون متسقة، صحيح؟ ستكون متسقة في البيانات التي تمثّلها، ومتسقة في الطريقة التي نستخدمها بها، صحيح. نعرف يقينًا أن الطابور سيكون وضعًا من نوع الداخل أولًا يخرج أولًا.
والطريقة التي سننفّذ بها هذا هي استخدام هذه الأشياء المسمّاة أصناف Python. والسبب الذي يجعلنا ننشئ أصناف Python هذه هو صنع كود قابل لإعادة الاستخدام (Reusable) بصورة جميلة جدًّا. يمكننا إنشاء أصناف Python بسيطة حقًّا، سنراها اليوم. ثم يمكننا البناء على هذه الأصناف لإنشاء أصناف أكثر تعقيدًا، سنراها يوم الأربعاء.
لكن الفكرة الكبرى هنا — وهذا شيء كنت مرتبكة قليلًا بشأنه عندما بدأت تعلّم البرمجة كائنية التوجه — هي أنك أنت المسؤول عن قرار التصميم. أنت تقرر ما البيانات التي تمثّل الصنف، وتقرر ما السلوكيات التي تمثّل الصنف.
فإذا أردت أن تقول إنك — تمثّل الطابور باستخدام قائمة — الداخل أولًا يخرج أولًا — إذا أضفت عناصر إلى النهاية، تُخرج عناصر من البداية. هذا قرار تصميم واحد. وقد يكون قرار تصميم آخر: حسنًا، ما زلت تمثّله قائمة، لكن تُضاف العناصر الجديدة إلى المقدمة. وللاتساق مع فكرة الطابور، يعني ذلك أنك تُخرج العناصر من الخلف. ثم يكون السلوك نفسه. نحن ننفّذ طابورًا أيًّا كان قرار التصميم الذي اتخذناه من هذين.
حسنًا. أثناء مرورنا بمحاضرة اليوم، أريد التنبيه إلى بضعة أشياء. لدي علامات التبويب الصغيرة هذه في الأعلى هنا. سنبدّل طريقة تفكير عقولنا قليلًا اليوم، أساسًا. سنعرّف كائن Python.
صفحة المصدر 5
سنكتب كودًا يخبر Python: مرحبًا، أقول لك إنني أود إنشاء نوع الكائن هذا. هذه البيانات التي تمثّلهم، تمثّله، وهذه السلوكيات التي تمثّله. فهذا نحن ننفّذ الصنف، أي نخبر Python أننا الآن ننشئ ونخبرك بماهية كائن من هذا النوع وما يفعله.
والشيء الآخر هو أنه بمجرد أن يكون لدينا تعريف لنوع الكائن هذا، سنستخدم النوع فعلًا. سننشئ كائنات جديدة من هذا النوع. عندما ننشئ الصنف، وعندما نخبر Python أن كائنًا كهذا موجود، نقرر اسم صنفنا. ونقرر ما البيانات التي تجرّده. ونقرر ما السلوكيات التي يمكننا تنفيذها به. فإذا فكّرنا في القائمة، فنحن لم نرَ بالفعل الكود الذي يفعل ذلك. لكن شخصًا كتب كودًا لتعريف صنف القائمة هذا.
أما استخدام الصنف، فيعني أننا نفترض أن هذا الكود موجود بالفعل. وأنك تنشئ فقط مجموعة كاملة من كائنات هذا النوع. وقد فعلنا هذا بالتأكيد، صحيح؟ إذا فكّرنا في صنف القائمة مرة أخرى، هنا مثلًا أنشأنا كائنًا فعليًّا يمكننا التعامل معه. L يساوي 1، فاصلة، 2. وأنشأنا أيضًا L يساوي 3، فاصلة، 4، فاصلة، 5، وكل هذه الأشياء. ننشئ أساسًا هذه المثيلات (Instances) التي نستطيع التعامل معها واستخدامها في برنامجنا لتحقيق شيء مفيد.
واليوم سنرى كيف نستطيع فعل الأمرين كليهما. أريد رسم تشابه قليل مع الدوال، لأن الأمر سيبدو مشابهًا جدًّا. مع الدوال، عندما كنّا نعرّف دالة، كنّا نخبر Python أنني أود تجريد بعض الكود الذي يفعل شيئًا مفيدًا باستخدام هذا الصنف — باستخدام تعريف هذه الدالة (Function Definition).
كنّا نكتب تعريف الدالة بهذه الطريقة المجردة. لم نكن نشغّل الدالة فعلًا عند تلك النقطة. عرّفناها فقط. وعندما نعرّف صنفًا، فهذا أساسًا ما نفعله. نخبر Python أننا ننشئ هذا الكائن الذي يجمع البيانات والسلوكيات معًا.
عندما ننشئ مثيلات من نوع البيانات هذا الذي سنعرّفه، يكون ذلك نوعًا ما مثل استدعائنا الدالة الفعلية التي عرّفناها. عندما استدعينا الدالة، كنّا نفعل الآن شيئًا مفيدًا في برنامجنا. قلنا: هذه بعض المعاملات الفعلية التي أريد منك تشغيل هذه الدالة بها. والآن أخبرني ما المخرج (Output). وهذا بالضبط ما سنفعله عندما ننشئ مثيلات من نوع البيانات الذي نعرّفه. ننشئ الآن كائنات فعلية يمكننا التعامل معها واستخدامها في صنفنا.
حسنًا. الكائن الذي سننشئه في محاضرة اليوم هو نقطة إحداثية (Coordinate) في مستوى ثنائي الأبعاد (2D Plane) — بسيط إلى حدٍّ ما، ورياضي إلى حدٍّ ما. قبل أن نكتب الكود فعلًا، لنفكّر فيما يعنيه وضع نقطة إحداثية في مستوى ثنائي الأبعاد. سنفكّر: لو كانت لدينا مجموعة مثيلات، لو كانت لدينا مجموعة نقاط إحداثية في مستوى ثنائي الأبعاد، كيف تبدو؟ ما نوع البيانات التي نهتم بأخذها من هذه المثيلات؟ وما بعض الأشياء التي نستطيع فعلها بها؟
هنا لدي نقطة في مستواي ثنائي الأبعاد. إذا فكّرنا في كيفية النظر إلى هذه النقطة الإحداثية، فنحن نعرف مدى بعدها على المحور x، ونعرف مدى بعدها على المحور y. هذا إذًا مثيل واحد لكائن إحداثي.
لنقل الآن إن لدينا آخر. هذه نقطة أخرى في مستواي ثنائي الأبعاد. مرة أخرى، ستعرف هذه النقطة أيضًا مدى بعدها على المحور x ومدى بعدها على المحور y. تجريد بيانات معقول لنقطة إحداثية في مستوى ثنائي الأبعاد يمكن أن يكون القول إنني أريد عددين، أحدهما يمثّل مدى بعدها على المحور x، والآخر مدى بعدها على المحور y.
صفحة المصدر 6
يبدو ذلك معقولًا إلى حدٍّ ما. لا يهمني اللون، رغم أنني لوّنت هذه الأشياء. لكن يمكنكم تخيّل صنع نسخة ألطف من كائن النقطة الإحداثية هذا لها لون مرتبط بها أيضًا. البيانات التي ستمثّل نقطتي في مستوى إحداثي، في مستوى إحداثي ثنائي الأبعاد، هي عددان فقط — واحد لـx، وواحد لـy.
والآن، ما بعض الأشياء التي نستطيع فعلها بهذه الكائنات الإحداثية؟ بالتأكيد هناك شيء بسيط حقًّا نستطيع فعله، وهو أن نقول: حسنًا، إحدى هذه النقاط — البرتقالية مثلًا — أخبريني كم تبعدين على المحور x، أو أخبريني كم تبعدين على المحور y. هذان الأمران قد يُرجعان شيئًا مثل 3، لأنه مدى بُعد تلك النقطة على المحور x، أو 4 لمدى بعدها على المحور y.
تلك أشياء بسيطة إلى حدٍّ ما. وهناك شيء أكثر إثارة للاهتمام، أن نقول: حسنًا، مرحبًا، أنتِ، النقطة البرتقالية هناك، هل تستطيعين إخباري كم تبعدين عن النقطة الخضراء؟ ستكون تلك المسافة الإقليدية (Euclidean Distance) بين هاتين النقطتين. وسنكتب كودًا يعرف مدى بُعد كائن إحداثي عن كائن إحداثي آخر.
حسنًا، لنبدأ تعريف هذا الصنف، Coordinate. ترون هنا أن هذا هو الكود الذي ينفّذ الصنف. سيخبر Python أننا ننشئ الآن نوع الكائن هذا، Coordinate. نحن لا نستخدمه بعد. لا ننشئ أي كائنات، أي مثيلات كائنات. نخبر Python فقط بأننا نود إنشاء نوع الكائن هذا.
نبدأ بالكلمة المفتاحية (Keyword) class. وبالموازاة، بدأنا بالكلمة المفتاحية def لتعريف دالة. ثم نقول اسم نوع الكائن عندنا. سيكون هذا حرفيًّا نوع الكائن، Coordinate، تمامًا كما كان لدينا list وfloat وكل تلك الأشياء. سيكون هذا من النوع Coordinate.
ثم هنا بين القوسين، نقول الأب (Parent) لهذا الصنف. عادة نقول object حتى المحاضرة التي بعد محاضرتين من الآن، حيث سنرى ما يحدث عندما نضع شيئًا آخر هناك. لكن عندما نضع object بين القوسين هناك، نخبر Python أن كل ما يستطيع كائن Python عام فعله، يستطيع كائننا أيضًا فعله.
شيء أساسي جدًّا، جدًّا، هو القول إنني سأنشئ هذا الكائن في الذاكرة وأُسند إليه متغيرًا، حتى أحصل على مقبض (Handle) لذلك الكائن باستخدام هذا المتغير — شيء أساسي للغاية. لدى أي كائن Python هذه القدرة. وستكون لدى كائننا أيضًا لأنني وضعت object هذا بين القوسين هنا.
حسنًا، أخبرنا الآن Python أننا ننشئ نوع بيانات اسمه Coordinate. بماذا سنملأ جسم هذا الصنف (Class Body)؟ الأشياء التي نحتاج إلى ملئها ستكون سماتنا (Attributes). مرة أخرى، ممَّ يتكوّن الكائن؟ شيئان — البيانات التي تريد تمثيل هذا الكائن بها، والإجراءات، التي تُسمّى أيضًا دوال، وتُسمّى أيضًا سلوكيات، التي تريد لهذا الكائن أن تكون له.
ستكون البيانات شيئين. قررنا أننا سنمثّل نقطة إحداثية باستخدام عددين. والآن، ماذا عن السلوكيات؟ ستكون السلوكيات أساسًا دوال تعمل مع كائنات من هذا النوع بالتحديد. سنعرّفها بوصفها دوال. لكننا سنعرّفها بطريقة خاصة حقًّا تخبر Python أنك لا تستطيع تشغيل هذه الدالة إلا على كائن من النوع Coordinate، وهذا منطقي.
لا أود إيجاد المسافة بين عددين صحيحين، فهذا طرح فقط. أو لا أود إيجاد المسافة بين قاموسين. ماذا يعني ذلك أصلًا؟ لذلك فإن التابع (Method) الخاص بالمسافة، الذي ذكرنا أنه شيء نود تنفيذه، لن يعمل إلا مع كائنات من النوع Coordinate.
صفحة المصدر 7
هذه الدوال الخاصة تُسمّى في الواقع توابع (Methods). وسأستخدم هذا المصطلح قليلًا اليوم. آمل أن تعتادوه. ثم من المحاضرة القادمة فصاعدًا، سأستخدم كلمة توابع فقط للإشارة إلى الدوال التي لا تعمل إلا مع كائنات من هذا النوع.
حتى الآن، في الشريحة السابقة، كان لدينا class Coordinate(object). والآن، ما الشيء التالي الذي عليكم فعله؟ الشيء التالي الذي عليكم فعله دائمًا عندما تخبرون Python بأنكم تنشئون نوع بيانات جديدًا هو إخبار Python كيف تريدون بناء (Construct) هذا النوع، شيء من قبيل دالة منشئ (Constructor Function).
وطريقتنا في فعل ذلك هي التعريف — ترون أننا نعرّفه مثل دالة، def. لكننا سنعرّف دالة لها اسم خاص. والاسم هو __init__. هذا اسم هذه الدالة. وترون أنها دالة: def، ثم الاسم، ثم أقواس. وهناك مجموعة أشياء بين الأقواس.
سيكون أول شيء هذا الشيء المسمّى self. وبالفعل سيكون مختلفًا قليلًا عن الدوال العادية. الآن سأقوم — هذه ليست المرة الوحيدة التي سأشرح فيها self. سأشرحه طوال المحاضرة. لكن الفكرة الأساسية لـself هي أنه سيكون دائمًا المعامل الأول لتابع، أي لدالة لا تعمل إلا مع كائن من هذا الصنف — من هذا النوع.
وسبب وجوده هنا هو أن كل ما نفعله هنا إخبار Python بأننا نود إنشاء نوع الكائن هذا. ليس لدينا كائن فعلي نتعامل معه. لم أنشئ كائنًا فعليًّا بعد. أُخبر Python فقط بأنني أود إنشاء هذا الكائن. فإذا لم يكن لدي كائن فعلي أُنشئ بعد، فأحتاج إلى طريقة للإشارة إلى مثيل دون أن يكون لدي مثيل فعلًا بعد.
وهذا ما يفعله self. إنه أساسًا متغير يخبر Python أن هذا كائن من — أن هذه دالة لا تعمل إلا مع كائن من هذا النوع. وسأستخدم هذا المتغير، self، للإشارة إلى هذا الكائن، إلى نفسي، إلى سمات بياناتي، وإلى توابعي، وما شابه. سيتضح ذلك. ستكون هناك أمثلة كثيرة. لكن حاليًّا، هو أساسًا طريقة لنا للإشارة إلى كائن من هذا النوع — مثيل من هذا النوع دون أن نكون قد أنشأنا واحدًا بالفعل.
كل ما يأتي بعد self هو أساسًا معاملات تريد إنشاء هذا الكائن بها. بالنسبة إلينا، لا معنى للقول: أنشئ كائن النقطة الإحداثية هذا، دون تهيئة قيمتي x وy فعلًا. عندما نضع كائنًا إحداثيًّا في مستوى ثنائي الأبعاد، أود وضعه في ذلك المستوى ثنائي الأبعاد. لذلك يحتاج إلى قيمة ابتدائية لـx وقيمة ابتدائية لـy.
هذه المعاملات هنا ستخبر Python بأن عليك تمرير قيمة لـx وy عندما تنشئ كائنك. ثم جسم هذا سيكون فيه ما تريد، أي كود تريد لتهيئة كائنك (Initialize). نعم، سؤال؟
طالب (STUDENT):
الطريقة التي تضعين بها الشرطات السفلية، هل هي جزء من طريقة كتابته؟
آنا بيل:
الشرطات السفلية جزء من طريقة كتابته. يجب أن يكون لديك __init__. نعم، إنها دالة خاصة. سنتحدث عنها في المحاضرة القادمة. تُسمّى دالة دندر (Dunder Function)، أي دالة ذات شرطتين سفليتين (Double Underscore)، دندر.
حسنًا. يمكن أن يحتوي جسم هذه الدالة مجموعة من كود التهيئة. أي شيء تريد تهيئته عندما تنشئ كائنًا من هذا النوع، هذا ما تضعه هنا. عادة، في معظم الأوقات، في 99% من الوقت، تريد تهيئة البيانات التي يتكوّن منها كائنك.
صفحة المصدر 8
البيانات التي قررنا أن كائننا يتكوّن منها هي مدى بُعدك على المحور x ومدى بُعدك على المحور y. هنا، هذه البيانات التي أريد لكل واحد من كائناتي أن يمتلكها — قيمة لـx وقيمة لـy — تُهيَّأ باستخدام self. — أي self. مع متغير اسمه x، وself. مع متغير اسمه y.
ووجود self. قبل هذه المتغيرات يميّز هذه المتغيرات، x وy هنا، عن المتغيرات العادية. لو قلت فقط x يساوي xval وy يساوي yval، فسيكون x وy متغيرين عاديين فقط. بمجرد أن تنتهي دالة init عندي، تختفي تلك المتغيرات.
لكن لأن لدي self.x وself.y، فهذا يعني أن هاتين القيمتين، x وy، ستبقيان طوال عمر كائني (Lifetime) عندما أنشئ كائني الفعلي. وكل كائن أنشئه ستكون له قيمتا x وy الخاصتان به. سؤال؟
طالب:
هل يجب أن يكون مختلفًا عن xval؟ أي مثلًا self.xval أو —
آنا بيل:
نعم، سؤال جيد. هل يجب أن يكون هذا الشيء بعد self. مختلفًا؟ ليس ملزمًا بذلك. يمكن أن يكون لديك self.xval يساوي xval، وself.yval يساوي yval. السبب الذي جعلني أفعل ذلك هنا هو إظهار أنهما في الواقع لا يلزم أن يكونا متطابقين. نعم، إنهما مختلفان تمامًا. self.x مختلف عن xval. نحن فقط نُسند هذه القيمة لتكون ما يُمرَّر إلينا.
حسنًا. قليل من، مرة أخرى، شرح معنى self في سياق مخطط — إذا فكّرنا في مخطط في الحياة الواقعية — هنا لدي مخطط لغرفة ربما أريد إنشاءها. لم أنشئ هذه الغرفة فعلًا بعد. إنها فكرة فقط. لكن ما أعرفه هو أنني سأستخدم هذا المخطط لتكون لدي غرفة تحتوي كرسيين وطاولة قهوة وأريكة.
في هذا المخطط، ليست لدي غرف فعلية طبّقت فيها هذا الشيء. ليست لدي غرف فعلية وضعت فيها كرسيين وطاولة قهوة وأريكة. إنها فكرة فقط. لكن self هو الطريقة التي يصل بها المخطط إلى سماته. فإذا قلت self.coffeetable، فهذا يعني أنه إذا كانت لدي في المستقبل غرفة فعلية، فإن self.coffeetable يعني أنني أشير إلى طاولة القهوة في تلك الغرفة.
إذًا self متغير نستخدمه للإشارة إلى البيانات أو السمات الخاصة بمخطط، عندما لا تكون لدي غرف فعلية منشأة. لكن بمجرد إنشاء مثيلات غرف — مثلًا هنا، أنشأت شيئًا اسمه livingroom. أخذت مخططي، والآن طلب مني شخص إنشاء غرفة بهذا المخطط.
لم أعد أستخدم self الآن، لأن لدي غرفة فعلية في يدي. سأشير الآن إلى طاولة القهوة في غرفة المعيشة هذه بوصفها livingroom.coffeetable، أو طاولة قهوة livingroom، ولم تعد طاولة قهوة self. فـself لا يُستخدم إلا في سياق مخططي.
ولتأكيد النقطة الأخيرة، فكرة أنه باستخدام المخطط يمكنك إنشاء مثيلات مختلفة كثيرة، فهذه غرفة معيشة طبّقت عليها مخططي. وهذه غرفة معيشة أخرى، غرفة مختلفة تمامًا، طلب مني شخص استخدام مخططي لإنشائها — كراسٍ مختلفة، وطاولات قهوة مختلفة، وأشياء مختلفة الألوان. هذه كلها مثيلات مختلفة استخدمت لها قالبي الواحد، مخططي الواحد للغرفة.
عندما نعرّف صنفًا، ليست لدينا كائنات فعلية. مرة أخرى، هذه فكرة كبيرة حقًّا هنا. نحن نخبر Python فقط: أود إنشاء هذا الكائن، وهذا شكله. أجمع هذه البيانات مع هذه السلوكيات. لكن لم أنشئ بعد كائنات فعلية من هذا النوع.
صفحة المصدر 9
فلننشئ بعض الكائنات فعلًا. الكود الذي يفعل ذلك كما يلي. وضعت تعريف صنفي، المنشئ، تابع init لصنفي هنا في الأعلى، فقط لتذكيرنا بشكله. ومع هذا الكود، نستطيع الآن البدء في إنشاء كائنات فعلية يمكننا التعامل معها. مثلما أنشأنا شيئًا مثل L يساوي قائمة بين أقواس مربعة، 1، فاصلة، 2، أنشئ الآن هذه النقاط الإحداثية الفعلية في كودي باستخدام مخططي.
الطريقة التي نفعل بها ذلك هي استدعاء اسم صنفنا. تقول Coordinate — هذا ما سمّيناه، صحيح؟ هذا نوع بياناتنا. وهنا أمرّر كل معامل باستثناء self. هيّأت كائن نقطة إحداثية باستخدام xval وyval. لذلك أحتاج إلى وضع معاملين هنا لـxval وyval.
ويصبح self في الواقع هذا الشيء الذي أنشأته للتو، هذا الكائن. Coordinate مع 3، فاصلة، 4، أصبح الآن كائنًا يشير إليه متغير اسمه c، ولهذا لا أمرّر self. الأمر غريب قليلًا عند التفكير فيه. لكن لدي الآن كائنًا واحدًا في الذاكرة، يُشار إليه بالاسم c.
وفي السطر التالي، لدي كائن آخر في الذاكرة. مرة أخرى، استدعيت اسم صنفي، Coordinate. في هذا الكائن بالتحديد، ستكون قيمة x هي 0، وستكون قيمة y هي 0، ولذلك هو مختلف عن الذي أنشأته للتو في السطر السابق. لكنه ستكون له البنية نفسها.
سيكون لكل منهما قيمة ما لـx وy. وستختلفان فقط بينهما. لكن سيكون لكليهما x، وسيكون لكليهما y. الذي سمّيته هنا في الأسفل سيكون origin. لدي إذًا كائنان من النوع Coordinate. أحدهما يُشار إليه بـc، بالاسم c. والآخر يُشار إليه بالاسم origin.
بما أن لدي الآن هذين الكائنين، أستطيع الوصول إلى أيٍّ من سماتهما. وستأخذ Python لي سمة ذلك الكائن بالتحديد. هنا لدي هذا الشيء المسمّى صيغة النقطة (Dot Notation)، الذي رأيناه من قبل. وسأشرحه مرة أخرى بعد بضع شرائح.
لكن صيغة النقطة تخبر Python بالوصول إلى سمة البيانات x للكائن c. سيأخذ لي هذا قيمة x الخاصة بـc، وهي 3. والسطر التالي سيأخذ لي قيمة x الخاصة بـorigin، وهي 0. وكل هذا ممكن لأن x — ويمكننا أيضًا الوصول إلى y — لأن x وy عُرّفا في تعريف الصنف باستخدام self. لو لم أستخدم self.، لكانا متغيرين فقط. وبمجرد أن أنشأت كائني، كانا سيختفيان لأن تلك الدالة انتهت.
لكن لكي تبقى هذه المتغيرات، x وy، طوال عمر كائني، عرّفتهما باستخدام self.x وself.y. فأي كائن أنشأته من النوع Coordinate ستكون له قيمة ما لـx وقيمة ما لـy. ونستطيع الوصول إلى تلك القيمة بهذه الصيغة. هل هذا واضح حتى الآن؟ هل هو جيد؟ حسنًا.
سنصوّر هذا بطريقة مختلفة قليلًا. الكود نفسه تمامًا كما في الشريحة السابقة، سننفّذه الآن في نموذج الذاكرة الصغير عندنا. هنا لدي c يساوي Coordinate مع 3 و4، تمامًا كما كان لدي في الشريحة السابقة. في الذاكرة، طريقة التفكير فيه هي كما كنّا نفكّر في الكائنات الأخرى. لا يختلف كثيرًا.
لدينا c هو اسمنا، وهو مرتبط بكائن من النوع Coordinate. صادف فقط أننا عرّفنا هذا الكائن. لكنها الفكرة نفسها. لدي اسم مرتبط بكائن ما. ولهذا الكائن قيمة x الخاصة به وقيمة y الخاصة به. عندما تقيّم c.x، تذهب Python إلى الذاكرة وتقول: ما نوع c؟ وتقول: أوه، إنه كائن Coordinate. هل للكائن Coordinate سمة بيانات اسمها x؟ نعم لديه، لأنها تنظر إلى init. ثم تقول: حسنًا، ما قيمتها؟ إنها 3. فتعيد ذلك فقط.
صفحة المصدر 10
الأسطر الثلاثة التالية هنا مختلفة قليلًا عما كان قبل شريحتين، لكنها مشابهة جدًّا. a يساوي 0 ينشئ لي متغيرًا اسمه a مرتبطًا بالقيمة 0، فقط لإظهار أنه الشيء نفسه تمامًا مثل وجود متغير اسمه c مرتبط بهذا الكائن الذي أنشأناه. ثم عندما أقول orig يساوي Coordinate(a,a)، تقول Python: حسنًا، هذا اسم orig، اختصارًا لنقطة الأصل (Origin).
بماذا هو مرتبط؟ حسنًا، هو أيضًا مرتبط بكائن من النوع Coordinate. وهو كائن عرّفناه. عرّفنا إذًا كائنًا من النوع Coordinate له قيمتا x وy. فها هما. وهما في البداية 0. ضُبطتا على 0 عندما أنشأت هذا الكائن.
عندما أقول orig.x، ستبحث Python عن orig. ستقول: ما نوعك؟ أوه، أنت نقطة إحداثية. هل لديك قيمة x؟ لديك. هذا ما عرّفناه في init. دعني آخذ تلك القيمة منك. نحن نتعامل فقط مع كائنات في الذاكرة. بما أننا كتبنا الآن الكود للعمل مع كائنات أنشأناها، فنحن ننشئ مجموعة كاملة من هذه الكائنات في الذاكرة، ثم نأخذ قيم x الخاصة بها. وبعد قليل سنحصل على المسافة بين كائنين.
هناك طريقة أخرى لأريكم الكود نفسه تمامًا، وهي تصوّره بصريًّا. هنا الكود، الكود كله كما سيكون في ملف. سيكون لديكم كل هذا معًا. الإطار الرمادي هو تعريف نوع الكائن عندي. والإطار الأزرق هو استخدامي لهذا الكائن الذي أنشأته للتو. فصلت ذلك فقط من أجل الوضوح.
عندما يكون لدي الإطار الرمادي، لا يوجد شيء نعرضه. إنه موجود في الذاكرة فقط. وPython تعرف بهذا النوع من الأصناف، Coordinate، الذي له سمتا بيانات — الأشياء التي عرّفتها باستخدام self.، أي x وy. عندما أنشئ c يساوي Coordinate(3,4)، ولتصوّر ما نحاول فعله هنا، لدي هنا هذا الكائن الذي اسمه c، وهو عند 3، فاصلة، 4. ثم لدي هذا الكائن الذي اسمه origin، وقيمتا x وy عنده هما 0، فاصلة، 0.
لأنني أنشأت هذه الكائنات باستخدام المخطط نفسه، مخطط النقطة الإحداثية الذي عرّفته في الأعلى بالرمادي، يعني ذلك أن كل كائن أنشأته، c وorigin، له قيمة self.x وقيمة self.y. صادف فقط أن القيم الفعلية لـx وy مختلفة بين هذين الكائنين. عندما آخذ origin.x، أبحث عن origin وآخذ قيمة x الخاصة به، 0؛ هذه طريقة أخرى فقط لتصوّر الأمر.
حسنًا. هل سمات البيانات هذه واضحة للجميع؟ حسنًا. لنضف الآن تابعًا. تذكّروا أن التابع ليس إلا دالة تعمل مع كائن من هذا النوع. فالطريقة التي نخبر بها Python بأننا نود إنشاء تابع هي تمرير self بوصفه المعامل الأول.
لننشئ هذه الدالة التي اسمها distance. إذا نظرتم إلى كود Python الفعلي لليوم، لدي دالتان إضافيتان — واحدة للحصول على قيمة x لهذا الكائن الحالي، وواحدة للحصول على قيمة y. لكن هاتين ليستا مثيرتين للاهتمام بالقدر نفسه. هذه الدالة distance مثيرة للاهتمام، مع ذلك. أود إنشاء هذه الدالة التي لا تعمل إلا مع كائن من النوع Coordinate. ما فعلناه حتى الآن هو هذه الأسطر هنا في الأعلى.
لدينا الآن def — مرة أخرى، إنها دالة فقط. لدينا def، ثم اسمها، distance، ثم المعاملات. مرة أخرى، بما أنها دالة لا تعمل إلا مع كائن من النوع Coordinate، أحتاج إلى وضع self بوصفه المعامل الأول. وسيساعدنا self هذا على الإشارة إلى الكائن عندما أستدعي التابع عليه.
إذا كان self هو المعامل الأول، فهذا يعني أن تابع distance هذا سيُستدعى على self. عندما يكون لدي كائن فعلي أستدعي distance عليه، فإن المعامل self سيأخذ القيمة التي هي ذلك الكائن. سنرى هذا في الشريحة القادمة. إذًا self هو الشيء الذي أستدعي هذه الدالة عليه.
صفحة المصدر 11
ثم ما المعاملات الأخرى التي أريد إعطاءها لهذه الدالة؟ حسنًا، أريد إيجاد المسافة بين نفسي، أي هذا الكائن الذي سأستدعي distance عليه، وكائن إحداثي آخر. الآن، باستثناء ربما سلسلة توثيق (Docstring) هنا تقول: انتبه، ينبغي أن يكون other كائنًا من النوع Coordinate، لا يوجد فعلًا شيء يفرض نوع other عند إجراء استدعاء دالة — أو عند إجراء استدعاء تابع (Method Call).
يمكنك استدعاء تابع distance هذا مع كون other عددًا صحيحًا، وهو ليس كائنًا من النوع Coordinate. سيعمل الكود، لكنه سيتعطل فورًا بسبب ما يجري بالداخل. فالطريقة الوحيدة التي سيعمل بها هذا الكود هي إذا كنت تستدعيه مع كائن من النوع Coordinate بالنسبة إلى other.
والسبب في ذلك هو أننا، حسنًا، عندما نفكّر في الحصول على المسافة بين كائنين هما نقطتان إحداثيتان في مستوى ثنائي الأبعاد، نأخذ الفرق بين قيمتي x ونربّعه، ونأخذ الفرق بين قيمتي y ونربّعه — فيثاغورس (Pythagoras) — ثم نجمع هذين ونأخذ الجذر التربيعي.
إذا كنت أستدعي تابع distance هذا على كائن من النوع Coordinate، أي self، فكيف آخذ قيمة x الخاصة بنفسي؟ أقول فقط self.x. قيمة x عندي، ما هي؟ ثم أود طرح ذلك من قيمة x الخاصة بالكائن الإحداثي الآخر. ما كائني الإحداثي الآخر؟ إنه الشيء الذي أمرّره بوصفه معاملًا. فخذ قيمة x الخاصة به.
إذا أخذت self.x ناقص other.x، فستأخذ Python قيمة x عندي، وتطرحها من قيمة x عند other، وتربّع ذلك. نفعل الشيء نفسه تمامًا مع y. نأخذ قيمة y عندي، ونطرحها من قيمة y عند other، ونربّع ذلك. ثم الباقي فيثاغورس فقط: اجمع هذين وخذ الجذر التربيعي. وترفعه للقوة 1/2.
ملاحظة المترجم: يورد الكلام اتجاه الطرح بصيغة «نطرحها من قيمة الآخر»، ثم يذكر التعبير
self.x - other.x. التعبير يطرح قيمة الآخر من قيمةself؛ لا يتغيّر مربع الفرق باختلاف اتجاه الطرح. نُقل الشرح الشفهي دون تبديل صامت.
وهذه الدالة دالة عادية، باستثناء كون self المعامل الأول، وعملنا مع سمات بيانات نفسي وربما معاملات أخرى. لكن ترون أنها تُرجع قيمة. وفيها def والاسم وما شابه.
الطريقة التي سنستخدم بها هذا التابع الذي كتبناه للتو هي استخدام معامل النقطة (Dot Operator). تمامًا كما وصلنا إلى سمة بيانات لكائن أنشأته، أستطيع الوصول إلى سمة إجرائية (Procedural Attribute)، أي تابع لكائن أنشأته للتو. نستخدم معامل النقطة لهذا. الشيء قبل النقطة هو الكائن الذي أود استدعاء التابع عليه، نقطة، ثم اسم التابع الذي أود استدعاءه. وبين الأقواس، إنها دالة فقط. لذلك أحتاج إلى إعطائها أي معاملات يتوقعها هذا التابع.
ينبغي أن يبدو هذا مألوفًا جدًّا. قدّمنا صيغة النقطة عندما عملنا مع القوائم. هل تذكرون ذلك؟ وقلت: عندما نعمل مع قائمة، عليكم حاليًّا تذكّر لماذا نستخدم هذه الطريقة الخاصة في كتابة هذه الدالة. لكنها كانت الفكرة نفسها. الشيء قبل النقطة كان القائمة التي أردت تطبيق الدالة عليها.
my_list هو اسم متغير قائمة. أردت تطبيق الدالة append. وصادف أنها تأخذ عددًا صحيحًا بوصفه معاملًا. وكذلك sort هنا مثال آخر أيضًا. لكن هذه لم تأخذ أي معاملات. إنها الفكرة نفسها، صيغة النقطة.
من حيث صنفنا، لدي هنا كائنان إحداثيان. ولدي صيغة نقطة مستخدمة هنا لإيجاد المسافة بين كائن وآخر. الشيء قبل النقطة هو كائن أود استخدام تابع distance عليه. اختَر أحدهما، c.distance، اسم التابع الذي أود استدعاءه. وبين الأقواس لدي كائن إحداثي آخر، orig. هنا أستخدم الصنف. ولدي قيم فعلية، كائنات فعلية أتعامل معها — c وorig.
صفحة المصدر 12
قد يبدو هذا غريبًا قليلًا. لكن عندما نستدعي الدالة فعلًا، تذكّروا أننا حذفنا self — عندما حذفنا — آسفة، حذفنا self عندما أجرينا استدعاء الدالة هذا. لكن ذلك لأن self يصبح ضمنيًّا (Implicitly) الشيء قبل النقطة، الشيء الذي تستدعي هذا التابع عليه.
لنتصوّر ذلك في ذاكرتنا. هنا لدي تعريف صنفي لنقطة إحداثية. له بعض سمات البيانات وبعض السمات الإجرائية. ويجري إنشاء هذين الكائنين. c هو هذا الكائن من النوع Coordinate. وorig هو هذا الكائن من النوع Coordinate. لديهما قيم مختلفة لـx وy. لكن لكل منهما قيم ما لـx وy.
عندما أجري استدعاء دالة إلى c — آسفة، استدعاء تابع على c — تقول Python: حسنًا، دعني أنظر إلى هذا الشيء قبل النقطة. ما هو؟ إنه كائن من النوع Coordinate. ثم تنظر إلى التابع الذي تحاول استدعاءه، distance. تقول: هل لدى Coordinate تابع distance معرَّف؟ نعم، لديه. كتبناه للتو.
ثم تقول: حسنًا، دعني أستدعِ تابع distance هذا. ستضبط self على c، الشيء قبل النقطة. وأي معاملات أخرى ستُضبط بالترتيب على ما يُمرَّر هنا. فسيصبح orig المعامل other من تعريفي لتلك الدالة.
هذه فقط الطريقة المعتادة لاستدعاء التوابع. وهي الطريقة التي كنّا نعمل بها مع القوائم والقواميس وما شابه. مرة أخرى، لدينا كائن ما، الشيء قبل النقطة، وتابع ما لتشغيله. وعندما نستدعيه بهذه الطريقة، يصبح الشيء قبل النقطة هو self في تعريف صنفنا، في تعريف تابعنا. ثم تُسنَد جميع المعاملات الأخرى واحدًا واحدًا، باستثناء self.
والآن، لإزالة الغموض عن هذا، أود أن أريكم ما يكافئه هذا فعلًا. نستطيع تشغيل الدالة، التابع الذي عرّفناه، بتمرير قيمة فعلية لـself، إذا كان هذا أوضح لكم. في تلك الحالة، لا يمكن أن يكون الشيء قبل النقطة كائنًا، لأنه إذا كان كائنًا من النوع Coordinate، فستقول Python: حسنًا، هذا هو الكائن الذي أشغّل تابع distance عليه.
لإزالة الغموض، يمكنك في الواقع استدعاء اسم الصنف، الكائن الذي تحاول إنشاءه، الاسم، نوع البيانات، Coordinate. عندها تقول Python: أوه، فهمت، أنت تستدعي اسم الصنف. ليس كائنًا. فماذا تريد مني؟ الشيء بعد النقطة يقول: أود تشغيل هذا التابع عليك. لكنه يحتاج الآن إلى جميع المعاملات في قائمة المعاملات، بما فيها self. هنا عليّ أن أعطيه صراحة c، فاصلة، 0، بدلًا من 0 فقط، لأن الشيء قبل النقطة اسم صنفي، وليس كائنًا فعليًّا كما هو في هذا الجانب. فهذه في الواقع الطريقة المعتادة لفعل هذا. وهذه الطريقة المختصرة، الموافقة لأسلوب Python، لفعل هذا. لكن آمل أن يزيل ذلك غموض موضوع self والطريقة التي نضبط بها فعلًا ذلك المعامل الأول على الشيء قبل النقطة. حسنًا. نعم، سؤال؟
ملاحظة المترجم: يرد «0» في التفريغ أثناء شرح المقابلة بين الاستدعاءين. المثال الذي كان يجري شرحه يستخدم الكائن
orig:c.distance(orig)يكافئCoordinate.distance(c, orig). حُفظت عبارة التفريغ أعلاه؛ وهذا التوضيح لا يعني تمرير العدد0إلى التابع الذي يحتاج كائنًا إحداثيًّا.
طالب:
عندما كنت ستنفّذين الأول، كان لديك أكثر من معامل [غير مسموع] 0، فاصلة —
آنا بيل:
نعم، بالضبط. إذا كانت هناك معاملات أكثر، فضع تلك الإضافية مع فواصل، تمامًا مثل دالة عادية. معامل النقطة هذا يصل أساسًا إما إلى بياناتنا، c.x، أو توابعنا، c.distance، أو أي شيء، أو أي اسم تابع لدينا.
هذا كل شيء لمحاضرة اليوم. في المحاضرة القادمة سنبني على كائن النقطة الإحداثية هذا بإنشاء دوائر (Circles). ثم سننشئ بعض كائنات الكسور (Fraction Objects). وسننظر إلى بعض الكائنات الأخرى التي نستطيع تجميعها معًا. حسنًا.