القوائم — قيم متعددة تحت اسم واحد
الاحتفاظ بأي عدد تريده من القيم تحت اسم واحد، والوصول إليها عبر الفهارس والشرائح، والدوال العضو التي تعدّل القائمة — وفخ ارتباط اسمين بالقائمة نفسها.
كيف تريد أن تتعلم؟
المشكلة التي نقوم بحلها
نريد حفظ أسعار ثلاثة منتجات في متجر. بما تعلمناه حتى الآن، توجد طريقة واحدة فقط:
price_1 = 15
price_2 = 60
price_3 = 850
print(price_1 + price_2 + price_3)925والسؤال الآن: ماذا لو كانت المنتجات أربعين؟ أربعون متغيراً. وسطر جمع من أربعين جزءاً. وإذا وصل منتج جديد إلى المتجر، فعليك أن تفتح الكود وتنشئ متغيراً جديداً.
المشكلة ليست في الجهد فقط، بل في البنية أيضاً. الأسماء price_1 وprice_2 وprice_3 بالنسبة لبايثون لا علاقة بينها إطلاقاً، تماماً كما لا علاقة بين name وage. لكنك تعرف أنها ثلاثة أمثلة لشيء واحد. ولا نملك حتى الآن طريقة لكتابة فكرة “عدة عناصر من الشيء نفسه” في الكود.
القائمة (list) هي هذه الطريقة. اسم واحد، وتحته أي عدد تريده من القيم، مع الحفاظ على ترتيبها.
في نهاية هذا الفصل ستكون قادراً على
- إنشاء قائمة، والوصول إلى ما بداخلها بالفهرس (index) والشريحة (slice)
- تعديل القائمة باستخدام
appendوinsertوremoveوpop - شرح لماذا يمكن تعديل القائمة بينما لا يمكن تعديل السلسلة النصية (string)
- التعرف على أن كتابة
b = aتربط اسمين بالقائمة نفسها، وتجنّب ذلك - قراءة
IndexErrorوValueErrorوفهم ما الخطأ الذي حدث
المتطلبات السابقة: حلقات for ودالة range.
إنشاء قائمة
أقواس مربعة، وبداخلها قيم تفصل بينها فواصل:
prices = [15, 60, 850]
print(prices)
print(len(prices))[15, 60, 850]
3تعمل len() هنا أيضاً، تماماً كما مع السلاسل النصية — ثلاث قيم، إذن 3.
عند الطباعة تظهر القائمة كاملة مع الأقواس المربعة. هذا الشكل ليس لقراءة المستخدم، بل لفهمك أنت: عندما ترى [15, 60, 850] تعرف أنها قائمة، وليست 925.
الوصول إلى ما بداخلها
بالطريقة نفسها التي كان يعمل بها فهرس السلسلة النصية — يبدأ من الصفر، والقيم السالبة تعدّ من النهاية:
prices = [15, 60, 850]
print(prices[0])
print(prices[-1])
print(prices[1:])15
850
[60, 850]الأسطر الثلاثة كلها قواعد الفصل 6، لا شيء جديد. فرق واحد يستحق الانتباه: prices[0] يعطي رقماً، أما prices[1:] فيعطي قائمة — الشريحة تعيد دائماً النوع نفسه الذي قُطعت منه، وليس قطعة مفردة.
الآن الفرق الحقيقي: القائمة قابلة للتعديل
رأينا في الفصل 6 أنه لا يمكن تغيير حرف داخل سلسلة نصية:
name = "rafi"
name[0] = "R"TypeError: 'str' object does not support item assignmentالسطر نفسه يعمل مع القائمة:
prices = [15, 60, 850]
prices[0] = 20
print(prices)[20, 60, 850]لم تُنشأ قائمة جديدة — القائمة نفسها هي التي تغيّرت. هذه أهم خاصية في القائمة، ومنها أيضاً يأتي الفخ الموجود في آخر هذا الفصل.
بالمصطلحات: السلسلة النصية immutable (غير قابلة للتعديل)، والقائمة mutable (قابلة للتعديل).
الإضافة والحذف
stock = ["pen", "notebook"]
stock.append("bag")
stock.insert(1, "eraser")
print(stock)
stock.remove("pen")
print(stock)
last = stock.pop()
print(last)
print(stock)['pen', 'eraser', 'notebook', 'bag']
['eraser', 'notebook', 'bag']
bag
['eraser', 'notebook']أربع دوال عضو (methods)، وأربع مهام مختلفة:
append(x)— تضيف في النهايةinsert(i, x)— تُدخل في الموضعi، وتزيح ما بعده إلى اليمينremove(x)— تبحث بـالقيمة وتحذف أول تطابقpop()— تحذف العنصر الأخير وتعيده
انتبه إلى الفرق بين الأخيرتين، لأن معظم الالتباس هنا: remove تريد أن تعرف ماذا تحذف، وpop تريد أن تعرف من أين. وpop تعطيك العنصر المحذوف، أما remove فلا.
هذه الدوال لا تعيد شيئاً. مع السلسلة النصية كان عليك أن تحتفظ بنتيجةname.upper()، لأنها تنشئ سلسلة جديدة وتعيدها. في القائمة العكس تماماً:appendتعدّل القائمة الأصلية نفسها، وتعيدNone.
لذلك هذا السطر خطأ معروف:
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)Noneضاعت القائمة. اكتب stock.append("bag")، وليس stock = stock.append("bag").
البحث والمرور على العناصر
stock = ["pen", "notebook", "bag"]
print("pen" in stock)
print("chair" in stock)
for item in stock:
print(item.upper())True
False
PEN
NOTEBOOK
BAGكانت in تبحث داخل السلسلة النصية، وهي تبحث داخل القائمة أيضاً — الكلمة نفسها، والعمل نفسه.
وfor item in stock تمر على كل قيمة في القائمة. لاحظ أنه لم نحتج إلى range ولا إلى حساب الفهارس — تماماً كما مررنا على أحرف السلسلة النصية في الفصل 11.
عندما تحتاج إلى الموضع (الفهرس) والقيمة معاً، استخدم enumerate:
stock = ["pen", "notebook"]
for position, item in enumerate(stock, start=1):
print(position, item)1 pen
2 notebookبدون start=1 يبدأ العدّ من الصفر. والقائمة التي تُعرض على الناس تبدأ عادة من واحد.
الفخ الذي يسبب أكثر المتاعب
رأينا في الفصل 4:
a = 3
b = a
b = b + 1
print(a, b)3 4أخذ b قيمة، ولم يكن ينظر إلى a. جرّب الآن الشيء نفسه مع قائمة:
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)[1, 2, 3, 4]
[1, 2, 3, 4]عدّلنا b، لكن a تغيّرت أيضاً.
السبب لا يخالف القاعدة السابقة، بل هو القاعدة نفسها. في السطر b = a أخذ b ما كان في a — وما كان في a هو مكان القائمة، وليس نسخة منها. لذلك يشير الاسمان الآن إلى القائمة نفسها. عدّلت b.append(4) تلك القائمة الوحيدة، ويظهر التعديل نفسه من خلال الاسمين.
لا تلاحظ هذا مع الأرقام، لأن السطر b = b + 1 لا يغيّر الرقم القديم — بل ينشئ رقماً جديداً ويضعه في b. الرقم آمن لأنه لا يمكن تعديله. القائمة يمكن تعديلها، لذلك ليست آمنة بالطريقة نفسها.
إذا أردت نسخة منفصلة فعليك أن تطلبها:
a = [1, 2, 3]
b = a.copy()
b.append(4)
print(a)
print(b)[1, 2, 3]
[1, 2, 3, 4]متى تنتبه. في كل مرة تضع فيها قائمة تحت اسم آخر، أو ترسل قائمة إلى دالة (function) (الفصل 19)، اسأل نفسك مرة واحدة: هل أريد تعديل هذه القائمة نفسها، أم أريد نسخة؟ من لا يطرح هذا السؤال يقضي أياماً في البحث عن أخطاء من نوع “لكنني لم أغيّر شيئاً هنا”.
مثال كامل
shopping.py:
# A shopping list, built up and then summarised
items = []
items.append("pen")
items.append("notebook")
items.append("bag")
items.insert(0, "eraser")
print("List :", items)
print("Count :", len(items))
print("First :", items[0])
print("Last :", items[-1])
print("Has bag :", "bag" in items)
items.remove("pen")
dropped = items.pop()
print()
print("After removing 'pen' and popping the last one:")
print("List :", items)
print("Dropped :", dropped)
print("Count :", len(items))
print()
print("Numbered:")
for position, item in enumerate(items, start=1):
print(f" {position}. {item}")List : ['eraser', 'pen', 'notebook', 'bag']
Count : 4
First : eraser
Last : bag
Has bag : True
After removing 'pen' and popping the last one:
List : ['eraser', 'notebook']
Dropped : bag
Count : 2
Numbered:
1. eraser
2. notebookثلاثة أمور تستحق الانتباه.
البداية بقائمة فارغة. السطر items = [] يشبه المجمّع (accumulator) الموجود خارج الحلقة — العمل الذي كان يقوم به total = 0 في الفصل 10 يقوم به [] هنا. قبل أن تجمع، تحتاج إلى مكان تجمع فيه.
وضعت insert(0, "eraser") العنصر في المقدمة، لذلك جاءت eraser أولاً في القائمة المطبوعة — ترتيب الإضافة ليس هو ترتيب القائمة.
أعادت pop() العنصر المحذوف، لذلك يحتفظ dropped بالقيمة bag. أما remove("pen") فلم تُعد شيئاً، فلم يعد pen موجوداً في أي مكان.
حالات الخطأ الشائعة وحلولها
IndexError: list index out of range طلبت فهرساً خارج عدد القيم الموجودة في القائمة. في قائمة من ثلاث قيم، الفهارس الصحيحة هي 0 و1 و2 — لا يوجد prices[3]. إذا أردت القيمة الأخيرة فإن prices[-1] هو الأكثر أماناً، لأنه لا يحتاج إلى معرفة الطول.
ValueError: list.remove(x): x not in list القيمة التي طلبت حذفها غير موجودة في القائمة. تحقق أولاً بـ if x in stock:، أو استخدم try من الفصل 24.
AttributeError: 'NoneType' object has no attribute 'append' في سطر سابق كُتب stock = stock.append(...)، فأصبح stock الآن None. لا تحتفظ بالنتيجة — stock.append(...) تكفي.
عدّلت قائمة فتغيّرت الأخرى أيضاً الاسمان مرتبطان بالقائمة نفسها. اكتب b = a.copy() وليس b = a.
أحذف داخل الحلقة، فتُتخطى بعض العناصر
numbers = [1, 2, 2, 3, 4]
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers)[1, 2, 3]بقي أحد الرقمين 2. إذا قصّرت القائمة التي تمر عليها، فلا يعود عدّ بايثون متطابقاً مع مواضع القائمة، فتُتخطى بعض القيم. الحل: لا تحذف، بل ضع ما تريد الاحتفاظ به في قائمة جديدة.
numbers = [1, 2, 2, 3, 4]
kept = []
for n in numbers:
if n % 2 != 0:
kept.append(n)
print(kept)[1, 3]سمّيت متغيراً list، والآن لا تعمل list(...) الاسم list اسم خاص ببايثون نفسها. استخدم أسماء ذات معنى مثل items وprices وnames.
الخطوة ٤ من ٦ — التوقع
تحقّق من فهمك
تم حفظ نتيجة append مرة أخرى في stock. ماذا يُطبع؟
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)- ANone
- B['pen', 'notebook', 'bag']
- C['bag']
- Dخطأ `AttributeError`
عُدّل b فقط. ماذا يطبع السطران؟
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)- A[1, 2, 3] [4]
- B[1, 2, 3] [1, 2, 3, 4]
- C[1, 2, 3, 4] [1, 2, 3]
- D[1, 2, 3, 4] [1, 2, 3, 4]
في القائمة ثلاث قيم، والمطلوب الفهرس ثلاثة. ماذا يحدث؟
prices = [15, 60, 850]
print(prices[3])- Aقائمة فارغة
- Bخطأ `IndexError` — الفهارس الصحيحة لثلاث قيم هي `0` و`1` و`2`
- CNone
- D850 — القيمة الأخيرة
الإجابة تتطلب حساباً
سجّل الدخول للتحقق من إجاباتك
الأسئلة موجودة أعلاه، والتفكير في إجاباتها ذهنياً هو الجزء المهم. سجّل الدخول لرؤية الإجابات والتفسيرات والتلميحات ذات المستويات الثلاثة.
دورك الآن
اكتب ملفاً باسم marks.py فيه قائمة بدرجات ستة أشخاص على الأقل.
ثم اطبع:
- القائمة كاملة، وعدد الدرجات فيها
- الدرجة الأولى والأخيرة — استخدم
[-1]للأخيرة - المجموع والمتوسط (اجمعهما بحلقة، لا تكتبهما يدوياً)
- قائمة مرقّمة تبدأ من واحد باستخدام
enumerate - قائمة جديدة بدرجات النجاح (40 أو أكثر)، وعدد ما فيها
ثم قم بتجربتين، وكلتاهما مهمة:
- أضف درجة إلى نهاية القائمة بـ
append، ثم اطبع المجموع مرة أخرى. هل احتجت إلى تغيير سطر المجموع؟ إن لم تحتج، فقد كتبت حساباً حقيقياً. - اكتب
backup = marksثم نفّذmarks.append(100)، واطبع الاثنين. ماذا ترى فيbackup؟ ثم كرّر العمل نفسه باستخدام.copy()ولاحظ الفرق.
التجربة الأخيرة هي أثمن ثلاث دقائق في هذا الفصل. إذا رأيتها بعينك مرة واحدة، فلن تحتاج إلى وقت طويل لاكتشافها لاحقاً.
الخطوة ٦ من ٦
التحدي — اختبار الفصل
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
سجّل الدخول لأداء الاختبار