Bagaimana cara mengkloning atau menyalin daftar?


2550

Apa opsi untuk mengkloning atau menyalin daftar dengan Python?

Saat menggunakan new_list = my_list, setiap modifikasi ke new_listperubahan my_listsetiap saat. Kenapa ini?

Jawaban:


3330

Dengan new_list = my_list, Anda sebenarnya tidak memiliki dua daftar. Tugas hanya menyalin referensi ke daftar, bukan daftar yang sebenarnya, jadi keduanya new_listdan my_listmerujuk ke daftar yang sama setelah penugasan.

Untuk benar-benar menyalin daftar, Anda memiliki berbagai kemungkinan:

  • Anda dapat menggunakan list.copy()metode builtin (tersedia sejak Python 3.3):

    new_list = old_list.copy()
  • Anda dapat mengirisnya:

    new_list = old_list[:]

    Pendapat Alex Martelli (setidaknya pada tahun 2007 ) tentang ini adalah, bahwa ini adalah sintaks aneh dan tidak masuk akal untuk menggunakannya . ;) (Menurutnya, yang berikutnya lebih mudah dibaca).

  • Anda dapat menggunakan list()fungsi bawaan:

    new_list = list(old_list)
  • Anda dapat menggunakan generik copy.copy():

    import copy
    new_list = copy.copy(old_list)

    Ini sedikit lebih lambat daripada list()karena harus mencari tahu tipe data old_listpertama.

  • Jika daftar berisi objek dan Anda juga ingin menyalinnya, gunakan generik copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)

    Jelas metode yang paling lambat dan paling membutuhkan memori, tetapi kadang-kadang tidak dapat dihindari.

Contoh:

import copy

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

    def __repr__(self):
        return 'Foo({!r})'.format(self.val)

foo = Foo(1)

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

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

print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

Hasil:

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

7
Jika saya tidak salah: newlist = [*mylist]juga kemungkinan di Python 3. newlist = list(mylist)mungkin lebih jelas.
Stéphane

9
kemungkinan lain adalah new_list = old_list * 1
aris

4
Manakah dari metode ini yang merupakan salinan dangkal dan mana dari mereka yang merupakan salinan dalam?
Eswar

4
@ Eswar: semua kecuali yang terakhir membuat salinan yang dangkal
Felix Kling

3
@ Eswar itu adalah salinan yang dangkal.
juanpa.arrivillaga

604

