Apa opsi untuk mengkloning atau menyalin daftar dengan Python?
Saat menggunakan new_list = my_list
, setiap modifikasi ke new_list
perubahan my_list
setiap saat. Kenapa ini?
Dengan new_list = my_list
, Anda sebenarnya tidak memiliki dua daftar. Tugas hanya menyalin referensi ke daftar, bukan daftar yang sebenarnya, jadi keduanya new_list
dan my_list
merujuk 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_list
pertama.
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)]
newlist = [*mylist]
juga kemungkinan di Python 3.newlist = list(mylist)
mungkin lebih jelas.Felix sudah memberikan jawaban yang sangat baik, tetapi saya pikir saya akan melakukan perbandingan cepat dari berbagai metode:
copy.deepcopy(old_list)
Copy()
metode menyalin kelas dengan deepcopyCopy()
metode tidak menyalin kelas (hanya perintah / daftar / tupel)for item in old_list: new_list.append(item)
[i for i in old_list]
( daftar pemahaman )copy.copy(old_list)
list(old_list)
new_list = []; new_list.extend(old_list)
old_list[:]
( daftar pengirisan )Jadi yang tercepat adalah daftar slicing. Tapi ketahuilah itu
copy.copy()
,list[:]
danlist(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 :)
sumber
timeit
modul. juga, Anda tidak bisa menyimpulkan banyak dari tolok ukur mikro sewenang-wenang seperti ini.[*old_list]
harus kira-kira setara denganlist(old_list)
, tetapi karena itu sintaks, bukan jalur panggilan fungsi umum, ini akan menghemat sedikit saat runtime (dan tidak sepertiold_list[:]
, yang tidak mengetik konversi,[*old_list]
bekerja pada setiap iterable dan menghasilkan alist
).timeit
, 50m berjalan bukannya 100k) lihat stackoverflow.com/a/43220129/3745896[*old_list]
tampaknya mengungguli hampir semua metode lain. (lihat jawaban saya yang ditautkan dalam komentar sebelumnya)Saya telah diberitahu bahwa Python 3.3+ menambahkan
list.copy()
metode, yang harus secepat memotong:newlist = old_list.copy()
sumber
s.copy()
membuat salinan dangkals
(sama sepertis[:]
).python3.8
,.copy()
adalah sedikit lebih cepat daripada mengiris. Lihat jawaban @AaronsHall di bawah ini.Dalam Python 3, salinan dangkal dapat dibuat dengan:
Dalam Python 2 dan 3, Anda bisa mendapatkan salinan dangkal dengan potongan penuh dari aslinya:
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:
Anda juga dapat mencapai hal yang sama dengan melewati daftar melalui konstruktor daftar,
tetapi menggunakan konstruktor kurang efisien:
Python 3
Di Python 3, daftar dapatkan
list.copy
metode:Dalam Python 3.5:
Membuat pointer lain tidak membuat salinan
my_list
hanyalah nama yang menunjuk ke daftar aktual dalam memori. Ketika Anda mengatakannew_list = my_list
Anda 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.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
deepcopy
dalamcopy
modul :Untuk menunjukkan bagaimana ini memungkinkan kami membuat sub-daftar baru:
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:
Dalam 64 bit Python 2.7:
pada 64 bit Python 3.5:
sumber
list_copy=[]
for item in list: list_copy.append(copy(item))
dan jauh lebih cepat.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 mengikatnyanew_list
juga. Apa pun nama yang Anda gunakan, hanya ada satu daftar, jadi perubahan yang dibuat saat merujuknyamy_list
akan tetap ada saat merujuknya sebagainew_list
. Masing-masing jawaban lain untuk pertanyaan ini memberi Anda cara berbeda untuk membuat objek baru untuk diikatnew_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.
Untuk membawa salinan daftar Anda satu langkah lebih jauh, salin setiap objek yang dirujuk daftar Anda, dan ikat salinan elemen tersebut ke daftar baru.
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.
Lihat dokumentasi untuk informasi lebih lanjut tentang kasus sudut dalam penyalinan.
sumber
Menggunakan
thing[:]
sumber
Mari kita mulai dari awal dan mengeksplorasi pertanyaan ini.
Jadi anggaplah Anda memiliki dua daftar:
Dan kita harus menyalin kedua daftar, sekarang mulai dari daftar pertama:
Jadi pertama mari kita coba dengan menetapkan variabel
copy
ke daftar asli kami,list_1
:Sekarang jika Anda berpikir menyalin menyalin list_1, maka Anda salah. The
id
Fungsi dapat menunjukkan kepada kita jika dua variabel dapat menunjuk ke objek yang sama. Mari kita coba ini:Outputnya adalah:
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
list
tetapi 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: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:
keluaran:
Jadi itu memodifikasi daftar asli:
Sekarang mari kita beralih ke metode pythonic untuk menyalin daftar.
Metode ini memperbaiki masalah pertama yang kami miliki:
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:
Sekarang mari kita coba memodifikasi daftar dan mari kita lihat apakah kita masih menghadapi masalah sebelumnya:
Outputnya adalah:
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.
list_2
harus merujuk ke objek lain yang merupakan salinanlist_2
. Mari kita periksa:Kami mendapatkan output:
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:
Ini memberi kami output:
Ini mungkin tampak sedikit membingungkan, karena metode yang sama yang kami gunakan sebelumnya bekerja Mari kita coba memahami ini.
Saat kamu melakukan:
Anda hanya menyalin daftar luar, bukan daftar dalam. Kita dapat menggunakan
id
fungsi sekali lagi untuk memeriksa ini.Outputnya adalah:
Ketika kita melakukannya
copy_2=list_2[:]
, ini terjadi: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
deepcopy
fungsinya.Mari kita periksa ini:
Kedua daftar luar memiliki ID yang berbeda, mari kita coba ini pada daftar bersarang dalam.
Outputnya adalah:
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: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:
Ini menghasilkan:
Seperti yang Anda lihat, itu tidak mengubah daftar bersarang asli, itu hanya mengubah daftar yang disalin.
sumber
Ungkapan Python untuk melakukan ini adalah
newList = oldList[:]
sumber
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, = list
dannew_list = [*list]
):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 hanya
copy.copy()
bekerja untuk objek Python yang lebih umum.Berikut adalah kode pengujian untuk pihak yang berkepentingan ( Templat dari sini ):
sumber
b=[*a]
- satu-satunya cara yang jelas untuk melakukannya;).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 kelist
objek 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 cepatdeepcopy
.Sementara
new_list = old_list[:]
,copy.copy(old_list)'
dan untuk Py3kold_list.copy()
bekerja untuk daftar level tunggal, mereka kembali menunjuk kelist
objek yang bersarang di dalamold_list
dannew_list
, dan mengubah ke salah satulist
objek diabadikan di yang lain.Sunting: Informasi baru terungkap
Seperti yang telah dinyatakan orang lain, ada masalah kinerja yang signifikan menggunakan
copy
modul dancopy.deepcopy
untuk daftar multidimensi .sumber
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 penggunaaneval()
Anda benar - benar harus menyebutkan bahwa itu bisa berbahaya.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 dariinput()
,sys.agrv
atau 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.new_list = eval(repr(old_list))
, jadi selain itu adalah ide yang buruk, mungkin juga terlalu lambat untuk bekerja.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.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.
sumber
old_list
dannew_list
dua 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).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):
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:
. Bonus : Jika Anda tidak ingin menyalin elemen gunakan (alias salinan dangkal):
Mari kita pahami perbedaan antara Solusi # 1 dan Solusi # 2
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.
sumber
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()
ataucopy.deepcopy()
bukan alternatif, misalnya dalam Python 3:Output:
sumber
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 menetapkannew_list = my_list
Anda 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)
sumber
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)
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:
Ini menggunakan yang baru untuk daftar:
Dan deepcopy_list terlihat seperti ini:
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.
sumber
Perspektif praktis sedikit untuk melihat ke memori melalui id dan gc.
sumber
Ingat itu dalam Python ketika Anda melakukannya:
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.sumber
Opsi deepcopy adalah satu-satunya metode yang bekerja untuk saya:
mengarah ke hasil:
sumber
Ini karena, baris
new_list = my_list
memberikan referensi baru ke variabelmy_list
yangnew_list
sama denganC
kode yang diberikan di bawah ini,Anda harus menggunakan modul salin untuk membuat daftar baru
sumber