python - كيفية استنساخ أو نسخ قائمة؟




list copy (12)

ما هي خيارات استنساخ أو نسخ قائمة في بايثون؟

في Python 3 ، يمكن عمل نسخة ضحلة من:

a_copy = a_list.copy()

في Python 2 و 3 ، يمكنك الحصول على نسخة ضحلة مع شريحة كاملة من النص الأصلي:

a_copy = a_list[:]

تفسير

هناك طريقتان دلاليتان لنسخ قائمة. تقوم النسخة الضحلة بإنشاء قائمة جديدة بالكائنات نفسها ، بينما تنشئ النسخة العميقة قائمة جديدة تحتوي على كائنات مكافئة جديدة.

نسخة قائمة الضحلة

نسخة ضحلة فقط نسخ القائمة نفسها ، وهي حاوية مراجع إلى الكائنات الموجودة في القائمة. إذا كانت الكائنات المحتواة في حد ذاتها قابلة للتغيير وتم تغيير واحد ، سينعكس التغيير في كلتا القائمتين.

هناك طرق مختلفة للقيام بذلك في Python 2 و 3. وستعمل طرق Python 2 أيضًا في Python 3.

بايثون 2

في Python 2 ، تكون الطريقة الاصطلاحية لعمل نسخة ضحلة من القائمة مع شريحة كاملة من النص الأصلي:

a_copy = a_list[:]

يمكنك أيضًا تحقيق الشيء نفسه بتمرير القائمة من خلال مُنشئ القائمة ،

a_copy = list(a_list)

لكن استخدام المُنشئ أقل كفاءة:

>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844

بايثون 3

في Python 3 ، تحصل القوائم على طريقة list.copy :

a_copy = a_list.copy()

في Python 3.5:

>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125

جعل مؤشر آخر لا يجعل نسخة

باستخدام new_list = my_list يقوم بتعديل new_list في كل مرة تتغير فيها my_list. لماذا هذا؟

my_list هو مجرد اسم يشير إلى القائمة الفعلية في الذاكرة. عندما تقول new_list = my_list فأنت لا تقوم بإنشاء نسخة ، فأنت تقوم فقط بإضافة اسم آخر يشير إلى تلك القائمة الأصلية في الذاكرة. يمكن أن يكون لدينا مشكلات مشابهة عند عمل نسخ من القوائم.

>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]

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

نسخ عميقة

لإنشاء نسخة عميقة من قائمة ، في Python 2 أو 3 ، استخدم deepcopy في وحدة copy :

import copy
a_deep_copy = copy.deepcopy(a_list)

لشرح كيف يسمح لنا ذلك بإنشاء قوائم فرعية جديدة:

>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]

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

لا تستخدم eval

قد ترى استخدام هذا كطريقة ل deepcopy ، ولكن لا تفعل ذلك:

problematic_deep_copy = eval(repr(a_list))
  1. إنه أمر خطير ، لا سيما إذا كنت تقيِّم شيئًا ما من مصدر لا تثق به.
  2. لا يمكن الاعتماد عليه ، إذا كان عنصر فرعي لا تقوم بنسخه يحتوي على تمثيل يمكن تكوينه لإعادة إنتاج عنصر مكافئ.
  3. كما أنها أقل أداء.

في 64 bit Python 2.7:

>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206

على 64 bit Python 3.5:

>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644

ما هي خيارات استنساخ أو نسخ قائمة في بايثون؟

باستخدام new_list = my_list بتعديل my_list كل مرة my_list فيها my_list .
لماذا هذا؟


لنبدأ من البداية والمستكشف قليلاً:

لذلك افترض أن لديك قائمتين:

list_1=['01','98']
list_2=[['01','98']]

وعلينا أن ننسخ كلا القائمتين ، والآن نبدأ من القائمة الأولى:

لذا دعنا أولاً نحاول بالطريقة العامة للنسخ:

copy=list_1

الآن إذا كنت تفكر في نسخ نسخة list_1 ثم يمكنك أن تكون على خطأ ، دعونا التحقق من ذلك:

The id() function shows us that both variables point to the same list object, i.e. they share this object.
print(id(copy))
print(id(list_1))

انتاج:

4329485320
4329485320

فاجأ؟ حسناً دعنا نستكشفها:

لذلك ، وكما نعلم أن python لا يخزن أي شيء في المتغير ، فالمتغيرات تشير فقط إلى الكائن وتخزين الكائن للقيمة. هنا الكائن هو list لكننا أنشأنا اثنين من المراجع إلى هذا الكائن نفسه من قبل اثنين من أسماء المتغيرات المختلفة. لذا فإن كلا المتغيرين يشيران إلى نفس الكائن:

