تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
المراجع المشتركة في بيثون shared references in Python
#1
المراجع المشتركة في بيثون shared references in Python

## ملاحظة: استعنت بموقع بيثون توتور كثيرا في هذا الموضوع: http://pythontutor.com



يعتقد بعض الناس أن أسهل طريقة لنسخ قائمة هي الإعلان عن متغير وتعيينه لها كما في المثال التالي:

كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list
print(copy_list)
# [1, 6, ['spam', 'eggs']]
ولكننا في الواقع من خلال القيام بذلك ، أنشأنا ببساطة اسمًا مستعارًا (alias) لنفس الكائن، أي أننا أنشأنا كائنًا مثيلا يشترك في نفس المرجع مع القائمة المنسوخة! لكي نتأكد من ذلك، يمكننا استخدام موقع بيثون توتور: http://pythontutor.com لمشاهدة رسم توضيحي للتعليمات السابقة.

سنلاحظ أن المتغيرين يشيران إلى نفس القائمة وأن المؤشر 2 من هذه القائمة يشير إلى القائمة الصغيرة المدرجة في القائمة الكبيرة.

ماذا يعني هذا؟ هذا يعني ببساطة أننا أنشأنا رابطا نحو القائمة الاصلية باسم آخر فقط لا غير! إذا قمنا بتعديل قيمة في copy_list ، فسنجد أننا قمنا بتعديل هذه القيمة في initial_list حيث إنها نفس المرجع! دعونا نحاول دون مزيد من التأخير بالاستعانة بموقع بيثون توتور:

كود :
copy_list[1] = 'FAKE'
print(copy_list)
print(initial_list)
# [1, 'FAKE', ['spam', 'eggs']]
## النسخ السطحي باستخدام الدالة list او بتقنية slicing

دعونا نحاول معرفة ما إذا كان بإمكاننا حل المشكلة عن طريق إعلان قائمة جديدة وتعريفها بالقائمة الاصلية. هناك طريقتنان متاحتان لنا وسنستعين بموقع بيثون توتور مرة أخرى:

كود :
# with list()
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = list(initial_list)
print(copy_list)
كود :
# with list[:]
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list[:]
print(copy_list)
ماذا نلاحظ هذه المرة؟ كل متغير يشير الى القائمة الخاصة به. يعني ذلك أنه في حالة تعديل  العنصر [1] في copy_list والذي يتوافق مع القيمة 6 فسوف لن يحدث تعديل تلقائي للعنصر [1] في القائمة initial_list. دعونا نجرب:

كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list[:]
copy_list[1] = 'beacon'
print(copy_list)
print(initial_list)
حصلنا على النتيجة المنتظرة! ولم يكن الأمر معقدا، لذلك سنعوض spam بـ beans:

كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list[:]
copy_list[2][0] = 'beans'
print(copy_list)
print(initial_list)
لكن ماذا جرى؟ لماذا حدث تعديل للقائمة الاصلية initial_list؟ نحن لا نريد ذلك!

يمكننا الاستعانة من جديد بموقع بيثون توتور لنلاحظ أن المتغيرين initial_list و copy_list يشيران الى قائمتين مختلفتين، لكن سنلاحظ أيضا أن المؤشر 2 في كلتا القائمتين يشير الى نفس القائمة الفرعية.

ما قمنا به يسمى في الواقع نسخ سطحي shallow copy فقد قمنا بنسخ العناصر الموجودة في المستوى الأعلى أما العناصر الموجودة في المستوى الثاني اي في القائمة الفرعية فلم نقم بنسخها. هذا لا يمثل مشكلا اذا كانت العناصر كلها غير قابلة للتعديل (لو كان لدينا صف داخلي عوض القائمة الفرعية مثلا)، لكن سيتوجب علينا الانتباه اذا كان لدينا قائمات او قواميس أو اي عناصر فرعية قابلة للتعديل.

يمكننا كتابة سكربت خاص يقوم بنسخ عميق كما في المثال التالي، لكن هذا ليس حلا جذريا لمجتلف الحالات، كما أنه معقد أكثر مما ينبغي:

كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = list(initial_list)
copy_list[2] = list(initial_list[2])
copy_list[2][0] = 'beans'
print(copy_list)
print(initial_list)
## الوحدة copy

تعمل الوحدة copy على مختلف أنواع الكائنات القابلة للتعديل وهي تتيح النسخ السطحي shallow copy او العميق deep copy

- النسخ السطحي يقوم بنسخ العناصر في المستوى الاول فقط أي العنصرين الاول والثاني في قائمتنا السابقة، أما العنصر الثالث الذي هو عبارة عن قائمة فرعية فسيصبح مرجعا مشتركا shared reference

- النسخ العميق سيقوم بنسخ الكائنات مهما كان مستواها (قائمة داخل قائمة داخل قائمة...) وسنحصل في النهاية على كائنين مختلفين تماما ليس بينهما اي رابط.