Felix sudah memberikan jawaban yang sangat baik, tetapi saya pikir saya akan melakukan perbandingan cepat dari berbagai metode:

  1. 10,59 dtk (105.9 us / itn) - copy.deepcopy(old_list)
  2. 10,16 dtk (101.6us / itn) - python murni Copy() metode menyalin kelas dengan deepcopy
  3. 1.488 dtk (14.88us / itn) - python murni Copy() metode tidak menyalin kelas (hanya perintah / daftar / tupel)
  4. 0,325 dtk (3,25us / itn) - for item in old_list: new_list.append(item)
  5. 0,217 dtk (2,17 us / itn) - [i for i in old_list]( daftar pemahaman )
  6. 0,186 dtk (1,86 us / itn) - copy.copy(old_list)
  7. 0,075 dtk (0,75us / itn) - list(old_list)
  8. 0,053 dtk (0,53 us / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 dtk (0,39us / itn) - old_list[:]( daftar pengirisan )

Jadi yang tercepat adalah daftar slicing. Tapi ketahuilah itu copy.copy(), list[:]dan list(list), tidak seperticopy.deepcopy() dan versi python tidak menyalin daftar, kamus dan instance kelas dalam daftar, jadi jika aslinya berubah, mereka akan berubah dalam daftar yang disalin juga dan sebaliknya.

(Ini skripnya jika ada yang tertarik atau ingin mengangkat masalah apa pun :)

from copy import deepcopy

class old_class:
    def __init__(self):
        self.blah = 'blah'

class new_class(object):
    def __init__(self):
        self.blah = 'blah'

dignore = {str: None, unicode: None, int: None, type(None): None}

def Copy(obj, use_deepcopy=True):
    t = type(obj)

    if t in (list, tuple):
        if t == tuple:
            # Convert to a list if a tuple to 
            # allow assigning to when copying
            is_tuple = True
            obj = list(obj)
        else: 
            # Otherwise just do a quick slice copy
            obj = obj[:]
            is_tuple = False

        # Copy each item recursively
        for x in xrange(len(obj)):
            if type(obj[x]) in dignore:
                continue
            obj[x] = Copy(obj[x], use_deepcopy)

        if is_tuple: 
            # Convert back into a tuple again
            obj = tuple(obj)

    elif t == dict: 
        # Use the fast shallow dict copy() method and copy any 
        # values which aren't immutable (like lists, dicts etc)
        obj = obj.copy()
        for k in obj:
            if type(obj[k]) in dignore:
                continue
            obj[k] = Copy(obj[k], use_deepcopy)

    elif t in dignore: 
        # Numeric or string/unicode? 
        # It's immutable, so ignore it!
        pass 

    elif use_deepcopy: 
        obj = deepcopy(obj)
    return obj

if __name__ == '__main__':
    import copy
    from time import time

    num_times = 100000
    L = [None, 'blah', 1, 543.4532, 
         ['foo'], ('bar',), {'blah': 'blah'},
         old_class(), new_class()]

    t = time()
    for i in xrange(num_times):
        Copy(L)
    print 'Custom Copy:', time()-t

    t = time()
    for i in xrange(num_times):
        Copy(L, use_deepcopy=False)
    print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t

    t = time()
    for i in xrange(num_times):
        copy.copy(L)
    print 'copy.copy:', time()-t

    t = time()
    for i in xrange(num_times):
        copy.deepcopy(L)
    print 'copy.deepcopy:', time()-t

    t = time()
    for i in xrange(num_times):
        L[:]
    print 'list slicing [:]:', time()-t

    t = time()
    for i in xrange(num_times):
        list(L)
    print 'list(L):', time()-t

    t = time()
    for i in xrange(num_times):
        [i for i in L]
    print 'list expression(L):', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(L)
    print 'list extend:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        for y in L:
            a.append(y)
    print 'list append:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(i for i in L)
    print 'generator expression extend:', time()-t

9
Karena Anda membuat tolok ukur, mungkin bermanfaat untuk menyertakan titik referensi. Apakah angka-angka ini masih akurat pada tahun 2017 menggunakan Python 3.6 dengan kode yang sepenuhnya dikompilasi? Saya mencatat jawaban di bawah ini ( stackoverflow.com/a/17810305/26219 ) sudah mempertanyakan jawaban ini.
Mark Edington

4
gunakan timeitmodul. juga, Anda tidak bisa menyimpulkan banyak dari tolok ukur mikro sewenang-wenang seperti ini.
Corey Goldberg

3
Jika Anda ingin menyertakan opsi baru untuk 3.5+, [*old_list]harus kira-kira setara dengan list(old_list), tetapi karena itu sintaks, bukan jalur panggilan fungsi umum, ini akan menghemat sedikit saat runtime (dan tidak seperti old_list[:], yang tidak mengetik konversi, [*old_list]bekerja pada setiap iterable dan menghasilkan a list).
ShadowRanger

3
@CoreyGoldberg untuk pembandingan-mikro yang sedikit lebih sembarang (menggunakan timeit, 50m berjalan bukannya 100k) lihat stackoverflow.com/a/43220129/3745896
River

1
@ShadowRanger [*old_list]tampaknya mengungguli hampir semua metode lain. (lihat jawaban saya yang ditautkan dalam komentar sebelumnya)
River


126

Apa opsi untuk mengkloning atau menyalin daftar dengan Python?

Dalam Python 3, salinan dangkal dapat dibuat dengan:

a_copy = a_list.copy()

Dalam Python 2 dan 3, Anda bisa mendapatkan salinan dangkal dengan potongan penuh dari aslinya:

a_copy = a_list[:]

Penjelasan

Ada dua cara semantik untuk menyalin daftar. Salinan dangkal membuat daftar baru dari objek yang sama, salinan dalam membuat daftar baru yang berisi objek setara baru.

Salinan daftar dangkal

Salinan dangkal hanya menyalin daftar itu sendiri, yang merupakan wadah referensi ke objek dalam daftar. Jika objek yang terkandung sendiri bisa berubah dan satu diubah, perubahan akan tercermin di kedua daftar.

Ada berbagai cara untuk melakukan ini di Python 2 dan 3. Cara Python 2 juga akan bekerja di Python 3.

Python 2

Dalam Python 2, cara idiomatis membuat salinan daftar yang dangkal adalah dengan sepotong yang asli:

a_copy = a_list[:]

Anda juga dapat mencapai hal yang sama dengan melewati daftar melalui konstruktor daftar,

a_copy = list(a_list)

tetapi menggunakan konstruktor kurang efisien:

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

Python 3

Di Python 3, daftar dapatkan list.copymetode:

a_copy = a_list.copy()

Dalam 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

Membuat pointer lain tidak membuat salinan

Menggunakan new_list = my_list kemudian memodifikasi new_list setiap kali perubahan my_list. Kenapa ini?

my_listhanyalah nama yang menunjuk ke daftar aktual dalam memori. Ketika Anda mengatakan new_list = my_listAnda tidak membuat salinan, Anda hanya menambahkan nama lain yang menunjuk pada daftar asli dalam memori. Kami dapat memiliki masalah serupa ketika kami membuat salinan daftar.

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

Daftar ini hanyalah sebuah array pointer ke konten, jadi salinan dangkal hanya menyalin pointer, dan Anda memiliki dua daftar berbeda, tetapi mereka memiliki konten yang sama. Untuk membuat salinan konten, Anda perlu salinan yang dalam.

Salinan dalam

Untuk membuat salinan daftar, dengan Python 2 atau 3, gunakan deepcopydalam copymodul :

import copy
a_deep_copy = copy.deepcopy(a_list)

Untuk menunjukkan bagaimana ini memungkinkan kami membuat sub-daftar baru:

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

Jadi kita melihat bahwa daftar yang disalin dalam adalah daftar yang sama sekali berbeda dari aslinya. Anda dapat memutar fungsi Anda sendiri - tetapi tidak. Anda kemungkinan akan membuat bug yang tidak Anda miliki dengan menggunakan fungsi deepcopy perpustakaan standar.

Jangan gunakan eval

Anda mungkin melihat ini digunakan sebagai cara untuk melakukan deepcopy, tetapi jangan lakukan itu:

problematic_deep_copy = eval(repr(a_list))
  1. Ini berbahaya, terutama jika Anda mengevaluasi sesuatu dari sumber yang tidak Anda percayai.
  2. Itu tidak dapat diandalkan, jika subelemen yang Anda salin tidak memiliki representasi yang dapat dievaluasi untuk mereproduksi elemen yang setara.
  3. Ini juga kurang berkinerja.

Dalam 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

pada 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

1
Anda tidak perlu deepcopy jika daftar adalah 2D. Jika itu adalah daftar daftar, dan daftar itu tidak memiliki daftar di dalamnya, Anda bisa menggunakan for for. Saat ini, saya menggunakan list_copy=[] for item in list: list_copy.append(copy(item))dan jauh lebih cepat.
John Locke

54

Ada banyak jawaban yang memberi tahu Anda cara membuat salinan yang tepat, tetapi tidak ada yang mengatakan mengapa 'salinan' asli Anda gagal.

Python tidak menyimpan nilai dalam variabel; itu mengikat nama ke objek. Tugas asli Anda mengambil objek yang dirujuk oleh my_listdan mengikatnya new_listjuga. Apa pun nama yang Anda gunakan, hanya ada satu daftar, jadi perubahan yang dibuat saat merujuknya my_listakan tetap ada saat merujuknya sebagai new_list. Masing-masing jawaban lain untuk pertanyaan ini memberi Anda cara berbeda untuk membuat objek baru untuk diikat new_list.

Setiap elemen daftar bertindak seperti nama, di mana setiap elemen mengikat secara non-eksklusif ke objek. Salinan dangkal membuat daftar baru yang elemennya mengikat objek yang sama seperti sebelumnya.

new_list = list(my_list)  # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]

Untuk membawa salinan daftar Anda satu langkah lebih jauh, salin setiap objek yang dirujuk daftar Anda, dan ikat salinan elemen tersebut ke daftar baru.

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]

