1. الفرق بين التمرير بالقيمة والتمرير بالمرجع
في بايثون، هناك طريقتان لتمرير الوسائط إلى الدوال: التمرير بالقيمة والتمرير بالمرجع.
- Pass-by-value : طريقة يتم فيها تمرير نسخة من القيمة إلى الدالة كوسيط؛ تعديل المتغيّر داخل الدالة لا يؤثر على المتغيّر الأصلي.
- Pass-by-reference : طريقة يتم فيها تمرير مرجع (عنوان) المتغيّر إلى الدالة، لذا فإن التغييرات التي تُجرى داخل الدالة تنعكس على المتغيّر الأصلي.
في بايثون، يختلف هذا السلوك حسب طبيعة الكائن. سلوك “التمرير بالمرجع” في بايثون ينطبق خصوصًا على الأنواع القابلة للتغيير ويمكن أن يؤثر بشكل كبير على سلوك الشيفرة، لذا من المهم فهمه بشكل صحيح.
2. خصائص الكائنات في بايثون
في بايثون، تُعامل جميع البيانات ككائنات، وبناءً على خصائص الكائن تُصنّف إما إلى غير قابلة للتغيير (immutable) أو قابلة للتغيير (mutable). هذا التمييز يؤثر على سلوكها عند تمريرها كوسائط إلى الدوال.
- Immutable objects : كائنات لا يمكن تعديلها بعد إنشائها. أمثلة تشمل الأعداد الصحيحة (
int)، الأعداد العشرية (float)، السلاسل النصية (str)، والـ tuples (tuple). نظرًا لأن هذه الأنواع لا يمكن تغييرها بمجرد إنشائها، فإن العمليات التي تُجرى عليها داخل الدالة لا تؤثر على المستدعي حتى وإن تم تمريرها بالمرجع. - Mutable objects : كائنات يمكن تعديلها بعد إنشائها. أمثلة تشمل القوائم (
list)، القواميس (dict)، والمجموعات (set). نظرًا لأن التغييرات التي تُجرى على هذه الأنواع داخل الدالة تنعكس على المستدعي، فإنها تتأثر بشكل خاص بسلوك تمرير المرجع في بايثون.

3. كيف يمرر بايثون الوسائط
في بايثون، عندما تمرّر وسائط إلى دالة، يتم تمرير مراجع الكائنات. يُطلق على هذا السلوك أحيانًا “التمرير بمرجع الكائن”. من هنا، سنستعرض أمثلة ملموسة توضح سلوك بايثون بناءً على ما إذا كانت الكائنات غير قابلة للتغيير أو قابلة للتغيير.
3.1 الكائنات غير القابلة للتغيير
عند تمرير كائن غير قابل للتغيير إلى دالة، يؤدي تعيين قيمة جديدة داخل الدالة إلى إنشاء كائن جديد ولا يؤثر على الكائن الأصلي.
مثال: تمرير عدد صحيح غير قابل للتغيير
def modify_number(num):
num = num * 2
print("Number inside the function:", num)
original_num = 5
modify_number(original_num)
print("Number outside the function:", original_num)
الإخراج:
Number inside the function: 10
Number outside the function: 5
شرح
داخل الدالة modify_number، يتم مضاعفة المتغيّر num، لكن ذلك لا يؤثر على original_num خارج الدالة. نظرًا لأن الأعداد الصحيحة غير قابلة للتغيير، يتم إنشاء كائن جديد عند تعيين قيمة جديدة، ويبقى original_num دون تغيير.
3.2 الكائنات القابلة للتغيير
من ناحية أخرى، عندما يتم تمرير كائن قابل للتغيير إلى دالة، يُستخدم مرجعه أيضًا داخل الدالة. وبالتالي، تنعكس التغييرات التي تُجرى داخل الدالة على الكائن الأصلي أيضًا.
مثال: تمرير قائمة قابلة للتغيير
def add_item(my_list):
my_list.append("Added element")
print("List inside the function:", my_list)
original_list = ["Element 1", "Element 2"]
add_item(original_list)
print("List outside the function:", original_list)
الإخراج:
List inside the function: ['Element 1', 'Element 2', 'Added element']
List outside the function: ['Element 1', 'Element 2', 'Added element']
شرح
نظرًا لأن القوائم قابلة للتغيير، فإن التغييرات التي تُجرى داخل الدالة add_item تنعكس مباشرةً على original_list خارج الدالة. بهذه الطريقة، تُشارك الكائنات القابلة للتغيير بين داخل الدالة وخارجها، لذا فإن التعديلات داخل الدالة تؤثر أيضًا على المستدعي.
4. التحذيرات والإجراءات الوقائية للتمرير بالمرجع
تمرير الكائنات القابلة للتغيير إلى الدوال في بايثون يمكن أن يؤدي إلى سلوك غير متوقع. إليك بعض الإجراءات الوقائية لتجنب مثل هذه المشكلات.
4.1 استخدم نسخًا من الكائنات
إذا كنت تريد تعديل كائن داخل دالة، يمكنك تجنب التأثير على الأصل عن طريق نسخه قبل إجراء التغييرات. في بايثون، يمكنك استخدام وحدة copy لإنشاء نسخ سطحية (copy.copy) أو نسخ عميقة (copy.deepcopy).
مثال: استخدام النسخة العميقة لتجنب مشاكل الكائنات القابلة للتغيير
import copy
def add_item(my_list):
my_list_copy = copy.deepcopy(my_list)
my_list_copy.append("Added element")
print("List inside the function (copy):", my_list_copy)
original_list = ["Element 1", "Element 2"]
add_item(original_list)
print("List outside the function:", original_list)
الناتج:
List inside the function (copy): ['Element 1', 'Element 2', 'Added element']
List outside the function: ['Element 1', 'Element 2']
شرح
باستخدام deepcopy، يتم إنشاء كائن جديد داخل الدالة، مما يترك القائمة الأصلية غير متأثرة. هذا يسمح بإجراء تغييرات مستقلة داخل الدالة وخارجها.
4.2 استخدم None للمعاملات الافتراضية
تجنب تعيين الكائنات القابلة للتغيير كمعاملات افتراضية للدالة؛ يُنصح باستخدام None كقيمة افتراضية وإنشاء كائن جديد داخل الدالة.
مثال: الطريقة الآمنة لجعل المعامل الافتراضي None
def add_item(my_list=None):
if my_list is None:
my_list = []
my_list.append("New element")
print("List inside the function:", my_list)
return my_list
# When no list is passed
result = add_item()
print("Returned list:", result)
# When a list is passed
existing_list = ["Element 1", "Element 2"]
result = add_item(existing_list)
print("Returned list:", result)
print("Original list:", existing_list)
الناتج:
List inside the function: ['New element']
Returned list: ['New element']
List inside the function: ['Element 1', 'Element 2', 'New element']
Returned list: ['Element 1', 'Element 2', 'New element']
Original list: ['Element 1', 'Element 2', 'New element']
شرح
باستخدام المعامل الافتراضي None وإنشاء قائمة جديدة داخل الدالة، يمكنك منع القوائم الخارجية من تعديلها بشكل غير متوقع. تعيين None كمعامل افتراضي يوضح ما إذا كان يجب إنشاء قائمة جديدة أم استخدام القائمة المقدمة.

