مقدمة في علوم الحاسوب والبرمجة باستخدام Python › المحاضرة 11: الأسماء المستعارة (Aliasing) والاستنساخ (Cloning) 13 / 30 السابق التالي

النص الكامل للمحاضرة 11: الأسماء البديلة والاستنساخ

النص الكامل للمحاضرة 11: الأسماء البديلة والاستنساخ

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

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

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

صفحة المصدر 1

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

[صرير]

[حفيف]

[نقرات]

آنا بيل (ANA BELL):

لنبدأ بالقوائم (Lists) وقابلية التغيير (Mutability). تحدثنا في المحاضرة الماضية كثيرًا عن معنى أن تكون لدينا هياكل البيانات القابلة للتغيير هذه، أي القوائم. واليوم لم ننتهِ من ذلك بعد.

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

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

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

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

صيغة نسخ قائمة هي كالتالي. لدينا قائمة منشأة بالفعل اسمها L، ونريد إنشاء نسخة منها. الصيغة هي L ثم قوسان مربعان بداخلهما نقطتان رأسيتان، أي L[:]. ووراء الكواليس تنشئ Python هذه القائمة داخل الذاكرة.

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

إذا كانت Loriginal هي 4, 5, 6 في الذاكرة، وأردت إنشاء نسخة من قائمتي، أقول فقط Loriginal ثم قوسين مربعين بداخلهما نقطتان رأسيتان: Loriginal[:]. وهذا يعني نسخ كل عنصر من بداية هذه القائمة إلى نهايتها وربطه بالاسم Lnew.

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

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

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

صفحة المصدر 2

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

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

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

ثم مرّوا على النسخة وأعيدوا إلى L جميع العناصر التي لا تساوي E. هذه هي العملية. وفي النهاية، عندما نستدعي دالة remove_all هذه، يكون الشيء الذي نمرره قد تغيّر. ليس لدينا شيء نُرجعه.

نحن نغيّر فقط الشيء الذي يُمرَّر. سأعطيكم دقيقتين للعمل على ذلك. ويمكنكم بدء كتابته قرب السطر 30.

حسنًا. هل لدى أحد كود نبدأ به؟ نعم.

طالب (STUDENT):

نهيئ قائمة جديدة؟

آنا بيل (ANA BELL):

نعم. نهيئ قائمة جديدة. نعم. ماذا تريد أن تسميها؟ Lnew؟ اسم جيد. Lnew تساوي — كيف ننشئ نسخة، وماذا ننسخ؟

طالب (STUDENT):

لماذا ننسخ [غير مسموع] نضيف إلى العناصر التي لا تساوي؟

آنا بيل (ANA BELL):

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

تمامًا مثل الصيغة الموجودة في الشرائح، سيحفظ لنا هذا أساسًا كل ما لدينا بالفعل في L داخل قائمة جديدة اسمها Lnew. حسنًا. والآن بعدما صار لدينا ذلك، هل لدى أحد — نعم.

طالب (STUDENT):

استخدمت L.clear.

آنا بيل (ANA BELL):

إذًا L.clear لا تأخذ معاملًا (Parameter)، صحيح؟ إنها مجرد دالة تفرغ L بالكامل. فستحذف أساسًا كل عنصر في L. حسنًا؟ لكننا سنرى دالة تزيل عناصر.

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

نعم. إذًا for n in Lnew؛ أي إنني أمرّ على القائمة التي تحتوي فعلًا على أشياء، الشيء الذي نسخته. ثم —

طالب (STUDENT):

يمكنك فقط أن تقول: إذا كانت e لا تساوي n، فعندها تريد أن تستخدم append [غير مسموع].

آنا بيل (ANA BELL):

نعم، بالضبط. L.append. لاحظوا: أضيف إلى L، لكنني أمرّ على Lnew، صحيح؟ Lnew تحتوي على كل هذه العناصر. أريد الوصول إلى كل عنصر لأرى قيمته.

