إذا كنت تعرف بالفعل كيف تنشئ List في Python، وتستخدم append() وremove() والفهرسة الأساسية، فالخطوة التالية ليست إعادة هذه الأساسيات، بل تعلم العمليات التي تجعل التعامل مع القوائم أكثر مرونة وأمانًا.
في هذا الدرس من بايثون العرب سنركز على عمليات Lists بعد الأساسيات: تقطيع القائمة باستخدام slicing، الفرق بين الربط بـ = والنسخ الحقيقي، القوائم المتداخلة، فك العناصر unpacking، الترتيب والعكس، count() وindex()، وأحد الأخطاء المهمة: تعديل القائمة أثناء المرور عليها بحلقة.
{getToc} $title={محتوى المقال}
{alertInfo} إذا كنت ما زلت تتعلم معنى List وإنشاء القوائم وappend وremove والفهرسة من الصفر، فابدأ أولًا من شرح Lists في Python للمبتدئين. هذه الصفحة مخصصة لما بعد تلك المرحلة.
1. تقطيع القوائم باستخدام slicing
الـ slicing يسمح لك بأخذ جزء من القائمة بدل الوصول إلى عنصر واحد فقط. الصيغة العامة هي:
list[start:stop:step]
القيمة عند start تدخل في النتيجة، أما stop فلا تدخل. ويمكن حذف أحد الأجزاء واستخدام القيم الافتراضية.
numbers = [10, 20, 30, 40, 50, 60]
print(numbers[1:4])
print(numbers[:3])
print(numbers[3:])
print(numbers[::2])
print(numbers[-3:])
print(numbers[::-1])
الناتج:
[20, 30, 40]
[10, 20, 30]
[40, 50, 60]
[10, 30, 50]
[40, 50, 60]
[60, 50, 40, 30, 20, 10]
لاحظ أن numbers[::-1] يعيد قائمة جديدة بترتيب معكوس، بينما القائمة الأصلية لا تتغير.
هل slicing ينشئ قائمة جديدة؟
نعم، عند تقطيع List تحصل على قائمة خارجية جديدة. لذلك يستخدم بعض المبرمجين items[:] لعمل نسخة سطحية من القائمة. لكن كلمة "سطحية" مهمة جدًا إذا كانت القائمة تحتوي قوائم أخرى بداخلها، وسنرى ذلك بعد قليل.
{alertSuccess} احفظ هذه القاعدة:items[start:stop:step]. وإذا كانstepسالبًا يمكنك المرور بالعكس، مثلitems[::-1].
2. الفرق بين = وcopy() عند نسخ List
هذه من أهم النقاط بعد تعلم القوائم. السطر التالي لا ينشئ نسختين مستقلتين:
original = [1, 2, 3]
linked = original
linked.append(4)
print(original)
print(linked is original)
الناتج يوضح أن linked وoriginal يشيران إلى نفس كائن القائمة:
[1, 2, 3, 4]
True
إذا أردت قائمة خارجية جديدة، استخدم copy() أو [:]:
original = [1, 2, 3]
copied = original.copy()
copied.append(4)
print(original)
print(copied)
print(copied is original)
هنا إضافة 4 إلى النسخة لا تغير القائمة الأصلية، لأننا أنشأنا List خارجية جديدة.
ما معنى shallow copy؟
list.copy() تنشئ Shallow Copy. هذا يكفي غالبًا مع القوائم البسيطة، لكن إذا كانت القائمة تحتوي عناصر قابلة للتعديل مثل Lists داخلية، فهذه العناصر الداخلية تظل مشتركة بين النسختين.
original = [[1, 2], [3, 4]]
copied = original.copy()
copied[0].append(99)
print(original)
print(copied)
الناتج:
[[1, 2, 99], [3, 4]]
[[1, 2, 99], [3, 4]]
إذا كنت تحتاج نسخة مستقلة حتى من العناصر الداخلية، استخدم copy.deepcopy() عند الحاجة:
import copy
original = [[1, 2], [3, 4]]
independent = copy.deepcopy(original)
independent[0].append(99)
print(original)
print(independent)
{alertWarning} لا تعتبرb = aعملية نسخ للقائمة. إنها تربط اسمًا جديدًا بالكائن نفسه. وcopy()نفسها نسخة سطحية وليست نسخًا عميقًا للعناصر المتداخلة.
3. القوائم المتداخلة Nested Lists
القائمة المتداخلة هي List تحتوي Lists أخرى. هذا الشكل مفيد لتمثيل صفوف وأعمدة بسيطة أو بيانات مجمعة.
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[1])
print(matrix[1][2])
matrix[0][1] = 20
print(matrix[0])
في matrix[1][2]، الفهرس الأول يحدد القائمة الداخلية، والثاني يحدد العنصر داخلها.
فخ شائع عند إنشاء عدة قوائم داخلية
لا تستخدم [[]] * 3 عندما تريد ثلاث قوائم داخلية مستقلة؛ لأنك ستكرر المرجع إلى القائمة الداخلية نفسها:
rows = [[]] * 3
rows[0].append("A")
print(rows)
safe_rows = [[] for _ in range(3)]
safe_rows[0].append("A")
print(safe_rows)
الناتج:
[['A'], ['A'], ['A']]
[['A'], [], []]
استخدام List Comprehension في المثال الثاني ينشئ قائمة داخلية منفصلة في كل مرة.
4. فك عناصر القائمة باستخدام unpacking
يمكنك توزيع عناصر List على متغيرات في سطر واحد:
fruits = ["apple", "banana", "orange"]
first, second, third = fruits
print(first)
print(second)
print(third)
إذا كان عدد المتغيرات لا يطابق عدد العناصر، فسيظهر ValueError. وعندما لا تعرف عدد العناصر الوسطى، استخدم النجمة *:
numbers = [10, 20, 30, 40, 50]
first, *middle, last = numbers
print(first)
print(middle)
print(last)
وهنا ستكون قيمة middle هي [20, 30, 40].
5. الفرق بين sort وreverse وsorted
sort() وreverse() يعدلان القائمة الأصلية مباشرة، لكنهما لا يفعلان الشيء نفسه. sort() يرتب القيم، بينما reverse() يقلب الترتيب الحالي فقط.
| العملية | ماذا تفعل؟ | هل تعدل الأصل؟ | القيمة المعادة |
|---|---|---|---|
items.sort() |
ترتب العناصر تصاعديًا افتراضيًا | نعم | None |
items.sort(reverse=True) |
ترتب العناصر تنازليًا | نعم | None |
items.reverse() |
تعكس الترتيب الحالي فقط | نعم | None |
sorted(items) |
تعيد قائمة مرتبة جديدة | لا | List جديدة |
numbers = [3, 1, 4, 2]
sorted_numbers = sorted(numbers, reverse=True)
print(numbers)
print(sorted_numbers)
numbers.reverse()
print(numbers)
إذا أردت الحفاظ على القائمة الأصلية، فغالبًا sorted() أو slicing بالعكس أنسب من تعديل الأصل مباشرة.
6. استخدام count وindex في List
الطريقة count() تحسب عدد مرات ظهور قيمة، بينما index() تعيد فهرس أول ظهور لهذه القيمة.
names = ["Ali", "Sara", "Ali", "Omar"]
print(names.count("Ali"))
if "Sara" in names:
print(names.index("Sara"))
في المثال السابق، count("Ali") تعيد 2، بينما index("Sara") تعيد 1.
{alertInfo} إذا لم يجدindex()القيمة، فإنه يرفعValueError. إذا كان العنصر قد لا يكون موجودًا، تحقق أولًا باستخدامin.
7. لا تعدّل طول List أثناء المرور عليها مباشرة
من الأخطاء المربكة حذف عناصر من القائمة نفسها أثناء حلقة for. عند الحذف تتحرك مواقع العناصر، بينما الحلقة تستمر إلى الفهرس التالي، وقد تتجاوز بعض العناصر.
numbers = [2, 4, 6, 8]
for number in numbers:
numbers.remove(number)
print(numbers)
قد تتوقع حذف كل الأرقام، لكن الناتج هو:
[4, 8]
الطريقة الأكثر وضوحًا عند الفلترة هي إنشاء قائمة جديدة:
numbers = [2, 4, 6, 8]
numbers = [number for number in numbers if number % 2 != 0]
print(numbers)
وإذا كنت تحتاج تعديل القيم نفسها دون تغيير عدد العناصر، فاستخدم enumerate() والفهرس:
numbers = [1, 2, 3, 4]
for index, value in enumerate(numbers):
numbers[index] = value * 10
print(numbers)
أما إذا كان هدفك الحذف من نفس القائمة عمدًا، فيمكنك أحيانًا المرور على نسخة مثل for item in items.copy(): ثم تعديل الأصل. لكن إنشاء قائمة جديدة غالبًا أوضح وأقل عرضة للأخطاء.
8. مثال يجمع Nested Lists وunpacking وsorted
في المثال التالي نخزن اسم الطالب ودرجته داخل List متداخلة، ثم ننشئ ترتيبًا جديدًا دون تغيير القائمة الأصلية، ونستخدم unpacking أثناء الحلقة:
scores = [
["Ali", 88],
["Sara", 95],
["Omar", 81]
]
top_scores = sorted(scores, key=lambda row: row[1], reverse=True)
for name, score in top_scores:
print(name, score)
هذا النوع من الأمثلة يوضح لماذا تصبح العمليات السابقة مفيدة عندما تنتقل من List بسيطة إلى بيانات أكثر تنظيمًا.
جدول سريع: أي عملية أستخدم؟
| إذا كنت تريد... | استخدم |
|---|---|
| أخذ جزء من القائمة | slicing |
| نسخ List بسيطة دون ربطها بالأصل | list.copy() أو [:] |
| نسخ بنية متداخلة بالكامل | copy.deepcopy() عند الحاجة |
| توزيع العناصر على متغيرات | unpacking |
| ترتيب القائمة الأصلية | sort() |
| إنشاء قائمة مرتبة جديدة | sorted() |
| قلب الترتيب الحالي | reverse() أو [::-1] |
| معرفة عدد التكرارات | count() |
| معرفة أول موضع لقيمة | index() |
أخطاء شائعة بعد تعلم أساسيات Lists
- استخدام
b = aثم توقع أن القائمتين مستقلتان. - الاعتقاد أن
copy()تنسخ القوائم الداخلية نسخًا عميقًا. - كتابة
new_list = old_list.sort()ثم الحصول علىNone. - الخلط بين
sort(reverse=True)وreverse(). - استخدام
index()لقيمة قد لا تكون موجودة دون التحقق منها. - حذف عناصر من نفس List أثناء المرور عليها مباشرة بحلقة
for.
إذا كان ما تحتاجه تحديدًا هو الفرق بين append() وextend()، فراجع الشرح المخصص للفرق بين append وextend في Python بدل توسيع هذا المقال إلى أساسيات الإضافة مرة أخرى.
أسئلة شائعة
ما الفرق بين = وcopy() في List Python؟
= لا تنسخ القائمة، بل تجعل متغيرين يشيران إلى الكائن نفسه. أما copy() فتنشئ List خارجية جديدة، لكنها Shallow Copy.
هل slicing ينشئ نسخة جديدة من القائمة؟
نعم، slicing يعيد List جديدة. واستخدام items[:] طريقة شائعة لعمل Shallow Copy للقائمة.
ما الفرق بين shallow copy وdeep copy؟
الـ shallow copy تنسخ الحاوية الخارجية فقط وتشارك المراجع للعناصر الداخلية القابلة للتعديل، بينما deepcopy() ينسخ البنية المتداخلة بصورة مستقلة.
ما الفرق بين sort(reverse=True) وreverse()؟
sort(reverse=True) يرتب القيم ترتيبًا تنازليًا، أما reverse() فيقلب الترتيب الحالي دون أن يرتب القيم.
هل تعديل List أثناء loop آمن؟
تغيير طول القائمة التي تمر عليها مباشرة قد يؤدي إلى تخطي عناصر أو سلوك مربك. عند الفلترة أنشئ قائمة جديدة، أو مر على نسخة إذا كان تعديل الأصل ضروريًا.
مصادر للتوسع
الخلاصة
بعد إتقان إنشاء List والإضافة والحذف والفهرسة الأساسية، تأتي العمليات التي تمنع كثيرًا من الأخطاء وتجعل الكود أوضح: استخدم slicing لاستخراج أجزاء من القائمة، وافهم أن = لا ينسخ List، وأن copy() نسخة سطحية، وتعلم التعامل مع Nested Lists وunpacking والفرق بين sort() وreverse().
والقاعدة الأهم عند الحلقات: إذا كان هدفك حذف أو فلترة عناصر، لا تغيّر طول القائمة نفسها أثناء المرور عليها مباشرة. أنشئ نتيجة جديدة أو مر على نسخة عندما يكون ذلك هو السلوك المطلوب.
{alertSuccess} الخلاصة العملية: بعد أساسيات Lists، ركز على slicing والنسخ الصحيح والقوائم المتداخلة والترتيب الآمن؛ هذه النقاط هي التي تمنع الأخطاء التي لا تظهر عادة في أمثلة المبتدئين البسيطة.