5. مثال عملي: كود لتعميق الفهم حول تمرير المرجع
دعونا نراجع ما غطيناه من خلال مثال عملي.
مثال: تعديل مفاتيح وقيم القاموس
القواميس هي كائنات قابلة للتغيير، لذا فإن العمليات التي تُجرى داخل دالة تؤثر على القاموس خارج الدالة. يوضح المثال أدناه السلوك عند تغيير مفاتيح وقيم القاموس.
def modify_dict(my_dict):
my_dict["new key"] = "new value"
print("Dictionary inside the function:", my_dict)
original_dict = {"key1": "value1", "key2": "value2"}
modify_dict(original_dict)
print("Dictionary outside the function:", original_dict)
الناتج:
Dictionary inside the function: {'key1': 'value1', 'key2': 'value2', 'new key': 'new value'}
Dictionary outside the function: {'key1': 'value1', 'key2': 'value2', 'new key': 'new value'}
شرح
نظرًا لأن القواميس قابلة للتغيير، فإن الإضافات التي تُجرى داخل الدالة تنعكس أيضًا في original_dict خارج الدالة. مثل القوائم، يمكن للقواميس أن تؤثر على المستدعي لأنها تُمرَّر بالمرجع.
6. الخلاصة
سلوك “تمرير المرجع” في بايثون يختلف حسب خصائص الكائن، لذا فإن فهمه ضروري لتحسين موثوقية الكود الخاص بك. على وجه الخصوص، لتجنب السلوك غير المتوقع عند تمرير مراجع إلى كائنات قابلة للتغيير، يُنصح باستخدام النسخ والمعامل الافتراضي None.
نقاط الخلاصة
- الفرق بين التمرير بالقيمة والتمرير بالمرجع : في بايثون، ما إذا كانت التعديلات داخل الدالة تؤثر على المتغير الأصلي يعتمد على طبيعة الكائن.
- غير قابل للتغيير مقابل قابل للتغيير : الكائنات غير القابلة للتغيير في بايثون لا تعكس التغييرات التي تُجرى داخل الدالة، بينما الكائنات القابلة للتغيير تُشارك بين داخل وخارج الدالة عبر تمرير المرجع.
- استراتيجيات التخفيف : بالنسبة للكائنات القابلة للتغيير، طرق مثل استخدام
deepcopyمن وحدةcopy، أو تعيين القيم الافتراضية إلىNone، فعّالة لتجنب السلوك غير المتوقع.
أتمنى أن تكون هذه المقالة قد عمّقت فهمك للتمرير بالمرجع في بايثون. استخدم هذه المعرفة لتطبيق شفرة فعّالة وأقل عرضة للأخطاء.