لذلك عندما copy=list_1 ما يفعله في الواقع:

هنا في الصورة list_1 والنسخة هما اسمان متغيران لكن الشيء نفسه لكل من المتغير الذي هو list

لذلك إذا حاولت تعديل القائمة المنسوخة ، فستقوم بتعديل القائمة الأصلية أيضًا لأن القائمة موجودة هناك فقط ، ستقوم بتعديل تلك القائمة بغض النظر عن قيامك بها من القائمة المنسوخة أو من القائمة الأصلية:

copy[0]="modify"

print(copy)
print(list_1)

انتاج:

['modify', '98']
['modify', '98']

لذلك عدلت القائمة الأصلية:

ما هو الحل إذن؟

حل :

الآن دعنا ننتقل إلى طريقة تناسقية ثانية لقائمة النسخ:

copy_1=list_1[:]

الآن هذه الطريقة إصلاح الشيء الذي كنا نواجهه في الإصدار الأول دعونا التحقق من ذلك:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432

حتى يمكننا أن نرى قائمتنا تحتوي على هوية مختلفة ، وهذا يعني أن كلا المتغيرين يشيران إلى كائنات مختلفة ، ما يحدث هنا هو:

الآن دعونا نحاول تعديل القائمة ودعونا نرى ما إذا كنا ما زلنا نواجه المشكلة السابقة:

copy_1[0]="modify"

print(list_1)
print(copy_1)

انتاج:

['01', '98']
['modify', '98']

لذلك ، كما ترى ، لا يعدّل القائمة الأصلية ، بل يعدّل القائمة المنسوخة فقط ، لذا فنحن على ما يرام.

الآن أعتقد أننا انتهينا؟ انتظرنا لنقوم بنسخ القائمة المتداخلة الثانية أيضًا ، لذا لنجرب طريقة pythonic:

copy_2=list_2[:]

لذا ، يجب أن تشير list_2 إلى كائن آخر وهو نسخة من list_2 دعنا نتحقق مما يلي:

print(id((list_2)),id(copy_2))

نحصل على الإخراج:

4330403592 4330403528

الآن يمكننا أن نفترض أن كلتا القائمتين تشيران إلى كائن مختلف ، لذلك دعونا الآن نحاول تعديله ودعنا نرى أنه يعطي ما نريد:

لذلك عندما نحاول:

copy_2[0][1]="modify"

print(list_2,copy_2)

يعطينا الإخراج:

[['01', 'modify']] [['01', 'modify']]

الآن ، وهذا مربك قليلاً استخدمنا الطريقة المثلية وما زلنا نواجه نفس المشكلة.

دعونا نفهم ذلك:

لذلك عندما نفعل:

copy_2=list_2[:]

نحن نقوم في الواقع بنسخ القائمة الخارجية فقط ، وليس القائمة المتداخلة ، لذا فإن القائمة المتداخلة هي نفس الكائن لكلتا القائمتين ، دعنا نتحقق مما يلي:

print(id(copy_2[0]))
print(id(list_2[0]))

انتاج:

4329485832
4329485832

لذلك في الواقع عندما نفعل copy_2=list_2[:] هذا ما يحدث:

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

فما هو الحل؟

الحل هو deep copy

from copy import deepcopy
deep=deepcopy(list_2)

والآن دعونا نتحقق منها:

print(id((list_2)),id(deep))

انتاج:

4322146056 4322148040

كلا التعريفين مختلفان ، والآن دعونا نتحقق من معرف القائمة المتداخلة:

print(id(deep[0]))
print(id(list_2[0]))

انتاج:

4322145992
4322145800

كما يمكنك رؤية كل معرف مختلف بحيث يمكننا أن نفترض أن كل قائمة متداخلة تشير إلى كائن مختلف الآن.

لذلك عندما تفعل deep=deepcopy(list_2) ما يحدث في الواقع:

لذا فإن كل من القائمة المتداخلة تشير إلى كائن مختلف ولديها نسخة seprate من القائمة المتداخلة الآن.

دعنا الآن نحاول تعديل القائمة المتداخلة ونرى ما إذا كانت قد حلت المشكلة السابقة أم لا:

لذلك إذا فعلنا ذلك:

deep[0][1]="modify"
print(list_2,deep)

انتاج:

[['01', '98']] [['01', 'modify']]

