وحدة `copy` في بايثون
توضح هذه المقالة وحدة copy في بايثون۔
مع التركيز على الفروق بين النسخ السطحي والعميق، نقدم شرحاً واضحاً — من الآليات الأساسية لنسخ الكائنات إلى التطبيقات في الأصناف المخصّصة — مع أمثلة عملية۔
YouTube Video
وحدة copy في بايثون
وحدة Python المسماة copy هي الوحدة القياسية للتعامل مع نسخ الكائنات۔ الهدف هو فهم الفرق بين النسخ السطحي والعميق والقدرة على التحكم في سلوك النسخ للكائنات المخصّصة۔
الأساسيات: ما هو النسخ السطحي؟
هنا نوضح سلوك النسخ السطحي للكائنات القابلة للتغيير مثل القوائم والقواميس۔ النسخ السطحي يكرّر الكائن على المستوى الأعلى فقط ويُبقي المراجع الداخلية مشتركة۔
1# Example: shallow copy with lists and dicts
2import copy
3
4# A nested list
5original = [1, [2, 3], 4]
6shallow = copy.copy(original)
7
8# Mutate nested element
9shallow[1].append(99)
10
11print("original:", original) # nested change visible in original
12print("shallow: ", shallow)في هذا الكود، يتم تكرار القائمة على المستوى الأعلى، لكن القوائم الفرعية الداخلية تُشارَك بالمراجع، لذا فإن shallow[1].append(99) ينعكس أيضًا في original۔ هذا سلوك نموذجي للنسخ السطحي۔
ما هو النسخ العميق؟
النسخ العميق يكرّر الكائن وجميع مراجعه الداخلية بصورةٍ عودية، منتجاً كائناً مستقلاً۔ استخدمه عندما تريد نسخ البُنى المتداخلة المعقّدة بأمان۔
1# Example: deep copy with nested structures
2import copy
3
4original = [1, [2, 3], {'a': [4, 5]}]
5deep = copy.deepcopy(original)
6
7# Mutate nested element of the deep copy
8deep[1].append(100)
9deep[2]['a'].append(200)
10
11print("original:", original) # original remains unchanged
12print("deep: ", deep)في هذا المثال، التغييرات على deep لا تؤثر في original۔ deepcopy ينسخ الرسم البياني للكائن بالكامل، ما ينتج نسخة مستقلة۔
كيفية تحديد أيّهما تستخدم
يُحدَّد اختيار نوع النسخ بحسب بنية الكائن وهدفك۔ إذا كنت لا تريد أن تؤثر التغييرات على العناصر الداخلية القابلة للتغيير في الكائن الأصلي، فـdeepcopy مناسب۔ وذلك لأنه يكرّر الكائن بأكمله بصورة عودية، منشئاً نسخة مستقلة تماماً۔
من ناحية أخرى، إذا كان تكرار الكائن على المستوى الأعلى فقط كافياً وتُعطي الأولوية للسرعة وكفاءة الذاكرة، فـcopy (نسخ سطحي) هو الأنسب۔
- تريد تجنّب تغيّر الأصل عند تعديل العناصر الداخلية القابلة للتغيير → استخدم
deepcopy۔ - يكفي تكرار المستوى الأعلى فقط وتريد إعطاء الأولوية للأداء (السرعة/الذاكرة) → استخدم
copy(نسخ سطحي)۔
التعامل مع الكائنات المضمّنة وغير القابلة للتغيير
الكائنات غير القابلة للتغيير (مثل int وstr والـ tuples ذات المحتوى غير القابل للتغيير) غالباً لا تحتاج إلى نسخ۔ copy.copy قد يعيد الكائن نفسه عند الاقتضاء۔
1# Example: copying immutables
2import copy
3
4a = 42
5b = copy.copy(a)
6print(a is b) # True for small ints (implementation detail)
7
8s = "hello"
9t = copy.deepcopy(s)
10print(s is t) # True (strings are immutable)بما أن نسخ الكائنات غير القابلة للتغيير يوفر فائدة محدودة من ناحية الكفاءة، فقد يُعاد استخدام الكائن نفسه۔ نادراً ما يكون هذا أمراً يستدعي القلق بشأنه في تصميم التطبيقات۔
الأصناف المخصّصة: تعريف __copy__ و__deepcopy__
إذا كان النسخ الافتراضي لا يطابق ما تتوقعه لصنفك، يمكنك توفير منطق النسخ الخاص بك۔ إذا عرّفت __copy__ و__deepcopy__ فسيستخدمهما copy.copy وcopy.deepcopy۔
1# Example: customizing copy behavior
2import copy
3
4class Node:
5 def __init__(self, value, children=None):
6 self.value = value
7 self.children = children or []
8
9 def __repr__(self):
10 return f"Node({self.value!r}, children={self.children!r})"
11
12 def __copy__(self):
13 # Shallow copy: create new Node, but reuse children list reference
14 new = self.__class__(self.value, self.children)
15 return new
16
17 def __deepcopy__(self, memo):
18 # Deep copy: copy value and deepcopy each child
19 new_children = [copy.deepcopy(child, memo) for child in self.children]
20 new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21 memo[id(self)] = new
22 return new- من خلال تنفيذ
__copy__و__deepcopy__، يمكنك التحكّم بمرونة في سلوك النسخ الخاص بالصنف۔ على سبيل المثال، يمكنك التعامل مع الحالات التي ينبغي فيها لسمات معيّنة أن تشير إلى (تشارك) الكائن نفسه، بينما ينبغي استنساخ سمات أخرى ككائنات جديدة تمامًا۔ - الوسيطة
memoالمُمرَّرة إلى__deepcopy__هي قاموس يسجّل الكائنات التي جرى معالجتها بالفعل أثناء النسخ العودي ويمنع الحلقات اللانهائية الناتجة عن المراجع الدائرية۔
1# Build tree
2root_node = Node('root', [Node('child1'), Node('child2')])
3
4shallow_copy = copy.copy(root_node) # shallow copy
5deep_copy = copy.deepcopy(root_node) # deep copy
6
7# Modify children
8# affects both root_node and shallow_copy
9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)- في هذا الكود، يتم إنشاء المتغير
shallow_copyعبر 'نسخ سطحي'، لذا لا يُكرَّر إلا المستوى الأعلى من الكائن، أما الكائنات التي يُشير إليها داخليًا (في هذه الحالة، قائمةchildren) فتكون مشتركة معroot_nodeالأصلي۔ وبالتالي، عند إضافة عقدة جديدة إلىshallow_copy، تُحدَّث قائمةchildrenالمشتركة ويَنعكس التغيير أيضًا في محتوياتroot_node۔ - من ناحية أخرى، يتم إنشاء المتغير
deep_copyعبر 'نسخ عميق'، لذا تُكرَّر البنية الداخلية بشكل عودي، منتجة شجرة مستقلة تمامًا عنroot_node۔ لذلك، حتى إذا أضفت عقدة جديدة إلىdeep_copy، فلن يؤثر ذلك فيroot_node۔
المراجع الدورية (الكائنات العودية) وأهمية memo
عندما يشير مخطط كائن معقّد إلى نفسه (مراجع دورية)، يستخدم deepcopy قاموساً باسم memo لتتبّع الكائنات التي تم نسخها بالفعل ومنع الحلقات اللانهائية۔
1# Example: recursive list and deepcopy memo demonstration
2import copy
3
4a = []
5b = [a]
6a.append(b) # a -> [b], b -> [a] (cycle)
7
8# deepcopy can handle cycles
9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))- داخلياً، يستخدم
deepcopyعنصرmemoللإشارة إلى الكائنات التي تم تكرارها مسبقاً، ما يتيح النسخ الآمن حتى مع وجود دورات۔ تحتاج إلى آلية مشابهة عند تنفيذ العودية يدوياً۔
copyreg والنسخ المخصّص بأسلوب التسلسل (متقدّم)
إذا أردت تسجيل سلوك نسخ خاص بالتنسيق مع المكتبات، فيمكنك استخدام الوحدة القياسية copyreg لتسجيل كيفية (إعادة) بناء الكائنات۔ هذا مفيد للكائنات المعقّدة وأنواع امتدادات C۔
1# Example: using copyreg to customize pickling/copy behavior (brief example)
2import copy
3import copyreg
4
5class Wrapper:
6 def __init__(self, data):
7 self.data = data
8
9def reduce_wrapper(obj):
10 # Return callable and args so object can be reconstructed
11 return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)- باستخدام
copyregيمكنك توفير قواعد لإعادة البناء تؤثر في كلٍ منpickleوdeepcopy۔ إنه واجهة برمجية متقدّمة، وفي معظم الحالات يكون__deepcopy__كافياً۔
تحذيرات وعثرات عملية
هناك عدة نقاط مهمة لضمان السلوك الصحيح عند استخدام وحدة copy۔ نوضح أدناه العثرات الشائعة التي قد تواجهها أثناء التطوير وكيفية تجنّبها۔
- الأداء
deepcopyقد يستهلك قدراً كبيراً من الذاكرة والمعالج، لذا فكّر بعناية فيما إذا كان ضرورياً۔ - التعامل مع العناصر التي تريد مشاركتها
إذا أردت بقاء بعض السمات — مثل مخازن التخزين المؤقت الكبيرة — مشتركة، فتجنّب عمداً نسخ مراجعها داخل
__deepcopy__۔ - حالة داخلية غير قابلة للتغيير إذا كانت لديك بيانات داخلية غير قابلة للتغيير، فقد لا تكون هناك حاجة إلى النسخ۔
- الخيوط والموارد الخارجية الموارد التي لا يمكن نسخها — مثل المقابس ومقابض الملفات — إما أن يكون نسخها بلا معنى أو يسبب أخطاء، لذا يجب تجنّب نسخها ضمن التصميم۔
مثال عملي: نمط لتحديث القواميس بأمان
عند تحديث كائن إعدادات معقّد، يستخدم هذا المثال deepcopy لحماية الإعدادات الأصلية۔
1# Example: safely update a nested configuration using deepcopy
2import copy
3
4default_config = {
5 "db": {"host": "localhost", "ports": [5432]},
6 "features": {"use_cache": True, "cache_sizes": [128, 256]},
7}
8
9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)باستخدام deepcopy يمكنك إنشاء إعدادات مشتقة بأمان دون تعريض الإعدادات الافتراضية للخطر۔ يكون هذا مفيداً بشكل خاص للبُنى المتداخلة القابلة للتغيير مثل الإعدادات۔
أفضل الممارسات
لاستخدام الوحدة copy بأمان وفعالية، من المهم أن تضع الإرشادات العملية التالية في الاعتبار۔
- اختر بين النسخ السطحي والعميق بناءً على ما إذا كانت ستحدث تغييرات ونطاقها۔
- نفّذ
__copy__و__deepcopy__في الأصناف المخصّصة لتحقيق السلوك المتوقع۔ - لأن النسخ العميق مكلف، قلّل الحاجة إلى النسخ عبر التصميم متى أمكن۔ ضع في الحسبان اللاقابلية للتغيير وطرائق الاستنساخ الصريحة وغيرها من التقنيات۔
- عند التعامل مع مراجع دورية، إمّا استفد من
deepcopyأو وفّر آلية شبيهة بـmemoيدوياً۔ - صمِّم بحيث لا تُنسَخ الموارد الخارجية مثل مقابض الملفات والخيوط التنفيذية۔
الخاتمة
وحدة copy هي الأداة القياسية لنسخ الكائنات في بايثون۔ من خلال فهم الفروق بين النسخ السطحي والعميق على نحوٍ صحيح وتنفيذ سلوك نسخ مخصّص عند الحاجة، يمكنك إجراء النسخ بأمان وبشكل متوقع۔ من خلال توضيح ما إذا كان النسخ ضرورياً حقاً وما الذي يجب مشاركته في مرحلة التصميم، يمكنك تجنّب الأخطاء غير الضرورية ومشكلات الأداء۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