Pertanyaan Bagaimana cara mengkloning atau menyalin daftar?


Apa saja pilihan untuk mengkloning atau menyalin daftar dengan Python?

Menggunakan new_list = my_list lalu modifikasi new_list setiap saat my_list perubahan.
Kenapa ini?


1690
2018-04-10 08:49


asal


Jawaban:


Dengan new_list = my_listAnda sebenarnya tidak memiliki dua daftar. Penugasan hanya menyalin referensi ke daftar, bukan daftar sebenarnya, jadi keduanya new_list dan my_list lihat daftar yang sama setelah penugasan.

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

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

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

    new_list = old_list[:]
    

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

  • Anda dapat menggunakan built in list() fungsi:

    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 datatype old_list pertama.

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

    import copy
    new_list = copy.deepcopy(old_list)
    

    Tentunya 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 str(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\n list.copy(): %r\n slice: %r\n list(): %r\n copy: %r\n deepcopy: %r'
      % (a, b, c, d, e, f))

Hasil:

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

2315
2018-04-10 08:55



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

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

Jadi yang tercepat adalah daftar mengiris. Tetapi sadari itu copy.copy(), list[:] dan list(list)tidak seperti copy.deepcopy() dan versi python tidak menyalin daftar, kamus, dan instance kelas dalam daftar, jadi jika yang asli berubah, mereka akan berubah dalam daftar yang disalin juga dan sebaliknya.

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

EDIT: Menambahkan gaya baru, kelas-gaya lama dan mendikte tolok ukur, dan membuat versi python jauh lebih cepat dan menambahkan beberapa metode termasuk daftar ekspresi dan extend().


447
2018-04-10 10:16



Saya sudah telah diberitahu bahwa Python 3.3+ tambah list.copy() metode, yang harus secepat mengiris:

newlist = old_list.copy()


116
2017-07-23 12:32



Apa saja pilihan 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 baru yang setara.

Salinan daftar dangkal

Salinan dangkal hanya menyalin daftar itu sendiri, yang merupakan wadah referensi ke objek dalam daftar. Jika benda-benda itu mengandung diri dapat berubah dan satu berubah, perubahan itu akan tercermin dalam kedua daftar.

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

Python 2

Dalam Python 2, cara idiomatis membuat salinan daftar dangkal adalah dengan potongan lengkap dari aslinya:

a_copy = a_list[:]

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

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

Dalam Python 3, daftar dapatkan list.copy metode:

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 penunjuk lain tidak membuat salinan

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

my_list hanya nama yang menunjuk ke daftar sebenarnya di memori. Ketika kamu berkata new_list = my_list Anda tidak membuat salinan, Anda hanya menambahkan nama lain yang menunjuk pada daftar asli itu di 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 array dari pointer ke konten, sehingga salinan dangkal hanya menyalin pointer, sehingga Anda memiliki dua daftar yang berbeda, tetapi mereka memiliki konten yang sama. Untuk membuat salinan dari konten, Anda membutuhkan salinan yang mendalam.

Salinan dalam

Untuk membuat sebuah salinan yang mendalam dari daftar, dengan menggunakan Python 2 atau 3 deepcopy dalam copy modul:

import copy
a_deep_copy = copy.deepcopy(a_list)

Untuk mendemonstrasikan bagaimana ini memungkinkan kita untuk 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 sepenuhnya berbeda dari aslinya. Anda bisa menggulirkan fungsi Anda sendiri - tetapi tidak. Anda mungkin membuat bug yang tidak akan Anda miliki dengan menggunakan fungsi pustaka cetak standar.

Jangan gunakan eval

Anda mungkin melihat ini digunakan sebagai cara untuk mencetak dalam-dalam, 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. Ini tidak dapat diandalkan, jika sub-elemen 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

87
2017-10-25 12:13



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_list dan mengikatnya new_list demikian juga. Tidak peduli nama yang Anda gunakan di sana, hanya ada satu daftar, jadi perubahan dilakukan saat merujuknya sebagai my_list akan tetap ada saat merujuknya sebagai new_list. Masing-masing jawaban lain untuk pertanyaan ini memberi Anda berbagai cara untuk membuat objek baru untuk diikat new_list.

Setiap elemen dari daftar bertindak seperti nama, di mana setiap elemen mengikat non-eksklusif ke suatu 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 membuat daftar Anda menyalin satu langkah lebih jauh, salin setiap objek yang merujuk pada daftar Anda, dan ikat 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 mendalam, karena setiap elemen dari daftar dapat merujuk ke objek lain, seperti daftar terikat ke elemennya. Untuk secara rekursif menyalin setiap elemen dalam daftar, dan kemudian setiap objek lain yang dirujuk oleh masing-masing elemen, dan seterusnya: melakukan salinan mendalam.

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