لذلك يمكنك أن ترى أنه لم يعدل القائمة المتداخلة الأصلية ، بل قام بتعديل القائمة المنسوخة فقط.

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


استخدام thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 

على عكس اللغات الأخرى التي تحتوي على متغير وقيمة ، فإن Python لها اسم وكلمة .

هذا البيان:

a = [1,2,3]

يعني إعطاء القائمة (الكائن) اسم a ، و ، هذا:

b = a

فقط يعطي نفس الكائن اسم جديد b ، لذلك كلما قمت بعمل شيء ما ، يتغير الكائن وبالتالي يتغير b .

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

يمكنك معرفة المزيد عن هذا here .


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

بينما تعمل new_list = old_list[:] و copy.copy(old_list)' و Py3k old_list.copy() للعمل في القوائم ذات المستوى الواحد ، فإنها تعود إلى الإشارة إلى كائنات list المتداخلة ضمن old_list و old_list ، والتغييرات إلى واحد من كائنات list تستمر في الآخر.

تحرير: المعلومات الجديدة تظهر للضوء

كما أشار كل من و PM 2Ring باستخدام eval() ليست فقط فكرة سيئة ، بل هي أيضا أبطأ بكثير من copy.deepcopy() .

وهذا يعني أنه بالنسبة للقوائم متعددة الأبعاد ، يكون الخيار الوحيد هو copy.deepcopy() . مع أن يقال ، فإنه ليس حقا خيارا مع الأداء يذهب نحو الجنوب عند محاولة استخدامه على صفيف متعدد الأبعاد بحجم معتدل. حاولت استخدام timeit باستخدام صفيف 42 × 42 ، ولم يسمع به أحد أو حتى كبيرًا لتطبيقات المعلوماتية الحيوية ، وتوقفت عن انتظار الرد وبدأت كتابة تعديلي على هذه المشاركة.

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

كما ذكر آخرون ، يمكن أن تكون هناك مشكلات أداء مهمة باستخدام وحدة copy و copy.deepcopy للقوائم متعددة الأبعاد . محاولة إيجاد طريقة مختلفة لنسخ القائمة متعددة الأبعاد دون استخدام برنامج deepcopy ، (كنت أعمل على حل مشكلة لدورة تسمح فقط بخمس ثوانٍ لتشغيل الخوارزمية بأكملها لتلقي الرصيد) ، لقد توصلت إلى طريقة استخدام الدالات المضمنة لعمل نسخة من القائمة المتداخلة دون الحاجة إلى الإشارة إلى بعضها البعض أو في كائنات list المتداخلة داخلها. لقد استخدمت eval() و repr() في المهمة لجعل نسخة القائمة القديمة في القائمة الجديدة دون إنشاء ارتباط إلى القائمة القديمة. يأخذ شكل:

new_list = eval(repr(old_list))

أساسا ما يفعله هذا هو جعل تمثيل old_list كسلسلة ثم تقييم السلسلة كما لو كان الكائن الذي يمثل السلسلة. من خلال القيام بذلك ، يتم إجراء أي ارتباط إلى كائن list الأصلي. يتم إنشاء كائن list جديد ويشير كل متغير إلى كائن مستقل الخاص به. هنا مثال على استخدام قائمة متداخلة 2 الأبعاد.

old_list = [[0 for j in range(y)] for i in range(x)] # initialize (x,y) nested list

# assign a copy of old_list to new list without them pointing to the same list object
new_list = eval(repr(old_list)) 

# make a change to new_list 
for j in range(y):
    for i in range(x):
    new_list[i][j] += 1

إذا قمت بعد ذلك بالتحقق من محتويات كل قائمة ، على سبيل المثال قائمة 4 من 3 ، فستعود بايثون

>>> new_list

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]

>>> old_list