إذا لم يساوِ العنصر الآتي من المعامل e، أضيفه إلى قائمتي L، التي هي فارغة حاليًا. حسنًا؟ وبعد ذلك، هل أحتاج إلى إرجاع شيء؟ لا نحتاج إلى الإرجاع؛ لن يضر أن نُرجع L، لكن L تكون قد تغيّرت بالفعل بفعل هذه الدالة.

صفحة المصدر 3

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

ولاحظوا: لا أحفظ القيمة المُرجعة من هذه الدالة في أي شيء، صحيح؟ لأن هذه الدالة ستغيّر فقط أي شيء مررته. وإذا طبعت قيمة Lin بعد استدعاء الدالة، فستطبع القيمة بعد تغييرها.

طالب (STUDENT):

إذًا عندما نضيف n باستخدام append [غير مسموع]؟

آنا بيل (ANA BELL):

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

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

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

لكن أولًا سأشرحها. أحد خيارات إزالة عنصر من قائمة هو، إذا كنت تعرف فهرس (Index) العنصر الذي تريد إزالته — مثل أن تريد إزالة أول عنصر في القائمة، أو آخر عنصر، أو العنصر في منتصفها، أو شيء كهذا — يمكنك أن تطلب من Python إزالة العنصر من القائمة L عند فهرس محدد باستخدام del هذه مع الأقواس؛ أي هذه الدالة del. وتمرّر L عند أي فهرس تريد إزالته.

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

حسنًا. pop مثيرة للاهتمام قليلًا لأن لها قيمة مُرجعة (Return Value). نستخدم صيغة النقطة (Dot Notation)، التي استخدمناها مع append وclear ومجموعة من الأشياء الأخرى في المحاضرة الماضية. لكن pop هنا لا تملك فقط الأثر الجانبي (Side Effect) المتمثل في تغيير قائمتي بحذف آخر عنصر منها، بل تُرجع شيئًا أيضًا.

فاستدعاء الدالة هنا سيُرجع لي قيمة العنصر الذي حُذف، في حال أردت فعل شيء به.

وأخيرًا، إذا كنت تعرف تحديدًا العنصر الذي تريد إزالته، فإذا كانت لديك قائمة بمجموعة من الأسماء وأردت إزالة Anna من تلك القائمة، وكانت السلسلة النصية Anna هي ما تريد إزالته، تفعل ذلك باستخدام الدالة L.remove.

أيًا كانت القائمة التي تنتمي إليها أسماؤك، تقول اسم تلك القائمة ثم .remove. ثم تمرّر لها السلسلة النصية Anna أو العدد 5 أو أي عنصر فعلي تريد إزالته. حسنًا.

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

ملاحظة المترجم: تصف المتحدثة del شفهيًا بأنها دالة، لكن del في Python تعليمة حذف وليست دالة ذات قيمة مُرجعة. حُفظ وصفها في الكلام، وهذه الملاحظة توضيح منفصل.

صفحة المصدر 4

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

حسنًا. لنبدأ بهذه L. إذا قلنا L.remove(2) فستبحث Python عن العنصر الذي قيمته 2. ها هو، موجود في مقدمة قائمتي. لا بأس. وستزيل Python ذلك العنصر. فستصبح هذه القائمة أقصر بعنصر واحد، صحيح؟

وتختفي 2 تلك. إذًا تتغيّر القائمة L الآن لتصبح 1, 3, 6, 3, 7, 0. حسنًا. وماذا لو أزلنا 3 الآن، صحيح؟ نفّذنا عملية إزالة 2، وانتهينا بهذه القائمة بعد تغييرها. والآن ماذا لو أزلنا 3 من هذه القائمة بعد تغييرها؟

هناك اثنتان منها فيها، صحيح؟ العنصر الذي سيُزال هو أول واحد تجده، أي هذا هنا فقط. ومرة أخرى، هذه عملية تغيّر قائمتي. فهذه القائمة التي بدأت بها ستكون أقصر بعنصر واحد. وتكون 3 تلك قد أُزيلت، صحيح؟ أصبح لدي الآن 1, 6, 3, 7, 0.