Ini belum merupakan salinan yang dalam, karena setiap elemen daftar dapat merujuk ke objek lain, sama seperti daftar terikat ke elemen-elemennya. Untuk secara rekursif menyalin setiap elemen dalam daftar, dan kemudian masing-masing objek lain disebut oleh masing-masing elemen, dan seterusnya: melakukan salinan yang dalam.

import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)

Lihat dokumentasi untuk informasi lebih lanjut tentang kasus sudut dalam penyalinan.


38

Menggunakan thing[:]

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

35

Mari kita mulai dari awal dan mengeksplorasi pertanyaan ini.

Jadi anggaplah Anda memiliki dua daftar:

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

Dan kita harus menyalin kedua daftar, sekarang mulai dari daftar pertama:

Jadi pertama mari kita coba dengan menetapkan variabel copyke daftar asli kami, list_1:

copy=list_1

Sekarang jika Anda berpikir menyalin menyalin list_1, maka Anda salah. The idFungsi dapat menunjukkan kepada kita jika dua variabel dapat menunjuk ke objek yang sama. Mari kita coba ini:

print(id(copy))
print(id(list_1))

Outputnya adalah:

4329485320
4329485320

Kedua variabel tersebut adalah argumen yang sama persis. Apakah kamu terkejut?

Jadi seperti yang kita tahu python tidak menyimpan apa pun dalam variabel, Variabel hanya merujuk ke objek dan objek menyimpan nilainya. Di sini objek adalah listtetapi kami membuat dua referensi ke objek yang sama dengan dua nama variabel yang berbeda. Ini berarti bahwa kedua variabel menunjuk ke objek yang sama, hanya dengan nama yang berbeda.