كود :
import copy
initial_list = [1, 6, ['spam', 'eggs', ['baked', 'beans']]]
shallow_copy = copy.copy(initial_list)
deep_copy = copy.deepcopy(initial_list)
shallow_copy[2][2][0] = 'wonderful'
deep_copy[2][2][0] = 'lovely'
print(initial_list)
print(deep_copy)
طبعا تتيح لنا الوحدة copy نسخا عميقا للقائمات لكنها تعمل بكفاءة تامة على أي نوع من الكائنات القابلة للتعديل كالقواميس مثلا:

كود :
import copy
dico = {'fname':'spam', 'lname':{'lname1':'lovely', 'lname2':'wonderful'}, 'age':18}
deep_dico = copy.deepcopy(dico)
deep_dico['fname'] = 'beans'
deep_dico['lname']['lname1'] = 'baked'
ما من اهمية لاستخدام الوحدة copy بالنسبة للكائنات غير القابلة للتعديل (str, int, float...) لأن ما سيحدث هو على كل حال انشاء كائن جديد له مرجعه الخاص به. مثال:

كود :
initial_list = ['spam', 18, 4.5]
copy_list = initial_list[:]
copy_list[0] = 'eggs'
print(copy_list)
print(initial_list)
مع التنبيه بالنسبة للصفوف tuple فالصف غير قابل للتعديل والسكربت التالي لن يقوم بنسخ سطحي كما هو منتظر ولن يقوم سوى بانشاء مرجع مشترك بين المتغيرين initial_tuple و copy_tuple

كود :
import copy
initial_tuple = ('spam ', 18, 4.5)
copy_tuple = copy.copy(initial_tuple)
print(copy_tuple)
print(initial_tuple)
يمكن استخدام الطريقة التالية للحصول على نسخة سطحية منفصلة:

كود :
initial_tuple = ('spam ', 18, 4.5)
copy_tuple = ()
copy_tuple += initial_tuple
print(copy_tuple)
print(initial_tuple)
بالنسبة للنسخ العميق ستحل الوحدة copy المشكل :



كود :
import copy
initial_tuple = ('spam ', 18, 4.5, ('baked', 'beans',['lovely', 'wonderful']))
copy_tuple = copy.deepcopy(initial_tuple)
copy_tuple[3][2][1] = 'eggs'
print(copy_tuple)
print(initial_tuple)
## المعامل is

يقوم المعامل == بمقارنة قيمة كائنين، أما المعامل is فيقارن قيمتين من حيث تطابقهما مع نفس الكائن في الذاكرة:

كود :
# Example 1
a = [1, 2]
b = [1, 2]
print('a == b ?', a == b)
print('a is b ?', a is b)

# Example 2
a = [1, 2]
b = a
print('a == b ?', a == b)
print('a is b ?', a is b)

# Example 3
a = [1, 2]
b = a[:]
print('a == b ?', a == b)
print('a is b ?', a is b)

# Example 4
a = b = [1, 2]
print('a == b ?', a == b)
print('a is b ?', a is b)

# Example 5
undef = None
print('undef == None ?', undef == None)
print('undef is None ?', undef is None)
## الدالة id

ينصح باستخدام is عوض == كلما كان ذلك ممكنا، فالمعامل is أكثر كفاءة حيث يقوم بمقارنة عنوانين في الذاكرة عوض قيمتين قد تكونان كبيرتان جدا. كما أنه يظهر الفرق عندما يتعلق الامر بالنسخ او بالمرجع المشترك. لفهم المعامل is اكثر سنستعين بمعامل آخر هو id وهو يعطينا المعرف الوحيد الخاص بكل كائن:

كود :
a = 3
b = 3
print('a', id(a), 'b', id(b))
print('a is b ?', a is b)
عجبا !! رغم أن لكل متغير عنوان خاص به في الذاكرة لكن مقارنتها بis تعطي True. في الواقع في حالة الاعداد الصغيرة، يحاول بيثون تحسين استخدام الذاكرة فيقوم بتخصيص وانشاء كائن واحد متوافق مع العدد الصحيح 3، وذلك لتجنب التبذير في استخدام الذاكرة. نسمي العدد الصحيح 3 مفرد (singleton). نجد هذا التحسين في كائنات أخرى، على سبيل المثال:

كود :
a = ""
b = ""
print('a', id(a), 'b', id(b))
print('a is b ?', a is b)
أو في هذا المثال أيضا:

كود :
a = "foo"
b = "foo"
print('a', id(a), 'b', id(b))
print('a is b ?', a is b)
## للمزيد من المعلومات

- حول الوحدة copy

https://docs.python.org/3/library/copy.html

- حول المراجع المشتركة:

لا يفوتني أن أنصح بمتابعة هذا الفيديو الذي يقدم المزيد من التوضيحات عن المراجع المشتركة: استخدم الترجمة الآلية اذا كنت لا تفهم اللغة الفرنسية: https://www.youtube.com/watch?v=k2FABoXYX7Q

- سيكون من المفيد الاستعانة بموقع بيثون توتور للحصول على رسوم توضيحية مساعدة: http://pythontutor.com
الرد


التنقل السريع :


مستخدمين يتصفحوا هذا الموضوع: 1 ضيف