حسنًا. ماذا لو أردنا حذف عنصر عند فهرس محدد؟ مرة أخرى نعمل الآن على القائمة بعد تغييرها: 1, 6, 3, 7, 0. تأخذ دالة del هذه فهرسًا في قائمة محددة وتزيل العنصر الموجود هناك. في هذه الحالة أريد إزالة العنصر عند الفهرس 1.

في هذه القائمة، العنصر عند الفهرس 1 هو 6، صحيح؟ هذا 0، وهذا 1. إذًا ستُزال 6. وستتغيّر قائمتي لتحتوي على هذه العناصر الأربعة فقط: 1, 3, 7, 0.

وأخيرًا، إذا نفّذنا pop، فستزيل تلك الدالة فقط العنصر الموجود عند نهاية القائمة. والعنصر عند نهاية القائمة هو 0 هذه. فالقائمة، من خلال الأثر الجانبي لـpop، ستتغيّر لتحتوي فقط على العناصر الثلاثة باستثناء العنصر الأخير. فستحتوي على 1, 3, 7.

وبالإضافة إلى ذلك، إذا أردت حفظ قيمة العنصر الذي أُزيل من نهاية القائمة، أي 0، يمكنك ذلك، لأن استدعاء الدالة هنا، L.pop، يمكنك حفظ قيمته المُرجعة في متغير.

لا واحدة من العمليتين الأخريين، del أو remove، لها قيمة مُرجعة، صحيح؟ فإذا حفظت متغيرًا من استدعاء الدالة في متغير، ستكون قيمة ذلك المتغير None. وpop مميزة لأنها تأخذ فعلًا قيمة ذلك المتغير وتُرجعها. حسنًا.

كل هذه العمليات تغيّر القائمة، صحيح؟ وهذا يعني أننا حين نفّذنا عملية بعد عملية كنا نعمل على القائمة بعد تغييرها. حسنًا. نعم، كان هناك سؤال.

طالب (STUDENT):

نعم. ينبغي أن تبقى L1؛ الثالثة إلى اليسار.

آنا بيل (ANA BELL):

آسفة. أعد قول ذلك.

طالب (STUDENT):

في الثالثة قلتِ احذف L1.

آنا بيل (ANA BELL):

L عند الفهرس 1. نعم. إذًا L عند الفهرس 1 هنا تعمل على القائمة التي غيّرناها للتو. هذه إذًا. العنصر عند الفهرس 1 هو 6.

طالب (STUDENT):

آسف.

صفحة المصدر 5

آنا بيل (ANA BELL):

آه، نعم. نعم. لا بأس. حسنًا. لننظر إلى الكود الذي كتبناه للتو في تمرين «جرّب بنفسك»، ونحاول إعادة كتابته باستخدام عملية remove هذه. يمكننا التفكير فيه على أننا نريد إزالة العنصر الذي هو e، صحيح؟

نعرف إذًا قيمة العنصر الذي نريد إزالته. إنها 3 أو 5 أو 1 أو 2 أو أي شيء. هذه هي e. ونعرف عملية يمكنها إزالة العنصر من القائمة. اسمها remove، كما تتوقعون.

يمكننا إذًا أن نقول L.remove(e)، صحيح؟ وسيزيل ذلك أول ظهور للعنصر في القائمة، لكن ربما يكون لدي الكثير من هذه العناصر في قائمتي. لذلك نستطيع كتابة حلقة while صغيرة حول هذه العملية.

ونقول: ما دامت هذه القيمة موجودة في قائمتنا، أزلها. صحيح؟ هذا ما تفعله حلقة while هذه. ستكون e in L إما True أو False، بحسب ما إذا كان العدد 5 أو أيًّا يكن موجودًا في قائمتي. وما دام لدي 5 في قائمتي، استدعِ L.remove على 5 أو أيًّا كانت القيمة. إذًا لدينا حل لطيف صغير من سطرين للمسألة نفسها.

ماذا لو أعدنا كتابة ذلك الكود بطريقة مختلفة قليلًا، باستخدام remove أيضًا، لكن لنفترض أننا ربما لم ندرك أننا نستطيع استخدام حلقة while؟ واستخدمنا بدلًا منها حلقة for للمرور على كل عنصر في L. وإذا كان ذلك العنصر يساوي e، أزلناه. صحيح؟ يبدو معقولًا.

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