Ketika Anda melakukannya copy=list_1, itu sebenarnya dilakukan:

masukkan deskripsi gambar di sini

Di sini, di daftar gambar_1 dan salin adalah dua nama variabel tetapi objeknya sama untuk kedua variabel tersebut list

Jadi, jika Anda mencoba mengubah daftar yang disalin maka itu akan mengubah daftar asli juga karena daftar itu hanya ada di sana, Anda akan memodifikasi daftar itu, apa pun yang Anda lakukan dari daftar yang disalin atau dari daftar asli:

copy[0]="modify"

print(copy)
print(list_1)

keluaran:

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

Jadi itu memodifikasi daftar asli:

Sekarang mari kita beralih ke metode pythonic untuk menyalin daftar.

copy_1=list_1[:]

Metode ini memperbaiki masalah pertama yang kami miliki:

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

4338792136
4338791432

Jadi seperti yang kita lihat daftar kedua kami memiliki id yang berbeda dan itu berarti bahwa kedua variabel menunjuk ke objek yang berbeda. Jadi yang sebenarnya terjadi di sini adalah:

masukkan deskripsi gambar di sini

Sekarang mari kita coba memodifikasi daftar dan mari kita lihat apakah kita masih menghadapi masalah sebelumnya:

copy_1[0]="modify"

print(list_1)
print(copy_1)

Outputnya adalah:

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

Seperti yang Anda lihat, itu hanya mengubah daftar yang disalin. Itu artinya berhasil.

Apakah Anda pikir kita sudah selesai? Tidak. Mari kita coba menyalin daftar bersarang kita.

copy_2=list_2[:]

list_2harus merujuk ke objek lain yang merupakan salinan list_2. Mari kita periksa:

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

Kami mendapatkan output:

4330403592 4330403528

Sekarang kita dapat mengasumsikan kedua daftar menunjuk objek yang berbeda, jadi sekarang mari kita coba memodifikasinya dan mari kita lihat memberikan apa yang kita inginkan:

copy_2[0][1]="modify"

print(list_2,copy_2)

Ini memberi kami output:

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

Ini mungkin tampak sedikit membingungkan, karena metode yang sama yang kami gunakan sebelumnya bekerja Mari kita coba memahami ini.

Saat kamu melakukan:

copy_2=list_2[:]

Anda hanya menyalin daftar luar, bukan daftar dalam. Kita dapat menggunakan idfungsi sekali lagi untuk memeriksa ini.

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

