مستوى المقال: مبتدئ. مناسب لأي حد كتب كام سطر بايثون أو جافاسكريبت وبيتعامل مع القوائم والكائنات، ولسه ماخدش باله من فخ المرجع المشترك.
النسخ السطحي والعميق: ليه تعديل النسخة بيغيّر الأصل
لو عملت نسخة من قائمة، عدّلت النسخة، ولقيت الأصل اتغيّر معاها، إنت مش بتتعامل مع نسختين. إنت بتتعامل مع نفس الكائن بمرجعين. المقال ده هيوريك السبب في دقيقتين، وإزاي تعمل نسخة حقيقية مستقلة بسطر واحد.
المشكلة باختصار
في بايثون، لما تكتب b = a إنت مش بتعمل نسخة. إنت بتدّي نفس القائمة اسم تاني. حتى a.copy() بيعمل نسخة، لكن نسخة سطحية بتشارك الكائنات المتداخلة جواها. النتيجة: تعديل عنصر متداخل في النسخة بيظهر في الأصل. الباج ده بيعدّي بسهولة في الكود وبيطلع في وقت متأخر لما البيانات تبقى متشابكة.
المفهوم بمثال بسيط الأول
تخيل عندك درج فيه ورق مهم. فيه تلات طرق تدّي حد حق الوصول له:
- الإسناد (
b = a): تدّيه مفتاح لنفس الدرج. أي ورقة يعدّلها بتتعدّل عندك، لأنه نفس الدرج. - النسخ السطحي (
a.copy()): تجيب درج جديد، بس تحط فيه نفس الملفات الأصلية اللي جوه الدرج القديم. الدرج مختلف، لكن الملفات مشتركة. يعدّل ورقة جوه ملف، تتعدّل عندك. - النسخ العميق (
copy.deepcopy(a)): تصوّر كل ورقة وكل ملف من الأول، وتحطهم في درج جديد. دلوقتي بقى عندك نسختين مستقلتين تمامًا. يعدّل زي ما هو عايز، ورقك ميتلمسش.
الخلاصة من المثال: النسخ السطحي بينسخ الطبقة الأولى بس. أي حاجة متداخلة (قائمة جوه قائمة، قاموس جوه قائمة) بتفضل مشتركة بين الأصل والنسخة.
نفس الكلام علميًا
في بايثون، المتغير مش بيخزّن القيمة نفسها، بيخزّن مرجع (عنوان) لكائن في الذاكرة. b = a بينسخ المرجع، فالاتنين بيأشروا على نفس الكائن. copy() وcopy.copy() بيعملوا كائن خارجي جديد، لكن بيملوه بنفس مراجع العناصر الداخلية. عشان كده الكائنات المتداخلة بتفضل مشتركة. copy.deepcopy() بيمشي على الشجرة كلها ويعيد بناء كل كائن متداخل بمرجع جديد، فمفيش أي مشاركة.
الكود اللي بيوريك الباج
import copy
a = [[1, 2], [3, 4]]
b = a.copy() # نسخة سطحية
b[0].append(99) # بنعدّل عنصر متداخل في النسخة
print(a) # [[1, 2, 99], [3, 4]] <-- الأصل اتغيّر!
c = copy.deepcopy(a) # نسخة عميقة
c[0].append(777) # نعدّل النسخة العميقة
print(a) # [[1, 2, 99], [3, 4]] <-- الأصل زي ما هو