وحدة `copy` في بايثون

وحدة `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 أيضًا.۔

YouTube Video