Outputnya adalah:

4329485832
4329485832

Ketika kita melakukannya copy_2=list_2[:], ini terjadi:

masukkan deskripsi gambar di sini

Itu membuat salinan daftar tetapi hanya salinan daftar luar, bukan salinan daftar bersarang, daftar bersarang sama untuk kedua variabel, jadi jika Anda mencoba untuk memodifikasi daftar bersarang maka itu akan mengubah daftar asli juga karena objek daftar bersarang adalah sama untuk kedua daftar.

Apa solusinya? Solusinya adalah deepcopyfungsinya.

from copy import deepcopy
deep=deepcopy(list_2)

Mari kita periksa ini:

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

4322146056 4322148040

Kedua daftar luar memiliki ID yang berbeda, mari kita coba ini pada daftar bersarang dalam.

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

Outputnya adalah:

4322145992
4322145800

Karena Anda dapat melihat kedua ID berbeda, artinya kita dapat mengasumsikan bahwa kedua daftar bersarang menunjuk objek yang berbeda sekarang.

Ini berarti ketika Anda melakukan deep=deepcopy(list_2)apa yang sebenarnya terjadi:

masukkan deskripsi gambar di sini

Kedua daftar bersarang menunjuk objek yang berbeda dan mereka memiliki salinan daftar daftar terpisah sekarang.

Sekarang mari kita coba untuk memodifikasi daftar bersarang dan melihat apakah itu memecahkan masalah sebelumnya atau tidak:

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

Ini menghasilkan:

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

Seperti yang Anda lihat, itu tidak mengubah daftar bersarang asli, itu hanya mengubah daftar yang disalin.


34

Ungkapan Python untuk melakukan ini adalah newList = oldList[:]


34

Python 3,6 Pengaturan waktu

Berikut adalah hasil pengaturan waktu menggunakan Python 3.6.8. Ingatlah bahwa saat-saat ini relatif satu sama lain, bukan absolut.

Saya terjebak untuk hanya melakukan salinan dangkal, dan juga menambahkan beberapa metode baru yang tidak mungkin di Python2, seperti list.copy()( irisan setara Python3 ) dan dua bentuk daftar membongkar ( *new_list, = listdan new_list = [*list]):

METHOD                  TIME TAKEN
b = [*a]                2.75180600000021
b = a * 1               3.50215399999990
b = a[:]                3.78278899999986  # Python2 winner (see above)
b = a.copy()            4.20556500000020  # Python3 "slice equivalent" (see above)
b = []; b.extend(a)     4.68069800000012
b = a[0:len(a)]         6.84498999999959
*b, = a                 7.54031799999984
b = list(a)             7.75815899999997
b = [i for i in a]      18.4886440000000
b = copy.copy(a)        18.8254879999999
b = []
for item in a:
  b.append(item)        35.4729199999997

Kita dapat melihat pemenang Python2 masih bekerja dengan baik, tetapi tidak mendukung Python3 list.copy() banyak , terutama mengingat keterbacaan superior dari yang terakhir.

Kuda hitam adalah metode membongkar dan mengemas ( b = [*a]), yang ~ 25% lebih cepat daripada mengiris mentah, dan lebih dari dua kali lebih cepat dari metode membongkar lainnya ( *b, = a).

b = a * 1 juga sangat baik.

Perhatikan bahwa metode ini tidak menghasilkan hasil yang setara untuk input selain dari daftar. Mereka semua bekerja untuk objek yang dapat diiris, beberapa bekerja untuk setiap iterable, tetapi hanyacopy.copy() bekerja untuk objek Python yang lebih umum.


Berikut adalah kode pengujian untuk pihak yang berkepentingan ( Templat dari sini ):

import timeit

COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'

print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a:  b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))

1
Masih dapat mengkonfirmasi cerita serupa di 3.8 b=[*a]- satu-satunya cara yang jelas untuk melakukannya;).
SuperShoot

20

