المحاضرة 10: القوائم وقابلية التغيير (Lists, Mutability)
المحاضرة 10: القوائم وقابلية التغيير (Lists, Mutability)
المصادر والنسبة والترخيص
المادة الأصلية: آنا بيل (Ana Bell)، MIT OpenCourseWare (MIT OCW)، معهد ماساتشوستس للتكنولوجيا، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022 (Fall 2022).
- صفحة المحاضرة الرسمية.
- صفحة الشرائح الرسمية.
- ملف الشرائح الأصلي، PDF.
- ملف شيفرة المحاضرة الأصلي.
- تفريغ المحاضرة على OCW (النسخة الإنجليزية الرسمية).
هذه ترجمة وتكييف عربي غير رسمي وفق رخصة CC BY-NC-SA 4.0: النسبة–غير التجاري–المشاركة بالمثل، ولا تعني اعتمادًا أو تأييدًا من MIT أو MIT OCW. شروط الاستخدام والاستشهاد.
منهج الترجمة: لكل صفحة في ملف الشرائح عنوان مستقل ورقم مطابق. الشرائح المتشابهة التي تتكرّر فيها الشيفرة نفسها مع رسم بياني متغيّر نُقلت كلّها، لأن تغيّر الرسم هو معنى الشريحة. المواضع التي رسم فيها MIT القوائم على شكل صناديق نُقلت إلى جداول تربط كل اسم بقيمته. الشيفرة والشفرة الوهمية محفوظة بالإنجليزية، ولم تُترجَم أسماء الدوال ولا الكلمات المفتاحية ولا أسماء المتغيّرات.
الشريحة 1: القوائم وقابلية التغيير (Lists, Mutability)
نزّل الشرائح وملفات .py لمتابعة الشرح.
6.100L، المحاضرة 10 — آنا بيل (Ana Bell).
الشريحة 2: الفهرسة والترتيب في القوائم (Indices and Ordering in Lists)
a_list = []
L = [2, 'a', 4, [1,2]]
| التعبير | النتيجة |
|---|---|
len(L) |
يُقيَّم إلى 4 |
L[0] |
يُقيَّم إلى 2 |
L[3] |
يُقيَّم إلى [1,2] — أي قائمة أخرى! |
[2,'a'] + [5,6] |
يُقيَّم إلى [2,'a',5,6] |
max([3,5,0]) |
يُقيَّم إلى 5 |
L[1:3] |
يُقيَّم إلى ['a', 4] |
for e in L |
متغيّر الحلقة يأخذ كل عنصر في L بالترتيب |
L[3] = 10 |
يُغيِّر (Mutates) L لتصبح [2,'a',4,10] |
الشريحة 3: قابلية التغيير (Mutability)
- القوائم قابلة للتغيير (Mutable)!
- الإسناد إلى عنصر عند فهرس يغيّر القيمة:
L = [2, 4, 3]
L[1] = 5
- صارت
Lالآن[2, 5, 3]؛ ولاحظ أن هذه هي الكائن نفسهL.
| الاسم | القيمة قبل | القيمة بعد |
|---|---|---|
L |
[2,4,3] |
[2,5,3] |
الشريحة 4: قابلية التغيير — مقارنة
- قارن بين:
- إنشاء
Lبتغيير عنصر (Mutating an Element)، و - إنشاء
tبإنشاء كائن جديد (Creating a New Object).
- إنشاء
L = [2, 4, 3]
L[1] = 5
t = (2, 4, 3)
t = (2, 5, 3)
| الاسم | ملاحظة |
|---|---|
L |
[2,4,3] ثم [2,5,3] — الكائن نفسه تغيّر |
t |
(2,4,3) ثم (2,5,3) — كائن جديد، فالكائن الأول لم يتغيّر |
الشريحة 5: العمليات على القوائم — الإلحاق (Operation on Lists — append)
- أضف عنصرًا إلى نهاية القائمة بـ
L.append(element). - هذا يغيّر القائمة (Mutates the list)!
L = [2,1,3]
L.append(5)
- صارت
Lالآن[2,1,3,5].
| الاسم | القيمة قبل | القيمة بعد |
|---|---|---|
L |
[2,1,3] |
[2,1,3,5] |
الشريحة 6: عملية الإلحاق — خطوة ثانية
نفس الشرح، مع إضافة السطر:
L = [2,1,3]
L.append(5)
L = L.append(5)
| الاسم | القيمة |
|---|---|
L |
[2,1,3,5] |
تنبيه: الأسطر في الشرائح 5 و6 معروضة كمثال على خطأ شائع، والمقصود إظهار أن append تُرجِع None، فالسطر الثالث لا يضيف شيئًا ويكسر الربط.
الشريحة 7: عملية الإلحاق — ما الذي حدث للقائمة؟
نفس الشيفرة. القائمة الفعلية في الذاكرة صارت:
| الاسم | القيمة في الذاكرة | القيمة المربوطة بالاسم |
|---|---|---|
L |
[2,1,3,5,5] |
ما زالت تشير إلى [2,1,3,5] |
الشريحة 8: عملية الإلحاق — القيمة None
نفس الشيفرة. صار الاسم L مربوطًا بالقيمة None، بينما الكائن في الذاكرة هو [2,1,3,5,5].
| الاسم | القيمة |
|---|---|
L |
None |
الشريحة 9: عملية الإلحاق — بإلحاقين منفصلين
نفس الشرح مع الشيفرة الصحيحة التي يوضّحها MIT:
L = [2,1,3]
L.append(5)
L.append(5)
print(L)
| الاسم | القيمة قبل | القيمة بعد |
|---|---|---|
L |
[2,1,3] |
[2,1,3,5,5] |
الشريحة 10: جرّب بنفسك! (YOU TRY IT!)
ما قيم L1 وL2 وL3 وL في النهاية؟
L1 = ['re']
L2 = ['mi']
L3 = ['do']
L4 = L1 + L2
L3.append(L4)
L = L1.append(L3)
الشريحة 11: الفكرة الكبرى (BIG IDEA)
بعض الدوال تُغيّر القائمة ولا تُرجِع شيئًا. نستعمل هذه الدوال لأثرها الجانبي (Side Effect).
الشريحة 12: عملية الإلحاق — ما هي النقطة؟ (What is the dot?)
L = [2,1,3]
L.append(5)
- القوائم كائنات في بايثون، وكل شيء في بايثون كائن.
- الكائنات لها بيانات (Data).
- لأنواع الكائنات عمليات مرتبطة بها (Associated Operations).
- الوصول إلى هذه المعلومة بالصيغة
object_name.do_something(). - هذا يكافئ استدعاء
appendبالوسيطينLو5.
الشريحة 13: جرّب بنفسك! (YOU TRY IT!)
اكتب دالة تحقّق هذه المواصفات:
def make_ordered_list(n):
""" n is a positive int
Returns a list containing all ints in order
from 0 to n (inclusive)
"""
الشريحة 14: جرّب بنفسك! (YOU TRY IT!)
اكتب دالة تحقّق هذه المواصفات:
def remove_elem(L, e):
"""
L is a list
Returns a new list with elements in the same order as L
but without any elements equal to e.
"""
L = [1,2,2,2]
print(remove_elem(L, 2))
# prints [1]
الشريحة 15: من النصوص إلى القوائم (Strings to Lists)
- حوّل نصًّا إلى قائمة بـ
list(s). - كل محرف في
sيصبح عنصرًا في القائمة. - استعمل
s.split()لتقسيم نص على محرف مُمرَّر كمعامل، ويُقسَّم على المسافات إذا استُدعيت دون معامل.
s = "I<3 cs &u?"
L = list(s)
L1 = s.split(' ')
L2 = s.split('<')
| الاسم | القيمة |
|---|---|
s |
نص |
L |
['I','<','3',' ','c','s',' ','&','u','?'] |
L1 |
['I<3','cs','&u?'] |
L2 |
['I', '3 cs &u?'] |
الشريحة 16: من القوائم إلى النصوص (Lists to Strings)
- حوّل قائمة نصوص إلى نص.
- استعمل
''.join(L)لتحويل قائمة نصوص إلى نص أكبر. - يمكن تمرير محرف بين علامتَي تنصيص ليُضاف بين كل عنصرين.
L = ['a','b','c']
A = ''.join(L)
B = '_'.join(L)
C = ''.join([1,2,3])
C = ''.join(['1','2','3'])
| التعبير | النتيجة |
|---|---|
L |
قائمة |
A |
"abc" |
B |
"a_b_c" |
''.join([1,2,3]) |
خطأ (Error) |
C |
"123" — نص! |
ملاحظة المترجم: السطر الرابع في النصّ المستخرَج ناقص قوسًا إغلاقًا؛ اعتُمدت صياغته الصحيحة ''.join(['1','2','3']) لأن الشرح يصف الناتج "123" نصًّا.
الشريحة 17: جرّب بنفسك! (YOU TRY IT!)
اكتب دالة تحقّق هذه المواصفات:
def count_words(sen):
""" sen is a string representing a sentence
Returns how many words are in s (i.e. a word is a
a sequence of characters between spaces. """
print(count_words("Hello it's me"))
الشريحة 18: بعض عمليات القوائم المهمة (A Few Interesting List Operations)
-
إضافة عنصر إلى نهاية القائمة بـ
L.append(element)— تُغيّر القائمة (Mutates the list). -
sort()
L = [4,2,7]
L.sort()
تُغيّر L.
reverse()
L = [4,2,7]
L.reverse()
تُغيّر L.
sorted()
L = [4,2,7]
L_new = sorted(L)
تُرجِع نسخة مرتّبة من L (بلا تغيير في L!).
الشريحة 19: قابلية التغيير — المثال الأول (Mutability)
L=[9,6,0,3]
L.append(5)
a = sorted(L) # returns a new sorted list, does not mutate L
b = L.sort() # mutates L to be [0,3,5,6,9] and returns None
L.reverse() # mutates L to be [9,6,5,3,0] and returns None
| الاسم | القيمة قبل | القيمة بعد |
|---|---|---|
L |
[9,6,0,3] |
[9,6,0,3,5] |
الشريحة 20: قابلية التغيير — تنفيذ sort()
نفس الشيفرة، وبعد تنفيذ b = L.sort():
| الاسم | القيمة |
|---|---|
L |
[0,3,5,6,9] |
الشريحة 21: قابلية التغيير — تنفيذ L.reverse()
نفس الشيفرة. اسم جديد في النطاق العام قيمته None، وL ما زالت [0,3,5,6,9] قبل تنفيذ reverse:
| الاسم | القيمة |
|---|---|
b |
None |
L |
[0,3,5,6,9] |
الشريحة 22: قابلية التغيير — تنفيذ a = sorted(L)
نفس الشيفرة. ظهر اسم جديد a في النطاق العام، وL صارت [9,6,5,3,0] بعد reverse:
| الاسم | القيمة |
|---|---|
a |
[0,3,5,6,9] — القائمة الجديدة المرتّبة |
b |
None |
L |
[9,6,5,3,0] |
ملاحظة المترجم: يبيّن النصّ المستخرَج أن a تحتفظ بنسخة [0,3,5,6,9] من حين استُدعيت sorted، بينما L نفسها صارت [9,6,5,3,0] بعد L.reverse(). هذا هو الفرق الجوهري بين sorted وsort وreverse.
الشريحة 23: جرّب بنفسك! (YOU TRY IT!)
اكتب دالة تحقّق هذه المواصفات:
def sort_words(sen):
""" sen is a string representing a sentence
Returns a list containing all the words in sen but
sorted in alphabetical order. """
print(sort_words("look at this photograph"))
الشريحة 24: الفكرة الكبرى (BIG IDEA)
الدوال ذات الآثار الجانبية (Side Effects) تُغيّر مُدخلاتها. ويمكنك أن تكتب مثلها بنفسك!
الشريحة 25: القوائم تدعم التكرار (Lists Support Iteration)
- لنكتب دالة تُغيّر مُدخلها.
- مثال: تربيع كل عنصر في قائمة، مع تغيير القائمة الأصلية.
def square_list(L):
for elem in L:
# ?? How to do L[index] = the square ??
# ?? elem is an element in L, not the index :(
- الحلول (سنتناول الخيار الثاني، جرّب البقية بنفسك):
- الخيار 1: أنشئ متغيّرًا جديدًا يمثّل الفهرس، يتهيّأ إلى
0قبل الحلقة ويزداد بمقدار1داخل الحلقة. - الخيار 2: مرّ على الفهرس لا على العنصر، واستعمل
L[index]للحصول على العنصر. - الخيار 3: استعمل
enumerateفي حلقةfor(أترك لك البحث عن هذا الخيار)، أيfor i,e in enumerate(L).
- الخيار 1: أنشئ متغيّرًا جديدًا يمثّل الفهرس، يتهيّأ إلى
الشريحة 26: القوائم تدعم التكرار — الحل
def square_list(L):
for i in range(len(L)):
L[i] = L[i]**2
- لاحظ: لا يوجد
return!
الشريحة 27: تتبّع الشيفرة بمثال (Trace the Code with an Example)
def square_list(L):
for i in range(len(L)):
L[i] = L[i]**2
نفترض أن L هي [2,3,4]:
| الخطوة | i |
L بعد التغيير |
|---|---|---|
| الأولى | 0 |
[4, 3, 4] |
| الثانية | 1 |
[4, 9, 4] |
| الثالثة | 2 |
[4, 9, 16] |
الشريحة 28: تتبّع الشيفرة — طباعة القبل والبعد
def square_list(L):
for i in range(len(L)):
L[i] = L[i]**2
Lin = [2,3,4]
print("before fcn call:",Lin) # prints [2,3,4]
square_list(Lin)
print("after fcn call:",Lin) # prints [4,9,16]
الشريحة 29: الفكرة الكبرى (BIG IDEA)
الدوال التي تُغيّر مُدخلها على الأرجح… مرّ على
len(L)لا علىL. وتُرجِعNone، فلا حاجة لحفظ نتيجة استدعاء الدالة.
الشريحة 30: التغيير (Mutation)
- القوائم بنى قابلة للتغيير (Mutable Structures).
- ولها مزايا كثيرة:
- لنفترض أن لدي قائمة طويلة جدًا (مثلًا سجلات موظفين) وأريد تحديث عنصر واحد. بدون التغيير (Mutation) لما كنت مضطرًّا إلى نسخ القائمة كاملة، مع نسخة جديدة من ذلك السجل في الموضع الصحيح. البنية القابلة للتغيير تتيح لي تغيير ذلك العنصر وحده.
- لكن هذه القدرة قد تضيف تحدّيات غير متوقّعة.
ملاحظة المترجم: نُقل المعنى كاملًا، وسُبِقت كلمة «مزايا» بما يقابل advantages في الأصل.
الشريحة 31: لمحة عامة عن الأمثلة الصعبة (Tricky Examples Overview)
- المثال الصعب 1: حلقة تمرّ على فهارس
LوتُغيّرLفي كل مرة (تضيف عناصر أكثر). - المثال الصعب 2: حلقة تمرّ على عناصر
Lمباشرة وتُغيّرLفي كل مرة (تضيف عناصر أكثر). - المثال الصعب 3: حلقة تمرّ على عناصر
Lمباشرة لكنها تُعيد إسنادLإلى كائن جديد في كل مرة. - المثال الصعب 4 (في المحاضرة القادمة): حلقة تمرّ على عناصر
Lمباشرة وتُغيّرLبإزالة عناصر.
الشريحة 32: المثال الصعب 1 — الإلحاق (append)
rangeتُرجِع شيئًا يتصرّف مثل الصف (لكنه ليس صفًّا؛ فهي تُرجِع قيمة قابلة للتكرار — Iterable).- تُرجِع العنصر الأول، وطريقة تكرار (Iteration Method) تُولِّد بها العناصر التالية عند الحاجة.
| التعبير | المقابل التقريبي |
|---|---|
range(4) |
مثل الصف (0,1,2,3) |
range(2,9,2) |
مثل الصف (2,4,6,8) |
L = [1,2,3,4]
for i in range(len(L)):
L.append(i)
print(L)
لأن len(L) تُقيَّم مرة واحدة عند إنشاء كائن range القابل للتكرار، تتوقف الحلقة عند أربع تكرارات:
| التكرار | حالة L |
|---|---|
| الأولى | [1, 2, 3, 4, 0] |
| الثانية | [1, 2, 3, 4, 0, 1] |
| الثالثة | [1, 2, 3, 4, 0, 1, 2] |
| الرابعة | [1, 2, 3, 4, 0, 1, 2, 3] |
الشريحة 33: المثال الصعب 1 — الرسم البياني
نفس الشيفرة، ومعها رسمة الكائنات:
| الكائن/الاسم | ملاحظة |
|---|---|
(0,1,2,3) |
كائن range الذي أنشأته range(len(L)) |
i |
متغيّر الحلقة، قيمته 3 في التكرار الأخير |
L |
[1,2,3,4,0,1,2,3] |
| الكائن الفعلي في الذاكرة | [1,2,3,4,0,1,2,3] |
ملاحظة المترجم: الشرائح تعرض القيم على جانبي السطر: قائمة في الذاكرة وقيمة الاسم الذي يشير إليها. نُقلت الجداول أعلاه، وسطرا القائمة المبدئية [1,2,3,4] والقائمة النهائية [1,2,3,4,0,1,2,3] كلاهما حاضران في النصّ المستخرَج.
الشريحة 34: المثال الصعب 2 — الإلحاق (append)
- يبدو مشابهًا لكن…
L = [1,2,3,4]
i = 0
for e in L:
L.append(i)
i += 1
print(L)
- في المثال السابق، كان
Lيُستعمَل عند البداية لإنشاءrangeقابل للتكرار؛ وفي هذا المثال، الحلقة تصل مباشرة إلى فهارسL.
| التكرار | e |
حالة L |
|---|---|---|
| الأولى | 1 |
[1, 2, 3, 4, 0] |
| الثانية | 2 |
[1, 2, 3, 4, 0, 1] |
| الثالثة | 3 |
[1, 2, 3, 4, 0, 1, 2] |
| الرابعة | 4 |
[1, 2, 3, 4, 0, 1, 2, 3] |
لا تتوقّف أبدًا! (NEVER STOPS!)
ملاحظة المترجم: جدول القيم لـe أعلاه مستخرج من الأرقام الظاهرة في شريحة الرسم البياني لهذه الشريحة. والنتيجة النهائية للحلقة لا تنتهي لأن len القائمة تتزايد في كل دورة؛ وهذا هو سبب تسميتها «المثال الصعب 2» في الشريحة 31.
الشريحة 35: دمج القوائم (Combining Lists)
- الدم (Concatenation) عبر المشغّل
+ينشئ قائمة جديدة بنسخ (With Copies). - لتغيير القائمة استعمل
L.extend(some_list)(نسخة منsome_list).
L1 = [2,1,3]
L2 = [4,5,6]
L3 = L1 + L2
L3هي[2,1,3,4,5,6].
| الاسم | القيمة |
|---|---|
L1 |
[2,1,3] |
L2 |
[4,5,6] |
L3 |
[2,1,3,4,5,6] |
الشريحة 36: دمج القوائم — استعمال extend
نفس الشيفرة، مع:
L1.extend([0,6])
L3ما زالت[2,1,3,4,5,6].- صارت
L1هي[2,1,3,0,6].
| الاسم | القيمة |
|---|---|
L1 |
[2,1,3,0,6] |
L2 |
[4,5,6] |
L3 |
[2,1,3,4,5,6] |
الشريحة 37: دمج القوائم — قائمة داخل قائمة
نفس الشيفرة، مع:
L1.extend([0,6])
L2.extend([[1,2],[3,4]])
- صارت
L2هي[4,5,6,[1,2],[3,4]].
| الاسم | القيمة |
|---|---|
L1 |
[2,1,3,0,6] |
L2 |
[4,5,6,[1,2],[3,4]] |
L3 |
[2,1,3,4,5,6] |
الشريحة 38: المثال الصعب 3 — الدمج (combining)
L = [1,2,3,4]
for e in L:
L = L + L
print(L)
| التكرار | L الجديدة |
|---|---|
| الأولى | [1, 2, 3, 4, 1, 2, 3, 4] |
| الثانية | [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] |
| الثالثة | 32 عنصرًا: تكرار 1, 2, 3, 4 ثماني مرات |
| الرابعة | 64 عنصرًا: تكرار 1, 2, 3, 4 ستَّ عشرة مرة |
الشريحة 39: المثال الصعب 3 — الرسم البياني بعد التكرار الأول
نفس الشيفرة. القيم بعد التكرار الأول:
| الاسم | القيمة |
|---|---|
e |
1 |
L (القائمة القديمة في الذاكرة) |
[1,2,3,4] |
L (القيمة الجديدة) |
[1,2,3,4,1,2,3,4] |
الشريحة 40: المثال الصعب 3 — الرسم البياني بعد التكرار الثاني
نفس الشيفرة. القيم بعد التكرار الثاني:
| الاسم / الكائن | القيمة |
|---|---|
e |
1 |
| القائمة القديمة في الذاكرة | [1,2,3,4] |
L الحالية |
[1,2,3,4,1,2,3,4] |
L الجديدة |
16 عنصرًا: تكرار 1,2,3,4 أربع مرات |
الشريحة 41: المثال الصعب 3 — الرسم البياني بعد التكرار الثالث
نفس الشيفرة. القيم بعد التكرار الثالث:
| الاسم / الكائن | القيمة |
|---|---|
e |
1 |
L الحالية |
16 عنصرًا: تكرار 1,2,3,4 أربع مرات |
L الجديدة |
32 عنصرًا: تكرار 1,2,3,4 ثماني مرات |
الشريحة 42: المثال الصعب 3 — الرسم البياني بعد التكرار الرابع
نفس الشيفرة. القيم بعد التكرار الرابع:
| الاسم / الكائن | القيمة |
|---|---|
L الحالية |
32 عنصرًا: تكرار 1,2,3,4 ثماني مرات |
L الجديدة |
64 عنصرًا: تكرار 1,2,3,4 ستَّ عشرة مرة |
الشريحة 43: إفراغ قائمة والتأكّد من أنها الكائن نفسه (Empty Out a List and Checking That It's the Same Object)
- يمكنك تغيير قائمة لإزالة كل عناصرها.
- هذا لا ينشئ قائمة فارغة جديدة!
استعمل L.clear().
كيف تتحقّق من أنها الكائن نفسه في الذاكرة؟ استعمل الدالة id().
جرّب هذا في الطرفية (Console):
>>> L = [4,5,6]
>>> L.append(8)
>>> id(L)
>>> L.clear()
>>> L = []
>>> id(L)
ملاحظة المترجم: الشريحة تعرض id(L) بعد كل خطوة من هذه السلسلة؛ والأرقام المعروضة لقيم id في الأصل لم يحفظها النصّ المستخرَج، ولذلك لم تُنقل هنا ولم يُخمَّن شيء عنها. الفكرة التي يعلّمها المثال أن L.clear() يُبقي id(L) كما هو، بينما L = [] يعطي id مختلفًا.
الشريحة 44: الخلاصة (SUMMARY)
- القوائم والصفوف توفّر طريقة لتنظيم البيانات تدعم طبيعيًّا الدوال التكرارية (Iterative Functions).
- الصفوف غير قابلة للتغيير (Immutable) مثل النصوص:
- الصفوف مفيدة حين تكون لديك بيانات لا تحتاج إلى تغيير، مثل
(خط العرض، خط الطول)أو(رقم الصفحة، رقم السطر).
- الصفوف مفيدة حين تكون لديك بيانات لا تحتاج إلى تغيير، مثل
- القوائم قابلة للتغيير (Mutable):
- يمكنك تعديل الكائن بتغيير عنصر عند فهرس.
- يمكنك تعديل الكائن بإضافة عناصر إلى النهاية.
- سنرى عمليات كثيرة أخرى على القوائم في المحاضرة القادمة.
- القوائم مفيدة في الحالات الديناميكية، مثل قائمة أفضل 40 أغنية يوميًا أو قائمة الأفلام التي شُوهدت مؤخّرًا.
الشريحة 45: MITOpenCourseWare
https://ocw.mit.edu
مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022.
للاطّلاع على كيفية الاستشهاد بهذه المواد أو على شروط الاستخدام: https://ocw.mit.edu/terms.