[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

في حين أن هذا ربما ليس الطريقة الصحيحة أو النحوية الصحيحة للقيام بذلك ، يبدو أنه يعمل بشكل جيد. لم أقم باختبار الأداء ، ولكن سأقوم بتخمين أن eval() و rep() سيكون أقل في التشغيل من إرادة deepcopy .


لست متأكدا ما إذا كان هذا ما زال فعليًا ، لكن نفس السلوك يحمل معاني القواميس أيضًا. انظر الى هذا المثال.

a = {'par' : [1,21,3], 'sar' : [5,6,8]}
b = a
c = a.copy()
a['har'] = [1,2,3]

a
Out[14]: {'har': [1, 2, 3], 'par': [1, 21, 3], 'sar': [5, 6, 8]}

b
Out[15]: {'har': [1, 2, 3], 'par': [1, 21, 3], 'sar': [5, 6, 8]}

c
Out[16]: {'par': [1, 21, 3], 'sar': [5, 6, 8]}


مع new_list = my_list ، ليس لديك بالفعل قائمتين. تنسخ المهمة فقط المرجع إلى القائمة ، وليس القائمة الفعلية ، بحيث يشير كل من my_list و my_list إلى نفس القائمة بعد التعيين.

لنسخ القائمة فعليًا ، لديك العديد من الاحتمالات:

  • يمكنك قطعها:

    new_list = old_list[:]
    

    رأي أليكس مارتيلي (على الأقل في عام 2007 ) حول هذا الموضوع ، أنه بناء لغوي غريب وليس من المنطقي استخدامه على الإطلاق . ؛) في رأيه ، واحد المقبل هو أكثر قابلية للقراءة).

  • يمكنك استخدام الدالة list() المضمنة:

    new_list = list(old_list)
    
  • يمكنك استخدام generic copy.copy() :

    import copy
    new_list = copy.copy(old_list)
    

    هذا أبطأ قليلاً من list() لأنه يجب أن تعرف نوع البيانات old_list أولاً.

  • إذا كانت القائمة تحتوي على كائنات وترغب في نسخها أيضًا ، فاستخدم copy.deepcopy() :

    import copy
    new_list = copy.deepcopy(old_list)
    

    من الواضح أن أبطأ وأكثر طريقة تحتاج الذاكرة ، ولكن في بعض الأحيان لا مفر منه.

مثال:

import copy

class Foo(object):
    def __init__(self, val):
         self.val = val

    def __repr__(self):
        return str(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a[:]
c = list(a)
d = copy.copy(a)
e = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print('original: %r\n slice: %r\n list(): %r\n copy: %r\n deepcopy: %r'
      % (a, b, c, d, e))

نتيجة:

original: ['foo', 5, 'baz']
slice: ['foo', 5]
list(): ['foo', 5]
copy: ['foo', 5]
deepcopy: ['foo', 1]

هناك طريقة بسيطة جدًا مستقلة عن إصدار python كانت مفقودة في الإجابات المقدمة بالفعل والتي يمكنك استخدامها معظم الوقت (على الأقل أقوم بذلك):

new_list = my_list * 1       #Solution 1 when you are not using nested lists

ومع ذلك ، إذا كانت my_list تحتوي على حاويات أخرى (على سبيل المثال ، القوائم المتداخلة) ، فيجب عليك استخدام deepcopy مثل الآخرين الموجودين في الإجابات أعلاه من مكتبة النسخ. فمثلا:

import copy
new_list = copy.deepcopy(my_list)   #Solution 2 when you are using nested lists

. مكافأة : إذا كنت لا ترغب في نسخ العناصر استخدم (ويعرف أيضا باسم نسخة ضحلة):

new_list = my_list[:]

دعونا نفهم الفرق بين الحل رقم 1 والحل رقم 2

>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55 
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])

كما ترى ، كان الحل رقم 1 يعمل على أكمل وجه عندما لم نكن نستخدم القوائم المتداخلة. دعونا نتحقق مما سيحدث عندما نطبق الحل رقم 1 على القوائم المتداخلة.

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   #Solution#1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       #Solution #2 - DeepCopy worked in nested list

يفاجئني أنه لم يتم ذكر ذلك بعد ، لذلك من أجل الاكتمال ...

يمكنك تنفيذ تفريغ القائمة مع "عامل المشطوب": * ، الذي سينسخ أيضًا عناصر قائمتك.

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]

الجانب السلبي الواضح لهذه الطريقة هو أنه متاح فقط في Python 3.5+.

ومع ذلك ، يبدو أن التوقيت يبدو أفضل من الطرق الشائعة الأخرى.

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

new_list = list(old_list)


new_list = my_list[:]

new_list = my_list حاول فهم هذا. لنفترض أن my_list موجود في ذاكرة الكومة في الموقع X أي أن my_list يشير إلى X. الآن عن طريق تعيين new_list = my_list فأنت تقوم بالإشارة إلى new_list = my_list إلى X. يُعرف هذا باسم "الضحلة الضحلة".

الآن إذا قمت بتعيين new_list = my_list[:] فأنت ببساطة تقوم بنسخ كل كائن من my_list إلى new_list. هذا هو المعروف باسم النسخ العميق.

الطريقة الأخرى التي يمكنك القيام بها هي:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)




clone