النسخ السطحية والعميقة: كيفية نسخ البيانات المعقدة بشكل صحيح في Python
في الفصل السابق، تعلمت أن copy = original لا تنسخ البيانات، بل تنسخ المرجع فقط. الآن ستتعلم كيفية نسخ البيانات بشكل صحيح: متى تستخدم النسخة السطحية (Shallow Copy)، ومتى تحتاج إلى النسخة العميقة (Deep Copy)؟ هذا الفهم أساسي للعمل مع القوائم والقواامس والكائنات المعقدة.
1. مراجعة سريعة: النسخ البسيط vs النسخ الحقيقي
تذكر أن copy = original ينسخ المرجع (Reference)، وليس البيانات الفعلية. هذا يعني أن كلا المتغيرين يشيران إلى نفس الكائن في الذاكرة:
# النسخ البسيط (Reference Copy) - خطر!
original = [1, 2, 3]
copy = original
print(f"id(original): {id(original)}")
print(f"id(copy): {id(copy)}")
print(f"هل هما نفس الكائن؟ {original is copy}") # True!
# تعديل copy يؤثر على original
copy.append(4)
print(f"original: {original}") # [1, 2, 3, 4] (تغيرت!)
الحل هو استخدام Shallow Copy أو Deep Copy. الفرق يكمن في عمق النسخ:
2. النسخة السطحية (Shallow Copy)
Shallow Copy تنسخ الكائن الأساسي فقط، لكنها تحافظ على نفس المراجع للعناصر الداخلية. بمعنى آخر: إذا كانت قائمتك تحتوي على قوائم أخرى (nested lists)، فإن النسخة السطحية ستشير إلى نفس القوائس الداخلية.
مثال 1: Shallow Copy مع قائمة بسيطة
# Shallow Copy مع قائمة بسيطة
original = [1, 2, 3]
shallow = original.copy() # أو list(original)
print(f"id(original): {id(original)}")
print(f"id(shallow): {id(shallow)}")
print(f"هل هما نفس الكائن؟ {original is shallow}") # False
# تعديل shallow لا يؤثر على original
shallow.append(4)
print(f"original: {original}") # [1, 2, 3]
print(f"shallow: {shallow}") # [1, 2, 3, 4]
ممتاز! هنا Shallow Copy عملت بشكل صحيح لأن عناصر القائمة أرقام (أنواع بسيطة). لكن ماذا يحدث عندما تحتوي القائمة على قوائس أخرى؟
مثال 2: الخطر المخفي - Shallow Copy مع Nested Lists
# Shallow Copy مع قائمة متعددة الأبعاد
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
shallow = matrix.copy()
print(f"matrix is shallow: {matrix is shallow}") # False (الحاويات مختلفة)
print(f"matrix[0] is shallow[0]: {matrix[0] is shallow[0]}") # True! (نفس الداخل)
# تعديل العنصر الداخلي
shallow[0][0] = 999
print(f"matrix: {matrix}") # [[999, 2, 3], [4, 5, 6], [7, 8, 9]] (تغيرت!)
print(f"shallow: {shallow}") # [[999, 2, 3], [4, 5, 6], [7, 8, 9]]
هنا المشكلة! Shallow Copy نسخت حاوية القائمة الخارجية فقط، لكن العناصر الداخلية (القوائس) تشير إلى نفس الموقع. لذلك عندما غيرنا shallow[0][0]، تأثرت matrix[0][0] أيضاً.
مثال 3: Shallow Copy مع Dictionaries
# Shallow Copy مع dictionary يحتوي على قوائم
person = {
"name": "أحمد",
"grades": [90, 85, 88]
}
person_copy = person.copy()
# تعديل القيمة الأساسية (string) - آمن
person_copy["name"] = "محمد"
print(f"person['name']: {person['name']}") # أحمد (لم تتغير)
# تعديل قائمة داخل dictionary - خطر!
person_copy["grades"].append(95)
print(f"person['grades']: {person['grades']}") # [90, 85, 88, 95] (تغيرت!)
الخلاصة عن Shallow Copy: تستخدم عندما تكون البيانات بسيطة. لكن إذا كانت البيانات متعددة المستويات (قوائس داخل قوائس، قواميس تحتوي على قوائس، إلخ)، فستحتاج إلى Deep Copy.
3. النسخة العميقة (Deep Copy)
Deep Copy تنسخ الكائن بالكامل، بما فيه جميع العناصر الداخلية. هذا يعني أن كل شيء سيكون مستقلاً تماماً، وتعديل النسخة لن يؤثر على الأصلية.
لاستخدام Deep Copy، تحتاج إلى استيراد وحدة copy:
from copy import deepcopy
# Deep Copy مع قائمة متعددة الأبعاد
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
deep = deepcopy(matrix)
# تعديل العنصر الداخلي
deep[0][0] = 999
print(f"matrix: {matrix}") # [[1, 2, 3], [4, 5, 6], [7, 8, 9]] (لم تتغير!)
print(f"deep: {deep}") # [[999, 2, 3], [4, 5, 6], [7, 8, 9]]
رائع! Deep Copy نسخت كل شيء بشكل مستقل. الآن يمكنك تعديل النسخة بدون التأثير على الأصلية.
مثال 1: Deep Copy مع Dictionary معقد
from copy import deepcopy
# Dictionary معقد
student = {
"name": "أحمد",
"age": 20,
"grades": [90, 85, 88],
"courses": {
"math": {"score": 92, "teacher": "محمد"},
"science": {"score": 88, "teacher": "فاطمة"}
}
}
# Deep Copy
student_copy = deepcopy(student)
# تعديل البيانات المتعددة المستويات
student_copy["name"] = "علي"
student_copy["grades"].append(95)
student_copy["courses"]["math"]["score"] = 100
print("البيانات الأصلية:")
print(f"الاسم: {student['name']}") # أحمد
print(f"الدرجات: {student['grades']}") # [90, 85, 88]
print(f"درجة الرياضيات: {student['courses']['math']['score']}") # 92
print("\nالنسخة المعدلة:")
print(f"الاسم: {student_copy['name']}") # علي
print(f"الدرجات: {student_copy['grades']}") # [90, 85, 88, 95]
print(f"درجة الرياضيات: {student_copy['courses']['math']['score']}") # 100
ممتاز! Deep Copy نسخت كل شيء بشكل مستقل، حتى الكائنات المتعددة المستويات.
مثال 2: Deep Copy مع Nested Lists
from copy import deepcopy
# قائمة متعددة الأبعاد جداً
data = [
[1, 2, [3, 4, 5]],
[6, 7, [8, 9, 10]],
[11, 12, [13, 14, 15]]
]
deep = deepcopy(data)
# تعديل العناصر المتعمقة
deep[0][2][0] = 999
deep[1][2].append(100)
print("البيانات الأصلية:")
print(data)
print("\nالنسخة المعدلة:")
print(deep)
4. جدول مقارنة شامل
| النوع | الاستخدام | الأداء | الاستقلالية |
|---|---|---|---|
copy = original |
نسخ المرجع فقط | سريع جداً | ❌ مشترك تماماً |
.copy() أو list() |
Shallow Copy | سريع | ⚠️ الحاوية مستقلة، العناصر الداخلية مشتركة |
deepcopy() |
Deep Copy | أبطأ | ✅ مستقل تماماً |
متى تستخدم كل واحد؟
- Reference Copy: عندما تريد أن يشير متغيرين إلى نفس الكائن (نادراً ما تريد هذا)
- Shallow Copy: عندما تكون البيانات بسيطة (قوائم من أرقام، قواميس بقيم بسيطة)
- Deep Copy: عندما تكون البيانات معقدة (قوائس متعددة الأبعاد، قواميس متعددة المستويات، كائنات معقدة)
5. طرق مختلفة لعمل Shallow Copy
هناك عدة طرق لعمل Shallow Copy:
1. باستخدام .copy()
# List
list1 = [1, 2, 3]
list2 = list1.copy()
# Dictionary
dict1 = {"a": 1, "b": 2}
dict2 = dict1.copy()
2. باستخدام Constructor
# List
list1 = [1, 2, 3]
list2 = list(list1)
# Dictionary
dict1 = {"a": 1, "b": 2}
dict2 = dict(dict1)
3. باستخدام Slicing (للقوائم فقط)
# List
list1 = [1, 2, 3]
list2 = list1[:] # Slicing يعمل كـ Shallow Copy
4. باستخدام copy module (للـ Shallow Copy أيضاً)
from copy import copy
list1 = [1, 2, 3]
list2 = copy(list1)
dict1 = {"a": 1, "b": 2}
dict2 = copy(dict1)
6. أمثلة عملية واقعية
مثال 1: إدارة قائمة الطلاب
from copy import deepcopy
# قائمة الطلاب الأصلية
original_students = [
{"id": 1, "name": "أحمد", "grades": [90, 85, 88]},
{"id": 2, "name": "فاطمة", "grades": [92, 89, 91]},
{"id": 3, "name": "محمد", "grades": [88, 87, 90]}
]
# إنشاء نسخة لتعديلها (الحل الخاطئ)
students_wrong = original_students.copy()
students_wrong[0]["grades"].append(100)
# النتيجة: تأثرت البيانات الأصلية!
print("الحل الخاطئ - تأثرت البيانات الأصلية:")
print(f"original_students[0]['grades']: {original_students[0]['grades']}")
# النتيجة: [90, 85, 88, 100]
# الحل الصحيح
original_students = [
{"id": 1, "name": "أحمد", "grades": [90, 85, 88]},
{"id": 2, "name": "فاطمة", "grades": [92, 89, 91]},
{"id": 3, "name": "محمد", "grades": [88, 87, 90]}
]
students_correct = deepcopy(original_students)
students_correct[0]["grades"].append(100)
print("\nالحل الصحيح - البيانات الأصلية آمنة:")
print(f"original_students[0]['grades']: {original_students[0]['grades']}")
# النتيجة: [90, 85, 88]
print(f"students_correct[0]['grades']: {students_correct[0]['grades']}")
# النتيجة: [90, 85, 88, 100]
مثال 2: تعديل البيانات في حلقة
from copy import deepcopy
# قائمة أصلية
products = [
{"name": "كتاب", "price": 50, "quantity": 10},
{"name": "قلم", "price": 5, "quantity": 20},
{"name": "دفتر", "price": 15, "quantity": 15}
]
# نسخة عميقة للعمل عليها
products_backup = deepcopy(products)
# تطبيق خصم على الأسعار
for product in products_backup:
product["price"] = product["price"] * 0.9 # خصم 10%
print("الأسعار الأصلية:")
for p in products:
print(f"{p['name']}: {p['price']}")
print("\nالأسعار بعد الخصم:")
for p in products_backup:
print(f"{p['name']}: {p['price']}")
مثال 3: نسخ قاموس معقد
from copy import deepcopy
# إعدادات التطبيق (dictionary معقد)
config = {
"database": {
"host": "localhost",
"port": 5432,
"credentials": ["admin", "password123"]
},
"api": {
"endpoints": ["users", "products", "orders"],
"timeout": 30
}
}
# محاولة نسخ الإعدادات (الحل الخاطئ)
config_dev = config.copy()
config_dev["database"]["host"] = "dev-server"
config_dev["database"]["credentials"][0] = "dev_admin"
print("الإعدادات الأصلية (تأثرت!):")
print(f"Host: {config['database']['host']}")
print(f"User: {config['database']['credentials'][0]}")
# الحل الصحيح
config = {
"database": {
"host": "localhost",
"port": 5432,
"credentials": ["admin", "password123"]
},
"api": {
"endpoints": ["users", "products", "orders"],
"timeout": 30
}
}
config_dev = deepcopy(config)
config_dev["database"]["host"] = "dev-server"
config_dev["database"]["credentials"][0] = "dev_admin"
print("\nالإعدادات الأصلية (آمنة الآن):")
print(f"Host: {config['database']['host']}")
print(f"User: {config['database']['credentials'][0]}")
print("\nإعدادات التطوير:")
print(f"Host: {config_dev['database']['host']}")
print(f"User: {config_dev['database']['credentials'][0]}")
7. الأخطاء الشائعة والحلول
الخطأ 1: نسيان أن Shallow Copy قد لا تكون كافية
# الخطأ: استخدام Shallow Copy مع بيانات متعددة المستويات
teams = [
{"name": "فريق أ", "members": ["أحمد", "فاطمة"]},
{"name": "فريق ب", "members": ["محمد", "علي"]}
]
teams_copy = teams.copy() # Shallow Copy - غير كافي!
teams_copy[0]["members"].append("سارة")
# المشكلة:
print(teams[0]["members"]) # ['أحمد', 'فاطمة', 'سارة'] (تغيرت!)
# الحل:
from copy import deepcopy
teams_copy = deepcopy(teams) # Deep Copy - الحل الصحيح
teams_copy[0]["members"].append("سارة")
print(teams[0]["members"]) # ['أحمد', 'فاطمة'] (آمن!)
الخطأ 2: الخلط بين reference copy و shallow copy
# الخطأ الشائع: عدم التمييز بين النسخ
data = [1, 2, 3]
# Reference copy (خطر!)
ref = data
# Shallow copy (أفضل قليلاً)
shallow = data.copy()
# Deep copy (الأفضل للبيانات المعقدة)
from copy import deepcopy
deep = deepcopy(data)
# القاعدة:
# - استخدم .copy() للبيانات البسيطة
# - استخدم deepcopy() للبيانات المعقدة
# - تجنب reference copy (=) إلا إذا كنت تريد ذلك بقصد
الخطأ 3: نسيان استيراد deepcopy
# الخطأ
# deep = deepcopy(data) # NameError: name 'deepcopy' is not defined
# الحل
from copy import deepcopy
data = [[1, 2, 3], [4, 5, 6]]
deep = deepcopy(data) # الآن يعمل بشكل صحيح
8. نصائح مهمة وأفضل الممارسات
- افهم البيانات أولاً: قبل النسخ، اسأل نفسك: "هل بياناتي متعددة المستويات أم بسيطة؟"
- استخدم .copy() كخط أول: للبيانات البسيطة، هذا كافٍ وأسرع
- استخدم deepcopy() للأمان: إذا كنت غير متأكد، استخدم deepcopy لتكون آمناً
- تجنب reference copy (=): إلا إذا كنت تريد متغيرين يشيران إلى نفس الكائن
- اختبر النسخ في الوثائق: في البرامج الكبيرة، وثّق نوع النسخ المستخدم
- احذر من الأداء: deepcopy قد تكون بطيئة مع البيانات الضخمة
- استخدم القائمات البرمجية (List Comprehensions): للنسخ البسيطة من البيانات البسيطة
مثال: استخدام List Comprehension للنسخ
# طريقة بسيطة: استخدام list comprehension
original = [1, 2, 3, 4, 5]
copy = [x for x in original] # نسخة مستقلة
copy.append(6)
print(f"original: {original}") # [1, 2, 3, 4, 5]
print(f"copy: {copy}") # [1, 2, 3, 4, 5, 6]
9. مثال عملي شامل: نظام إدارة الموارد
from copy import deepcopy
class ResourceManager:
def __init__(self):
self.resources = {
"servers": [
{"name": "server1", "cpu": 4, "memory": 8},
{"name": "server2", "cpu": 8, "memory": 16}
],
"storage": [
{"name": "storage1", "capacity": 1000}
]
}
def create_backup(self):
"""إنشاء نسخة احتياطية كاملة"""
return deepcopy(self.resources)
def get_snapshot(self):
"""الحصول على لقطة سريعة (shallow copy)"""
return self.resources.copy()
def modify_resource(self, resource_type, index, key, value):
"""تعديل موقع معين"""
if resource_type in self.resources:
if index < len(self.resources[resource_type]):
self.resources[resource_type][index][key] = value
return True
return False
def test_modification(self):
"""اختبار التعديلات بدون التأثير على الأصلية"""
backup = self.create_backup()
backup["servers"][0]["cpu"] = 16
# المقارنة
print("الموارد الأصلية:")
print(f"CPU للخادم الأول: {self.resources['servers'][0]['cpu']}")
print("\nالنسخة الاحتياطية (معدلة):")
print(f"CPU للخادم الأول: {backup['servers'][0]['cpu']}")
# الاستخدام
manager = ResourceManager()
manager.test_modification()
10. تمرين عملي
قم بكتابة برنامج يقوم بالمهام التالية:
- أنشئ قاموس متعمق (nested dictionary) يحتوي على معلومات طالب بما فيها القسم والمواد والدرجات
-
قم بإنشاء نسخة shallow copy باستخدام
.copy()واعدل درجة ما -
استعد البيانات الأصلية وقم بإنشاء deep copy باستخدام
deepcopy()واعدل نفس الدرجة - قارن النتائج لترى الفرق
-
استخدم
id()وisللتحقق من الاختلافات
تفكر متقدم: اكتب دالة تقبل كائن معقد وتحدد تلقائياً ما إذا كانت Shallow Copy أم Deep Copy ضرورية
الخلاصة والانتقال إلى الفصل التالي
لقد تعلمت اليوم:
- الفرق بين Reference Copy و Shallow Copy و Deep Copy
- متى تستخدم
.copy()ومتى تستخدمdeepcopy() - الأخطاء الشائعة عند نسخ البيانات المعقدة
- أمثلة عملية واقعية لاستخدام النسخ الصحيح
- كيفية اختبار وتشخيص مشاكل النسخ
- أفضل الممارسات عند التعامل مع البيانات المعقدة
في الفصل التالي، ستتعلم عن البرمجة الموجهة للكائنات (OOP)، حيث ستستخدم هذه المعرفة عندما تعمل مع كائنات معقدة. الفهم الجيد للنسخ سيساعدك على تجنب أخطاء صعبة التتبع في البرامج الكبيرة.