Lihat dokumentasi untuk informasi lebih lanjut tentang kasus-kasus sudut dalam menyalin.


42
2017-11-23 16:45



new_list = list(old_list)


30
2018-04-10 09:03



Menggunakan thing[:]

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

27
2018-04-10 08:53



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


26
2018-04-10 08:53



Semua kontributor lainnya memberi besar jawaban, yang bekerja ketika Anda memiliki daftar dimensi tunggal (diratakan), namun dari metode yang disebutkan sejauh ini, saja copy.deepcopy() berfungsi untuk mengkloning / menyalin daftar dan tidak memilikinya menunjuk ke nested list objek ketika Anda bekerja dengan multidimensi, daftar bertingkat (daftar daftar). Sementara Felix Kling Mengacu pada jawabannya, ada sedikit lebih banyak masalah dan mungkin solusi menggunakan built-in yang mungkin membuktikan alternatif yang lebih cepat untuk deepcopy.

Sementara new_list = old_list[:], copy.copy(old_list)' dan untuk Py3k old_list.copy() bekerja untuk daftar single-level, mereka kembali ke menunjuk pada list objek bersarang di dalam old_list dan new_list, dan berubah menjadi salah satu dari list benda-benda yang diabadikan di yang lain.

Edit: Informasi baru dibawa ke cahaya

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

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

Akan terlihat bahwa satu-satunya pilihan sebenarnya adalah menginisialisasi beberapa daftar dan mengerjakannya secara mandiri. Jika ada yang punya saran lain, untuk bagaimana menangani menyalin daftar multidimensi, itu akan dihargai.

Seperti yang telah dinyatakan orang lain, di sana dapat  signifikan masalah kinerja menggunakan copy modul dan copy.deepcopy  untuk daftar multidimensional.  Mencoba mencari cara lain untuk menyalin daftar multidimensi tanpa menggunakan deepcopy, (Saya sedang mengerjakan masalah untuk kursus yang hanya memungkinkan 5 detik untuk seluruh algoritma berjalan untuk menerima kredit), saya datang dengan cara menggunakan fungsi built-in untuk membuat salinan dari nested list tanpa meminta mereka menunjuk satu sama lain atau di list objek bersarang di dalamnya. Saya menggunakan eval() dan repr() dalam tugas untuk membuat salinan daftar lama ke daftar baru tanpa membuat tautan ke daftar lama. Itu mengambil bentuk:

new_list = eval(repr(old_list))

Pada dasarnya apa yang dilakukan adalah membuat representasi old_list sebagai string dan kemudian mengevaluasi string seolah-olah itu adalah objek yang mewakili string. Dengan melakukan ini, tidak ada tautan ke aslinya list objek dibuat. Baru list objek dibuat dan setiap variabel menunjuk ke objek independennya sendiri. Berikut ini contoh menggunakan daftar bersarang 2 dimensi.

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

Jika Anda kemudian memeriksa isi setiap daftar, misalnya daftar 4 oleh 3, Python akan kembali

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

Meskipun ini mungkin bukan cara yang benar secara kanonik atau sintaksis untuk melakukannya, tampaknya itu berjalan dengan baik. Saya belum menguji performa, tetapi saya akan menebaknya eval() dan rep() akan memiliki lebih sedikit overhead untuk menjalankan deepcopy akan.


18
2017-07-10 03:51



Python 3.6.0 Waktu

Berikut adalah hasil waktu menggunakan Python 3.6.0. Perlu diingat saat-saat ini relatif terhadap satu sama lain, tidak mutlak.

Saya terjebak untuk hanya melakukan salinan dangkal, dan juga menambahkan beberapa metode baru yang tidak mungkin dalam Python2, seperti list.copy() (Python3 iris setara) dan daftar membongkar (*new_list, = list):

METHOD                  TIME TAKEN
b = a[:]                6.468942025996512   #Python2 winner
b = a.copy()            6.986593422974693   #Python3 "slice equivalent"
b = []; b.extend(a)     7.309216841997113
b = a[0:len(a)]         10.916740721993847
*b, = a                 11.046738261007704
b = list(a)             11.761539687984623
b = [i for i in a]      24.66165203397395
b = copy.copy(a)        30.853400873980718
b = []
for item in a:
  b.append(item)        48.19176080400939

Kita bisa melihat pemenang lama masih keluar di atas, tetapi tidak benar-benar dengan jumlah besar, mengingat peningkatan pembacaan Python3 list.copy() pendekatan.

Perhatikan bahwa metode ini dilakukan tidak hasil output yang setara untuk setiap input selain daftar. Mereka semua bekerja untuk benda-benda yang dapat diiris, beberapa bekerja untuk setiap iterable, tetapi hanya copy.copy() berfungsi untuk objek Python apa pun.


Berikut ini 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\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", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\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 = []\nfor 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))

12
2018-04-05 01:01