القوائم — قيم متعددة تحت اسم واحد
الاحتفاظ بأي عدد تريده من القيم تحت اسم واحد، والوصول إليها عبر الفهارس والشرائح، ودوال تعديل القوائم — مع تجنب فخ ربط اسمين بنفس القائمة.
- 1المشكلة
- 2الفهم
- 3أمثلة محلولة
- 4التوقع
- 5التطبيق
- 6التحدي
المشكلة التي نقوم بحلها
يحتاج متجر ما إلى تخزين أسعار ثلاثة منتجات. وفقاً لما تعلمناه حتى الآن، لا توجد سوى طريقة واحدة للقيام بذلك:
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) هي الحل الأمثل لذلك. اسم واحد، يحتوي على أي عدد تريده من القيم مرتبة داخله.
في نهاية هذا الدرس ستكون قادراً على
- إنشاء قائمة والوصول إلى عناصرها باستخدام الفهارس والشرائح
- تعديل القائمة باستخدام الدوال
appendوinsertوremoveوpop - شرح لماذا يمكن تعديل القائمة بينما لا يمكن تعديل النص
- التعرف على فخ
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.
الوصول إلى العناصر
تعمل الفهرسة (Indexing) بنفس الطريقة تماماً كما كانت في النصوص — يبدأ العدّ من الصفر، والأرقام السالبة تبدأ العد من النهاية إلى الخلف:
prices = [15, 60, 850]
print(prices[0])
print(prices[-1])
print(prices[1:])15
850
[60, 850]الأسطر الثلاثة تتبع قواعد الدرس السادس ذاتها دون تغيير. وهناك فارق واحد يستحق الملاحظة: التعبير prices[0] يعيد رقماً، بينما التعبير prices[1:] يعيد قائمة. فشريحة الاقتطاع تُرجع دائماً كائناً من نفس النوع الذي اقتُطعت منه، ولا ترجع عنصراً مفرداً مجرداً.
وهنا يكمن الفارق الحقيقي: القائمة قابلة للتعديل
في الدرس السادس رأينا أنه لا يمكن استبدال حرف داخل نص:
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']أربع دوال لأربع مهام مختلفة:
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 ولا حسابات فهارس، تماماً كما كنا نمر على أحرف الكلمة في الدرس الحادي عشر.
وعندما تحتاج إلى موضع الفهرس والقيمة معاً، استخدم enumerate:
stock = ["pen", "notebook"]
for position, item in enumerate(stock, start=1):
print(position, item)1 pen
2 notebookمن دون start=1 سيبدأ العدّ من الصفر. وعند عرض قائمة للمستخدم، نفضل عادة أن يبدأ الترقيم من الرقم واحد.
الفخ الذي يقع فيه الجميع
في الدرس الرابع رأينا هذا المثال:
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]متى يجب أن تنتبه؟ في أي وقت تضع فيه قائمة تحت اسم متغير ثانٍ، أو تمرر قائمة إلى دالة (كما سنرى في الدرس التاسع عشر)، اسأل نفسك مرة واحدة: هل أريد تعديل هذه القائمة الأصلية نفسها، أم أريد نسخة مستقلة عنها؟ المبرمجون الذين لا يطرحون هذا السؤال على أنفسهم يقضون أياماً طويلة في تتبع أخطاء غامضة تحت عنوان "لكنني لم أغير أي شيء هنا!".
مثال متكامل
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 = [] يؤدي نفس الدور الذي كان يقوم به المجمّع خارج الحلقة في الدرس العاشر — فكما وضعنا total = 0 سابقاً، نضع الآن []. فأنت بحاجة إلى وعاء تجميع قبل أن تبدأ في جمع العناصر.
الأمر 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 التي سنتعلمها في الدرس الرابع والعشرين.
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.
Step 4 of 6 — Predict
Check your understanding
The result of append is stored back into stock. What is printed?
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)- ANone
- B['pen', 'notebook', 'bag']
- C['bag']
- DAn `AttributeError`
Only b was changed. What do the two lines print?
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)- A[1, 2, 3, 4] [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]
The list holds three values and index three is asked for. What happens?
prices = [15, 60, 850]
print(prices[3])- AAn `IndexError` — with three values the valid indexes are `0`, `1`, `2`
- B850 — the last value
- CNone
- DAn empty list
Answering needs an account
Sign in to check your answers
The questions are above, and working them out in your head is the part that matters. Sign in to see the answers, the explanations and the three-level hints.
دورك الآن
اكتب ملفاً باسم marks.py يحتوي على قائمة تضم ست درجات على الأقل.
ثم اطبع:
- القائمة كاملة، وعدد الدرجات الموجودة فيها
- الدرجة الأولى والدرجة الأخيرة — استخدم
[-1]للأخيرة - المجموع والمتوسط الحسابي، محسوبين باستخدام حلقة تكرارية وليس بالحساب اليدوي
- قائمة مرقمة باستخدام
enumerate، تبدأ بالرقم واحد - قائمة جديدة تضم فقط درجات النجاح (40 فما فوق)، وعدد هذه الدرجات الناجحة
ثم أجرِ هاتين التجربتين، وكلاهما في غاية الأهمية:
- استخدم
appendلإضافة درجة أخرى في نهاية القائمة ثم اطبع المجموع مرة أخرى. هل اضطررت لتعديل سطر الحساب؟ إذا لم تضطر لذلك، فهذا يعني أنك كتبت كود حساب حقيقي ومرن. - اكتب
backup = marks، ثم اكتبmarks.append(100)، واطبع كلاً منهما. ماذا تجد داخلbackup؟ كرر نفس التجربة الآن باستخدام.copy()ولاحظ الفرق بنفسك.
هذه التجربة الأخيرة هي أثمن ثلاث دقائق ستقضيها في هذا الدرس. فرؤيتها بعينيك مرة واحدة تعفيك من إضاعة ساعات في تتبع أخطاء الذاكرة لاحقاً.
Step 6 of 6
التحدي — the chapter quiz
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
Sign in to take the quiz