مستوى المقال: مبتدئ. لو لسه بادئ في بايثون أو جافاسكريبت وحصلك إن تعديل نسخة غيّر الأصل، المقال ده ليك.
النسخ السطحي مقابل النسخ العميق: ليه تعديل النسخة بيغيّر الأصل
لو نسخت قائمة أو object وعدّلت في النسخة، فلقيت الأصل اتغيّر لوحده، ده مش bug في اللغة. ده اسمه النسخ السطحي (Shallow Copy). بعد المقال ده هتعرف الفرق بينه وبين النسخ العميق (Deep Copy)، وتعرف تختار الصح في سطر واحد.
المشكلة باختصار
الموقف اللي بيلخبط أغلب المبتدئين: بتاخد نسخة من object فيه بيانات متداخلة، تعدّل في النسخة، وتتفاجأ إن الأصل اتعدّل معاها. النتيجة أخطاء غريبة في بيانات المستخدم أو الإعدادات، ومحدش عارف مين غيّرها. المشكلة مش عشوائية، ولها سبب دقيق وحل بسطر.
خليها بمثال: ماكينة التصوير
تخيّل معاك ورقة فيها اسمك، وفي آخرها ملاحظة مكتوب فيها: قائمة مهاراتي موجودة في الدرج رقم 3. روحت صوّرت الورقة. الصورة دلوقتي فيها نفس الاسم، وفيها نفس الجملة بالظبط: الدرج رقم 3.
دلوقتي أي حد يفتح الدرج 3 ويضيف مهارة، الأصل والصورة الاتنين هيشوفوا التعديل، لأن الاتنين بيشاوروا على نفس الدرج. ده بالظبط النسخ السطحي: نسخت الورقة، بس ما نسختش الدرج. النسخ العميق معناه إنك تعمل درج جديد مستقل للصورة وتنقل محتواه جواه، فتعديل درج الصورة ميلمسش الأصل.
ليه بيحصل ده؟ (الشرح العلمي)
في بايثون وجافاسكريبت، المتغير اللي ماسك قائمة أو object مابيخزّنش القيمة نفسها، بيخزّن مرجع (reference) يعني عنوان المكان اللي فيه البيانات في الذاكرة. لما تعمل نسخة سطحية، بتتنسخ القيم في المستوى الأول بس. أي قيمة متداخلة (قائمة جوه object مثلاً) بيتنسخ منها المرجع، يعني النسختين بيشاوروا على نفس المصفوفة الأصلية.
ملاحظة مهمة: الكلام ده على الأنواع المرجعية زي list و dict و object. الأنواع البسيطة زي الأرقام والنصوص (int، str، bool) بتتنسخ بقيمتها، فمفيش مشكلة معاها أصلاً.
شوفها بنفسك: مثال بايثون
import copy
original = {"name": "Ahmed", "skills": ["Python", "SQL"]}
# shallow copy
shallow = dict(original)
shallow["skills"].append("Go")
print(original["skills"]) # ['Python', 'SQL', 'Go'] the original changed!
لاحظ إننا عدّلنا في shallow بس، ومع ذلك original اتغيّر. لأن skills في النسختين نفس المصفوفة.
الحل: نسخة عميقة
import copy
original = {"name": "Ahmed", "skills": ["Python", "SQL"]}
# deep copy
deep = copy.deepcopy(original)
deep["skills"].append("Go")
print(original["skills"]) # ['Python', 'SQL'] original is safe