29-05-2018, 11:00 PM
المراجع المشتركة في بيثون shared references in Python
## ملاحظة: استعنت بموقع بيثون توتور كثيرا في هذا الموضوع: http://pythontutor.com
يعتقد بعض الناس أن أسهل طريقة لنسخ قائمة هي الإعلان عن متغير وتعيينه لها كما في المثال التالي:
كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list
print(copy_list)
# [1, 6, ['spam', 'eggs']]
سنلاحظ أن المتغيرين يشيران إلى نفس القائمة وأن المؤشر 2 من هذه القائمة يشير إلى القائمة الصغيرة المدرجة في القائمة الكبيرة.
ماذا يعني هذا؟ هذا يعني ببساطة أننا أنشأنا رابطا نحو القائمة الاصلية باسم آخر فقط لا غير! إذا قمنا بتعديل قيمة في copy_list ، فسنجد أننا قمنا بتعديل هذه القيمة في initial_list حيث إنها نفس المرجع! دعونا نحاول دون مزيد من التأخير بالاستعانة بموقع بيثون توتور:
كود :
copy_list[1] = 'FAKE'
print(copy_list)
print(initial_list)
# [1, 'FAKE', ['spam', 'eggs']]
دعونا نحاول معرفة ما إذا كان بإمكاننا حل المشكلة عن طريق إعلان قائمة جديدة وتعريفها بالقائمة الاصلية. هناك طريقتنان متاحتان لنا وسنستعين بموقع بيثون توتور مرة أخرى:
كود :
# 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)
كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list[:]
copy_list[1] = 'beacon'
print(copy_list)
print(initial_list)
كود :
initial_list = [1, 6, ['spam', 'eggs']]
copy_list = initial_list[:]
copy_list[2][0] = 'beans'
print(copy_list)
print(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 على مختلف أنواع الكائنات القابلة للتعديل وهي تتيح النسخ السطحي 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)
كود :
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'
كود :
initial_list = ['spam', 18, 4.5]
copy_list = initial_list[:]
copy_list[0] = 'eggs'
print(copy_list)
print(initial_list)
كود :
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)
كود :
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 فيقارن قيمتين من حيث تطابقهما مع نفس الكائن في الذاكرة:
كود :
# 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)
ينصح باستخدام is عوض == كلما كان ذلك ممكنا، فالمعامل is أكثر كفاءة حيث يقوم بمقارنة عنوانين في الذاكرة عوض قيمتين قد تكونان كبيرتان جدا. كما أنه يظهر الفرق عندما يتعلق الامر بالنسخ او بالمرجع المشترك. لفهم المعامل is اكثر سنستعين بمعامل آخر هو id وهو يعطينا المعرف الوحيد الخاص بكل كائن:
كود :
a = 3
b = 3
print('a', id(a), 'b', id(b))
print('a is b ?', a is b)
كود :
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