عمليات Lists في Python: slicing وcopy والقوائم المتداخلة | أساسيات بايثون 16

عمليات Lists في Python بعد الأساسيات: slicing وcopy والقوائم المتداخلة

إذا كنت تعرف بالفعل كيف تنشئ List في Python، وتستخدم append() وremove() والفهرسة الأساسية، فالخطوة التالية ليست إعادة هذه الأساسيات، بل تعلم العمليات التي تجعل التعامل مع القوائم أكثر مرونة وأمانًا.

في هذا الدرس من بايثون العرب سنركز على عمليات Lists بعد الأساسيات: تقطيع القائمة باستخدام slicing، الفرق بين الربط بـ = والنسخ الحقيقي، القوائم المتداخلة، فك العناصر unpacking، الترتيب والعكس، count() وindex()، وأحد الأخطاء المهمة: تعديل القائمة أثناء المرور عليها بحلقة.

{getToc} $title={محتوى المقال}

{alertInfo} إذا كنت ما زلت تتعلم معنى List وإنشاء القوائم وappend وremove والفهرسة من الصفر، فابدأ أولًا من شرح Lists في Python للمبتدئين. هذه الصفحة مخصصة لما بعد تلك المرحلة.
شرح فكرة القوائم 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] يعيد قائمة جديدة بترتيب معكوس، بينما القائمة الأصلية لا تتغير.

مثال يوضح الوصول إلى عناصر القائمة في بايثون باستخدام الفهرس index بداية من صفر

هل 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.
شرح تعديل وإضافة وحذف عناصر القائمة في بايثون باستخدام append و insert و pop و remove

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)
ملخص أهم عمليات وmethods القوائم في Python مثل sort وreverse وcount وindex

أما إذا كان هدفك الحذف من نفس القائمة عمدًا، فيمكنك أحيانًا المرور على نسخة مثل 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 والنسخ الصحيح والقوائم المتداخلة والترتيب الآمن؛ هذه النقاط هي التي تمنع الأخطاء التي لا تظهر عادة في أمثلة المبتدئين البسيطة.

إرسال تعليق

أحدث أقدم