وعندما طبعت النتيجة، طبعت فعليًا 1, 2. فما زال لدي عنصران في قائمتي. يبدو أنها لم تزل إحدى قيم 2 على نحو صحيح. وفي البداية يكون سبب ذلك مفاجئًا، صحيح؟ لأن الكود يبدو صحيحًا. ويبدو أنه يعمل تمامًا.

لكن لنتتبّع مخطط الذاكرة (Memory Diagram) هذا ونرى بالضبط ما يحدث خطوة بخطوة، أي في كل تكرار (Iteration) من حلقة for. في البداية لدي L تحتوي على 1, 2, 2، صحيح؟ حتى الآن لا مشكلة. هذا فقط تنفيذ هذا السطر هنا.

ثم أستدعي دالة remove_all. أريد إزالة العدد 2 من قائمتي. لدي حلقة for اسم متغير الحلقة فيها elem. وستمرّ على كل عنصر في تسلسلي، حيث تسلسلي هو جميع العناصر الموجودة في L.

أولًا سيكون 1، صحيح؟ ثم سيكون القيمة التالية في التسلسل: 2، ثم 2، ثم 2. صحيح؟ هنا هيأت elem فقط بأول قيمة في التسلسل. وإذا كانت elem == e، فإن 1 لا تساوي 2. لذلك لا نزيل شيئًا.

بعد ذلك تنتقل حلقة for إلى القيمة التالية في تسلسلي، وهي 2. أصبحت elem الآن 2. وإذا كانت elem == 2، فهي فعلًا تساوي 2؛ فماذا سأفعل؟ أحتاج إلى تنفيذ L.remove(e). وهنا تحدث الأمور السيئة.

سأزيل عنصرًا من قائمتي، صحيح؟ ما زالت لدي قيم 2 الثلاث هناك. لكن بمجرد حذف واحدة من قيم 2، تتحرك جميع العناصر التي تأتي بعدها. لكن Python لا تعرف أنه ينبغي أيضًا تحريك المؤشر (Pointer)، صحيح؟

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

ملاحظة المترجم: تذكر المتحدثة في بداية التتبّع 1, 2, 2، ثم تتابع وصف ثلاث قيم 2؛ نُقل هذا الاختلاف الشفهي كما ورد، دون تصحيح صامت.

صفحة المصدر 6

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

حسنًا. هذه مشكلة كبيرة. يمكننا إكمال المثال هنا، لكننا رأينا المشكلة بالفعل. في المرة الأخيرة عبر الحلقة، ترى Python: هل 2 هذه تساوي الشيء الذي أريد إزالته؟ نعم. فتزيلها.

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

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

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

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

لننظر إلى مسألة مختلفة قليلًا. ستكون في سياق دالة اسمها remove_duplicates. ستأخذ هذه الدالة قائمتين. على سبيل المثال هنا، لدي قائمة تحتوي على 10, 20, 30, 40. ولدي قائمة أخرى تحتوي على 10, 20, 50, 60.

الغرض من هذه الدالة هو تغيير L1، حسنًا؟ والطريقة التي أريد تغيير L1 بها هي: إذا كان عنصر في L1 موجودًا أيضًا في L2، أريد إزالته. حسنًا.

لاحظوا أن 10 و20 مشتركتان بين L1 وL2. لذلك أريد إزالة 10 و20 من L1. وتبقى 30 و40، لأنه لا توجد 30 أو 40 في L2.

هذه هي مهمتنا. وهذا هو الكود الذي يُفترض أنه يفعل ذلك. لدي حلقة تمرّ على كل عنصر في L1، أي 10 ثم 20 ثم 30 ثم 40. وأسأل: هل ذلك العنصر موجود في L2؟ ها هما؛ توجد اثنتان منهما هنا. عندئذ أزله من L1.

هذا شبيه جدًا بما فعلناه للتو. هذا الكود لا يعمل، لأننا إذا شغّلناه فعليًا، فستغيّر Python في النهاية L1 لتحتوي على 20 و30 و40، صحيح؟ بينما نريد فقط أن تحتفظ بـ30 و40، لأن 20 ظهرت أيضًا في L2. فلماذا احتفظنا بها أصلًا؟

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