Semua kontributor lain memberikan jawaban yang bagus , yang bekerja ketika Anda memiliki daftar satu dimensi (diratakan), namun metode yang disebutkan sejauh ini, hanya copy.deepcopy()berfungsi untuk mengkloning / menyalin daftar dan tidak mengarahkannya ke listobjek bersarang saat Anda berada. bekerja dengan multidimensi, daftar bersarang (daftar daftar). Sementara Felix Kling merujuknya dalam jawabannya, ada sedikit lebih banyak untuk masalah ini dan mungkin solusi menggunakan built-in yang mungkin membuktikan alternatif yang lebih cepat deepcopy.

Sementara new_list = old_list[:], copy.copy(old_list)'dan untuk Py3k old_list.copy()bekerja untuk daftar level tunggal, mereka kembali menunjuk ke listobjek yang bersarang di dalam old_listdan new_list, dan mengubah ke salah satulist objek diabadikan di yang lain.

Sunting: Informasi baru terungkap

Seperti yang ditunjukkan oleh Aaron Hall dan PM 2Ring menggunakan eval()tidak hanya ide yang buruk, itu juga jauh lebih lambat daripada copy.deepcopy().

Ini berarti bahwa untuk daftar multidimensi, satu-satunya pilihan adalah copy.deepcopy(). Dengan itu dikatakan, itu benar-benar bukan pilihan karena kinerja berjalan jauh ke selatan ketika Anda mencoba menggunakannya pada array multidimensi berukuran sedang. saya mencoba untuktimeit menggunakan array 42x42, tidak pernah terdengar atau bahkan sebesar itu untuk aplikasi bioinformatika, dan saya menyerah menunggu jawaban dan baru mulai mengetikkan edit saya ke posting ini.

Tampaknya satu-satunya pilihan nyata adalah menginisialisasi banyak daftar dan bekerja secara mandiri. Jika ada yang punya saran lain, untuk bagaimana menangani penyalinan daftar multidimensi, itu akan dihargai.

Seperti yang telah dinyatakan orang lain, ada masalah kinerja yang signifikan menggunakan copymodul dan copy.deepcopy untuk daftar multidimensi .


5
Ini tidak akan selalu berhasil, karena tidak ada jaminan bahwa string yang dikembalikan oleh repr()cukup untuk membuat ulang objek. Juga, eval()merupakan alat pilihan terakhir; lihat Eval benar-benar berbahaya oleh veteran SO Ned Batchelder untuk detailnya. Jadi, ketika Anda menganjurkan penggunaan eval()Anda benar - benar harus menyebutkan bahwa itu bisa berbahaya.
PM 2Ring

1
Titik adil. Meskipun saya pikir poin Batchelder adalah memiliki eval()fungsi dalam Python secara umum adalah risiko. Itu tidak begitu banyak apakah Anda menggunakan fungsi dalam kode tetapi itu adalah lubang keamanan di Python dalam dan dari dirinya sendiri. Misalnya saya tidak menggunakannya dengan fungsi yang menerima masukan dari input(), sys.agrvatau bahkan file teks. Itu lebih sepanjang garis menginisialisasi daftar multidimensi kosong sekali, dan kemudian hanya memiliki cara menyalinnya dalam satu lingkaran daripada menginisialisasi ulang pada setiap iterasi dari loop.
AMR

1
Seperti yang telah ditunjukkan oleh @AaronHall, kemungkinan ada masalah kinerja yang signifikan untuk digunakan new_list = eval(repr(old_list)), jadi selain itu adalah ide yang buruk, mungkin juga terlalu lambat untuk bekerja.
AMR

13

Ini mengejutkan saya bahwa ini belum disebutkan, jadi demi kelengkapan ...

Anda dapat melakukan pembongkaran daftar dengan "operator percikan":, *yang juga akan menyalin elemen daftar Anda.

old_list = [1, 2, 3]

new_list = [*old_list]

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

Kelemahan yang jelas untuk metode ini adalah bahwa itu hanya tersedia dalam Python 3.5+.

Meskipun demikian, pengaturan waktu tampaknya lebih baik daripada metode umum lainnya.

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)

1
Bagaimana cara metode ini ketika memodifikasi salinan?
not2qubit

2
@ not2qubit maksud Anda menambahkan atau mengedit elemen dari daftar baru. Dalam contoh old_listdan new_listdua daftar berbeda, mengedit satu tidak akan mengubah yang lain (kecuali jika Anda secara langsung mengubah elemen itu sendiri (seperti daftar daftar), tidak satupun dari metode ini adalah salinan yang dalam).
SCB