هنا لدي 10, 20, 30, 40 في L1، و10, 20, 50, 60 في L2. في حلقتي، متغيري هو e، لذلك سيكون أولًا 10. ونسأل: إذا كانت 10 في L2 — وهذا صحيح — فأزلها من L1. تستطيعون رؤية ما سيحدث. تُزال 10، ويتحرك كل شيء آخر بمقدار واحد، لكن فهرس حلقتي يظل ثابتًا.

صفحة المصدر 7

بعد ذلك تقول Python: سأزيد متغيري e لأنتقل إلى العنصر التالي في تسلسلي. فتصبح e هي 30. وقد تجاوزت بالفعل عنصرًا كنت مهتمة بإزالته.

هنا عندما نشير إلى 30، تقول Python: 30 ليست في L2. فلا نفعل شيئًا. ثم تشير إلى 40. و40 ليست في L2. فلا نفعل شيئًا. ثم ينتهي الكود. وانتهينا خطأً بتغيير L1 لتصبح فقط 20, 30, 40.

حسنًا. لنحاول إعادة كتابة الكود بحيث يعمل فعليًا باستخدام النسخ (Copies). يمكننا بالتأكيد استخدام الحيلة نفسها التي استخدمناها في أول تمرين «جرّب بنفسك»، حيث ننشئ نسخة، ونفرغ L1، ثم نعيد إضافة العناصر. نستطيع فعل ذلك.

لكن يمكننا أيضًا تنفيذ نسخة مختلفة قليلًا من ذلك، ننشئ فيها نسخة مجددًا. هنا لدي L1_copy = L1[:]. والأمر الأساسي هنا هو أننا نمرّ على النسخة، صحيح؟

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

عندما أستدعي دالتي هنا، لدي L1_copy = L1[:]. ينشئ هذا لي متغيرًا جديدًا في الذاكرة، وهو تكرار مطابق، نسخة أو استنساخ، من L1. حسنًا.

أصبح كل عنصر من عناصري محفوظًا الآن. لذا أستطيع فعل ما أشاء بـL1 مع علمي بأن لدي طريقة للمرور والنظر إلى كل متغير من L1 الأصلية. الآن يمرّ متغير حلقتي e على عناصر L1_copy.

أولًا ننظر إلى 10. وأقول: إذا كانت 10 في L2 — وهي كذلك — فأزلها من L1. لاحظوا: غيّرت للتو L1، لا النسخة، لتصبح أقل بعنصر واحد.

ثم ينتقل متغير الحلقة e إلى القيمة التالية في تسلسلي. لا أتجاوز شيئًا هنا لأنني لم أغيّر L1_copy. فننظر الآن إلى 20 بصورة صحيحة هذه المرة، صحيح؟ ونسأل الآن: هل 20 في L2؟ نعم، لذلك نزيلها من L1.

ثم لا نفعل شيئًا لـ30 و40. هل هناك أسئلة حول ذلك؟ هل هذا واضح؟ هل السرعة عالية جدًا؟ هل هي بطيئة جدًا؟ جيد. حسنًا. حسنًا.

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

لنأخذ نظرة عامة سريعة على معنى الاسم البديل. إذا فكرنا في مدينة، بوسطن (Boston) مثلًا، فإن اسمًا بديلًا لبوسطن هو أساسًا أي اسم آخر يشير إلى المدينة نفسها، صحيح؟ الكائن نفسه.

بوسطن، المعروفة أيضًا باسم «المركز» (the Hub)، أو «مدينة الفاصولياء» (Beantown)، أو «أثينا أمريكا» (Athens of America): كل هذه الأسماء تشير إلى المدينة نفسها في جوهرها، صحيح؟ فإذا قلت إن بوسطن صغيرة ومتمرسة في التقنية، فإن هاتين السمتين أو الخاصيتين تشيران إلى الكائن نفسه، صحيح؟ إلى المدينة.

إذًا the Hub صغيرة ومتمرسة في التقنية، أو Beantown صغيرة ومتمرسة في التقنية، صحيح؟ لا يهم أي اسم أستخدم للإشارة إلى هذا الكائن.

صفحة المصدر 8

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

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

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

وفي Python، استخدام عامل الإسناد (Assignment Operator) هذا، علامة التساوي، يعني أنك تنشئ اسمًا بديلًا للكائن نفسه في الذاكرة. إنه مجرد اسم آخر للإشارة إلى الكائن نفسه. إذا غيّرت ذلك الكائن من خلال L1، تكون L1_copy قد تغيّرت أيضًا لأنها تشير إلى الكائن نفسه، والعكس صحيح.

لذلك، هذا الكود بالتحديد على اليسار ليس في الحقيقة أفضل من قول for e in L1، لأن L1_copy تشير إلى الكائن نفسه تمامًا في الذاكرة. حسنًا. دعوني أريكم بالضبط ما يعنيه هذا في المخطط الصغير على شكل سحابة الذي كنا نستخدمه.

هذا هو الكود الذي ينشئ اسمًا بديلًا، لا نسخة. لدي L1 تساوي 10, 20, 30, 40. وL2 هي 10, 20, 50, 60، تمامًا كما من قبل. الكود هنا في الأعلى، L1_copy = L1؛ سميتها فقط «نسخة»، لكنه لا ينشئ نسخة فعليًا، صحيح؟

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

إذًا حلقة for e in L1_copy تمرّ على هذا الكائن هنا، الذي تشير إليه L1_copy وL1. حسنًا. فإذا كنت أمرّ عليه وأزيل عناصر أثناء ذلك، فهذا هو الكود الأصلي المعيب الذي كان لدينا، والذي مرّ على L1، صحيح؟

أزيل 10، وأزيد متغير e إلى العنصر التالي، ثم لا أفعل شيئًا لـ30 ولا أفعل شيئًا لـ40. هل هذا منطقي؟ الأسماء البديلة؟ هل هذا واضح؟ حسنًا.

الفكرة الكبيرة التي ستربط أمرين معًا تتعلق بالدوال: المعاملات الشكلية (Formal Parameters) والمعاملات الفعلية (Actual Parameters). عندما نكتب تعريف دالة، تسمى المعاملات داخل تعريف الدالة معاملات شكلية، صحيح؟

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

ها هي دالتنا مرة أخرى. الفرق عما كنا نراه حتى الآن — هذا هو الكود الذي رأيناه للتو. الفرق الذي أحدثته في هذا الكود بالذات هو أنني لم أسمِّ هذا L1 وL2 كما سُمّي هنا في الأعلى، صحيح؟ لأنه ليس من الضروري أن يُسمّى L1 وL2.

صفحة المصدر 9

سميته La وLb. وهذا سيوضح الفكرة. عندما أستدعي دالة remove_duplicates مع La وLb، تأخذ Python هذا الكائن وهذا الكائن وتمرّرهما بوصفهما معاملين.

في مخطط الذاكرة لدي La هي 10, 20, 30, 40، وLb هي 10, 20, 50, 60، صحيح؟ هذا ما لدي هنا في الأسفل. بمجرد أن أستدعي دالتي، تذكروا: تربط Python المعاملات الشكلية بالمعاملات الفعلية.

لكن عندما نتعامل مع هذه الكائنات القابلة للتغيير، تكون L1 وL2 اسمين بديلين للشيئين اللذين يُمرَّران. إذًا ستشير L1 إلى — أخبروني أنتم.

طالب (STUDENT):

La؟

آنا بيل (ANA BELL):

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

طالب (STUDENT):

إذًا هل يعني ذلك أن La وL1 ستكون لهما قيمة ID نفسها؟

آنا بيل (ANA BELL):

ستكون لـLa وL1 قيمتا المعرّف نفسيهما. نعم. نعم. بالضبط. نعم. باستخدام دالة id التي استخدمناها في المرة الماضية. بالضبط. أدعوكم إلى تجربتها أيضًا. لكنني أعتقد أنه ينبغي أن تكون كذلك لأنهما تعدّلان الكائن نفسه.