8

Sebuah pendekatan yang sangat sederhana tanpa versi python telah hilang dalam jawaban yang sudah diberikan yang dapat Anda gunakan sebagian besar waktu (setidaknya saya lakukan):

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

Namun, Jika my_list berisi wadah lain (misalnya, daftar bertingkat), Anda harus menggunakan deepcopy seperti yang disarankan dalam jawaban di atas dari perpustakaan salinan. Sebagai contoh:

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

. Bonus : Jika Anda tidak ingin menyalin elemen gunakan (alias salinan dangkal):

new_list = my_list[:]

Mari kita pahami perbedaan antara Solusi # 1 dan Solusi # 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])

Seperti yang Anda lihat Solusi # 1 bekerja dengan sempurna ketika kami tidak menggunakan daftar bersarang. Mari kita periksa apa yang akan terjadi ketika kita menerapkan solusi # 1 ke daftar bersarang.

>>> 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

8

Perhatikan bahwa ada beberapa kasus di mana jika Anda telah menetapkan kelas kustom Anda sendiri dan Anda ingin menyimpan atribut maka Anda harus menggunakan copy.copy()atau copy.deepcopy()bukan alternatif, misalnya dalam Python 3:

import copy

class MyList(list):
    pass

lst = MyList([1,2,3])

lst.name = 'custom list'

d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}


for k,v in d.items():
    print('lst: {}'.format(k), end=', ')
    try:
        name = v.name
    except AttributeError:
        name = 'NA'
    print('name: {}'.format(name))

Output:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list

5
new_list = my_list[:]

new_list = my_list Coba pahami ini. Katakanlah my_list ada di memori tumpukan di lokasi X yaitu my_list menunjuk ke X. Sekarang dengan menetapkan new_list = my_listAnda membiarkan New_list menunjuk ke X. Ini dikenal sebagai Copy dangkal.

Sekarang jika Anda menetapkan new_list = my_list[:]Anda hanya menyalin setiap objek dari my_list ke new_list. Ini dikenal sebagai Deep copy.

Cara lain yang dapat Anda lakukan adalah:

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

3

Saya ingin memposting sesuatu yang sedikit berbeda dari beberapa jawaban lainnya. Meskipun ini kemungkinan besar bukan opsi yang paling mudah dipahami, atau tercepat, ia memberikan sedikit pandangan ke dalam tentang seberapa dalam copy berfungsi, serta menjadi pilihan alternatif lain untuk penyalinan dalam. Tidak masalah jika fungsi saya memiliki bug, karena intinya adalah menunjukkan cara untuk menyalin objek seperti jawaban pertanyaan, tetapi juga menggunakan ini sebagai titik untuk menjelaskan cara kerja deepcopy pada intinya.

Inti dari setiap fungsi penyalinan yang dalam adalah cara untuk membuat salinan yang dangkal. Bagaimana? Sederhana. Setiap fungsi salin yang dalam hanya menduplikasi wadah dari objek yang tidak dapat diubah. Ketika Anda menyalin daftar bersarang, Anda hanya menduplikasi daftar luar, bukan objek yang bisa berubah di dalam daftar. Anda hanya menduplikasi kontainer. Hal yang sama juga berlaku untuk kelas. Saat Anda mendalami suatu kelas, Anda mendokumentasikan semua atributnya yang bisa berubah. Jadi bagaimana? Kenapa Anda hanya perlu menyalin wadah, seperti daftar, dicts, tuple, iters, kelas, dan instance kelas?

Itu mudah. Objek yang bisa berubah tidak dapat benar-benar diduplikasi. Itu tidak pernah bisa diubah, jadi itu hanya nilai tunggal. Itu berarti Anda tidak perlu menduplikasi string, angka, bools, atau yang lainnya. Tetapi bagaimana Anda akan menduplikasi kontainer? Sederhana. Anda hanya membuat inisialisasi wadah baru dengan semua nilai. Deepcopy bergantung pada rekursi. Ini menduplikasi semua wadah, bahkan yang memiliki wadah di dalamnya, sampai tidak ada wadah yang tersisa. Wadah adalah benda abadi.

Setelah Anda tahu itu, menduplikasi objek sepenuhnya tanpa referensi apa pun cukup mudah. Inilah fungsi untuk menyalin tipe data dasar (tidak akan berfungsi untuk kelas khusus tetapi Anda selalu dapat menambahkannya)

def deepcopy(x):
  immutables = (str, int, bool, float)
  mutables = (list, dict, tuple)
  if isinstance(x, immutables):
    return x
  elif isinstance(x, mutables):
    if isinstance(x, tuple):
      return tuple(deepcopy(list(x)))
    elif isinstance(x, list):
      return [deepcopy(y) for y in x]
    elif isinstance(x, dict):
      values = [deepcopy(y) for y in list(x.values())]
      keys = list(x.keys())
      return dict(zip(keys, values))

Deepcopy bawaan Python sendiri didasarkan pada contoh itu. Satu-satunya perbedaan adalah mendukung tipe lain, dan juga mendukung kelas pengguna dengan menduplikasi atribut menjadi kelas duplikat baru, dan juga memblokir rekursi tak terbatas dengan referensi ke objek yang sudah terlihat menggunakan daftar memo atau kamus. Dan itu benar-benar untuk membuat salinan yang dalam. Pada intinya, membuat salinan yang dalam hanya membuat salinan yang dangkal. Saya harap jawaban ini menambah sesuatu pada pertanyaan.

CONTOH

Katakanlah Anda memiliki daftar ini: [1, 2, 3] . Angka-angka yang tidak dapat diubah tidak dapat diduplikasi, tetapi lapisan lainnya bisa. Anda dapat menduplikatnya menggunakan pemahaman daftar: [x untuk x dalam [1, 2, 3]

Sekarang, bayangkan Anda memiliki daftar ini: [[1, 2], [3, 4], [5, 6]] . Kali ini, Anda ingin membuat fungsi, yang menggunakan rekursi untuk menyalin semua lapisan daftar. Alih-alih pemahaman daftar sebelumnya:

[x for x in _list]

Ini menggunakan yang baru untuk daftar:

[deepcopy_list(x) for x in _list]

Dan deepcopy_list terlihat seperti ini:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]

Maka sekarang Anda memiliki fungsi yang dapat menyalin semua daftar str, bools, floast, int dan bahkan daftar ke banyak lapisan tanpa batas menggunakan rekursi. Dan begitulah, deepcopying.

TLDR : Deepcopy menggunakan rekursi untuk menduplikasi objek, dan hanya mengembalikan objek yang sama seperti sebelumnya, karena objek yang tidak dapat diubah tidak dapat diduplikasi. Namun, ia menduplikasi lapisan paling dalam dari objek yang bisa berubah hingga mencapai lapisan paling bisa berubah dari suatu objek.


3

Perspektif praktis sedikit untuk melihat ke memori melalui id dan gc.

>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']

>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272) 
     |           |
      -----------

>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
     |           |           |
      -----------------------

>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
     |           |           |
      -----------------------

>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word'])  # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
     |           |
      -----------

>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
     |           |           |
      -----------------------

>>> import gc
>>> gc.get_referrers(a[0]) 
[['hell', 'word'], ['hell', 'word']]  # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None) 

3

Ingat itu dalam Python ketika Anda melakukannya:

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2 tidak menyimpan daftar sebenarnya, tetapi referensi ke list1. Jadi ketika Anda melakukan apa saja untuk list1, list2 berubah juga. gunakan modul salin (bukan default, unduh di pip) untuk membuat salinan asli dari daftar ( copy.copy()untuk daftar sederhana, copy.deepcopy()untuk yang bersarang). Ini membuat salinan yang tidak berubah dengan daftar pertama.


1

Opsi deepcopy adalah satu-satunya metode yang bekerja untuk saya:

from copy import deepcopy

a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')

mengarah ke hasil:

Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------

1

Ini karena, baris new_list = my_listmemberikan referensi baru ke variabel my_listyang new_list sama dengan Ckode yang diberikan di bawah ini,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

Anda harus menggunakan modul salin untuk membuat daftar baru

import copy
new_list = copy.deepcopy(my_list)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.