هل الجميع بخير حتى الآن؟ اسمان، اسمان بديلان للكائن نفسه. ولهذا عندما نغيّر L1 هنا، فإن La وLb اللتين مررناهما ستتغيّران، صحيح؟ وها هي L1_copy أيضًا. فلدي ثلاثة أسماء لهذا الكائن بالتحديد.

ثم نفعل الشيء الذي نغيّر فيه الشيء، صحيح؟ وفي نهاية الدالة، عندما تنتهي، لا يحتوي هذا كله على أي إرجاع. يُرجع None. لكن عندما نطبع La، فالشيء الذي نطبعه هو هذا الكائن هنا. إنه أي شيء تشير إليه La. وهو هذا الشيء الذي تغيّر من خلال L1.

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

حتى الآن، الأمثلة التي نعمل عليها قوائم تحتوي فقط على سلاسل نصية (Strings) أو أعداد صحيحة (Integers) أو أشياء غير قابلة للتغيير (Immutable). لكن ماذا يحدث بالضبط وراء الكواليس عندما تكون لدينا عناصر هي نفسها قابلة للتغيير؟

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

لا يهمني الآن ما العناصر الموجودة في تلك القوائم. كل ما أعرفه هو أنه في المستوى الأعلى (Top Level)، تحتوي old_list على ثلاثة أشياء. حسنًا. لننشئ أسماء بديلة. ثم ننشئ نسخة سطحية (Shallow Copy) من هذه القائمة. ثم ننشئ نسخة عميقة (Deep Copy) منها ونريكم ما يحدث.

في هذا المثال، ما سنفعله هو إنشاء اسم بديل مباشر لـold_list. سنجعل old_list وnew_list اسمين بديلين للكائن نفسه تمامًا، هذا الشيء هنا. أفعل ذلك باستخدام عامل الإسناد العادي فقط.

صفحة المصدر 10

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

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

إذا قلت new_list = old_list، فستنشئ Python اسمًا آخر للشيء نفسه في الذاكرة. عندما أنفّذ هذا السطر هنا، حيث أصل إلى new_list عند الفهرس 2 — إذًا هذا 0, 1, 2 — ثم أتبعها إلى الفهرس 1 هنا، أي هذا هنا، 6، فقد غيّرت السلسلة النصية foo لتصبح 6.

والآن تشير new_list وold_list كلتاهما إلى الكائن نفسه. فتكون قد تغيّرت لتحتوي على 6 تلك في تلك القائمة الفرعية (Sublist). حسنًا. هذا هو إنشاء الأسماء البديلة.

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

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

حسنًا. ما نفعله بهذا الكود بالتحديد هو أننا سننشئ هذه القائمة القديمة هنا. إذًا 1, 2 بوصفهما العنصر الأول، و3, 4 بوصفهما العنصر الثاني، و5, 6 بوصفهما العنصر الأخير.

سننشئ ما يسمى نسخة سطحية. وكان يمكننا أيضًا أن نقول old_list[:]. سيكون ذلك مكافئًا. وفي هذه النسخة السطحية تنشئ Python نسخة من المستوى الأعلى فقط.

لاحظوا: تشير new_list إلى قائمة تحتوي على ثلاثة عناصر، لكن أي شيء في مستوى أعمق من ذلك المستوى الأعلى لا يُنسخ. إذًا كل هذه الأشياء القابلة للتغيير التي هي عناصري — هذه القائمة، وهذه القائمة، وهذه القائمة — هي ثلاثة عناصر قابلة للتغيير.

لا تحصل على نسخ خاصة بها لأننا أنشأنا نسخة سطحية فقط. وما يعنيه هذا هو أن المستوى الأعلى — آسفة. هذا مجرد ما يطبعه. في المستوى الأعلى نستطيع إضافة عناصر إلى old_list، ولن يتداخل ذلك مع المستوى الأعلى لـnew_list.

كمثال هنا، سنضيف القائمة 7, 8 هذه إلى old_list. نتبع old_list، ونضيف عنصرًا آخر إلى نهايتها. حسنًا. ها هو. لكن ذلك العنصر لم يُضف إلى new_list، صحيح؟ لأننا أضفناه فقط إلى المستوى الأعلى لـold_list.

السؤال الآن: ماذا يحدث إذا دخلنا وغيّرنا واحدًا من هذه العناصر الثلاثة المشتركة؟ old_list وnew_list كما نتوقع. لننفّذ عملية إضافية. بالإضافة إلى إلحاق 7 و8 كما نفعل هنا، لنغيّر أيضًا أحد تلك العناصر المشتركة.

ها هو. هذا ما فعلناه للتو في الشريحة السابقة. ها هما 7 و8. والآن لندخل إلى old_list عند الفهرس 1. إذًا 0, 1، هذه الوسطى هنا. وعند الفهرس 1 داخلها — إذًا هذا 0, 1، و4 هنا — لنغيّر 4 إلى 9.

صفحة المصدر 11

حسنًا؟ عندما نطبع new_list، سنطبع قائمة تحتوي على ثلاثة أشياء. الأول هو القائمة 1, 2. والثاني هو 3, 9. غيّرنا ذلك للتو. والأخير هو 5, 6.

وعندما نطبع old_list، ستكون فيها أيضًا 9 هناك لأن تلك العناصر الوسطى مشتركة، لكن سيكون لدينا أيضًا عنصر إضافي في المستوى الأعلى: 7, 8، الذي أضفناه للتو إلى old_list فقط. حسنًا. ما رأيكم في هذا المثال؟ ما الذي يربككم؟ نعم.

طالب (STUDENT):

هل يمكنك شرح لماذا تُضاف 9 إلى القائمة الجديدة؟

آنا بيل (ANA BELL):

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

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

لكن إذا كانت لديك أي عناصر هي نفسها قابلة للتغيير، فلا تحصل على نسخ خاصة بها. إذًا، داخل الذاكرة في الحقيقة، إذا كانت هذه تشير إلى كائن ما، كما تشير إلى تلك القائمة 1, 2، فإن النسخة ستشير أيضًا إلى الكائن الفرعي نفسه، البنية الفرعية (Substructure) نفسها.

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

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

لكن الوسطى، أو أي مستويات تتجاوز المستوى الأعلى، مشتركة. ليست نسخًا. نعم.

طالب (STUDENT):

إذًا إذا عدّلتها من خلال القائمة الجديدة، هل يحدث ذلك في الاتجاهين؟

آنا بيل (ANA BELL):

نعم. نعم. بالضبط، سؤال رائع. إذا عدّلنا العدد 1 هذا من خلال القائمة الجديدة، فنعم. ستظل القائمة القديمة ترى التعديلات لأن كلتيهما تشير إلى هذه الأشياء المشتركة. لكن إذا عدّلت 7 و8، فلن تُعدّلا إلا من خلال old_list، لأن 7, 8 تلك لا تراها إلا old_list.

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

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

الآن، إذا غيّرت old_list لإلحاق 7 و8، فلا يُضاف ذلك إلا إلى old_list. وإذا غيّرت old_list لجعل هذا العنصر 9، فلا يتغيّر ذلك إلا من خلال old_list. إذًا تحتوي old_list على القيم التي تغيّرت، لكن new_list تبقى دون مساس، لأنني أنشأت نسخًا في كل مستوى. نعم.

طالب (STUDENT):

إذًا هل تقوم copy.deepcopy بتنفيذ old_list مع قوسين مربعين بداخلهما النقطتان الرأسيتان في النهاية؟

صفحة المصدر 12

آنا بيل (ANA BELL):

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

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

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

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

كل واحدة من هذه العمليات لها نوع من الأثر الجانبي. ومن المهم التأكد من أنك لا تغيّر شيئًا لا تريد تغييره. حسنًا.

أظن أنه بقي لدي فقط شيء أخير أقوله عن القوائم والصفوف (Tuples). رأينا كليهما. متى تريد استخدام الصفوف لا القوائم؟ عندما تريد شيئًا لا ينبغي تغييره.

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

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

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