Daftar perubahan daftar tercermin di seluruh daftar tak terduga


645

Saya perlu membuat daftar daftar dengan Python, jadi saya mengetik berikut ini:

myList = [[1] * 4] * 3

Daftarnya terlihat seperti ini:

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

Lalu saya mengubah salah satu nilai terdalam:

myList[0][0] = 5

Sekarang daftar saya terlihat seperti ini:

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

yang bukan apa yang saya inginkan atau harapkan. Bisakah seseorang tolong jelaskan apa yang terjadi, dan bagaimana cara mengatasinya?

Jawaban:


560

Ketika Anda menulis, [x]*3Anda mendapatkan, pada dasarnya, daftar [x, x, x]. Yaitu, daftar dengan 3 referensi yang sama x. Ketika Anda memodifikasi single ini, xitu terlihat melalui ketiga referensi untuk itu:

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

Untuk memperbaikinya, Anda perlu memastikan bahwa Anda membuat daftar baru di setiap posisi. Salah satu cara untuk melakukannya adalah

[[1]*4 for _ in range(3)]

yang akan mengevaluasi ulang [1]*4setiap kali alih-alih mengevaluasi satu kali dan membuat 3 referensi ke 1 daftar.


Anda mungkin bertanya-tanya mengapa *tidak dapat membuat objek independen seperti pemahaman daftar. Itu karena operator perkalian *beroperasi pada objek, tanpa melihat ekspresi. Saat Anda menggunakan *untuk mengalikan [[1] * 4]dengan 3, *hanya melihat daftar 1-elemen yang [[1] * 4]dievaluasi, bukan [[1] * 4teks ekspresi. *tidak tahu bagaimana membuat salinan elemen itu, tidak tahu bagaimana cara mengevaluasi kembali [[1] * 4], dan tidak tahu Anda bahkan ingin salinan, dan secara umum, bahkan mungkin tidak ada cara untuk menyalin elemen.

Satu-satunya pilihan *adalah membuat referensi baru ke sublist yang ada alih-alih mencoba membuat sublists baru. Hal lain akan menjadi tidak konsisten atau memerlukan pendesainan ulang besar terhadap keputusan desain bahasa mendasar.

Sebaliknya, pemahaman daftar mengevaluasi kembali ekspresi elemen pada setiap iterasi. [[1] * 4 for n in range(3)]Mengevaluasi kembali [1] * 4setiap waktu karena alasan yang sama [x**2 for x in range(3)]mengevaluasi kembali x**2setiap waktu. Setiap evaluasi [1] * 4menghasilkan daftar baru, sehingga pemahaman daftar melakukan apa yang Anda inginkan.

Kebetulan, [1] * 4juga tidak menyalin elemen [1], tapi itu tidak masalah, karena bilangan bulat tidak dapat diubah. Anda tidak dapat melakukan sesuatu seperti 1.value = 2dan mengubah angka 1 menjadi angka 2.


24
Saya terkejut bahwa tidak ada yang menunjukkan bahwa, jawaban di sini menyesatkan. [x]*3menyimpan 3 referensi suka [x, x, x]hanya benar bila xbisa berubah. Ini tidak berfungsi untuk misalnya a=[4]*3, di mana setelah a[0]=5,a=[5,4,4].
Allanqunzi

42
Secara teknis, itu masih benar. [4]*3pada dasarnya setara dengan x = 4; [x, x, x]. Memang benar, bahwa ini tidak akan pernah menyebabkan masalah karena 4tidak dapat diubah. Juga, contoh Anda yang lain sebenarnya bukan kasus yang berbeda. a = [x]*3; a[0] = 5tidak akan menyebabkan masalah bahkan jika xbisa diubah, karena Anda tidak mengubah x, hanya memodifikasi a. Saya tidak akan menggambarkan jawaban saya sebagai menyesatkan atau salah - Anda tidak bisa menembak diri sendiri jika Anda berurusan dengan benda yang tidak dapat diubah.
Pembuat Iklan

19
@ Allanqunzi Anda salah. Lakukan x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python tidak membedakan antara objek yang dapat berubah dan tidak berubah di sini.
timgeb

129
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Bingkai dan Objek

Visualisasikan Python Tutor Langsung


Jadi, mengapa jika kita menulis matrix = [[x] * 2] tidak membuat 2 elemnts untuk objek yang sama seperti contoh yang Anda jelaskan, tampaknya konsep yang sama, apa yang saya lewatkan?
Ahmed Mohamed

@AhmedMohamed Memang ia membuat daftar dengan dua elemen dari objek yang sama persis yang xmerujuk. Jika Anda membuat objek global yang unik dengan x = object()dan kemudian membuat matrix = [[x] * 2]ini menjadi kenyataan:matrix[0][0] is matrix[0][1]
nadrimajstor

@nadrimajstor jadi mengapa perubahan dalam matriks [0] tidak mempengaruhi matriks [1] seperti contoh di atas dengan matriks 2d.
Ahmed Mohamed

@AhmedMohamed Surprise datang ketika Anda membuat "salinan" dari urutan yang bisa berubah (dalam contoh kita ini adalah list) jadi jika a row = [x] * 2daripada di matrix = [row] * 2mana kedua baris adalah objek yang persis sama, dan sekarang perubahan ke satu baris matrix[0][0] = ytiba-tiba mencerminkan di yang lain(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Lihatlah Ned Batchelder - Fakta dan Mitos tentang nama dan nilai Python karena mungkin menawarkan penjelasan yang lebih baik. :)
nadrimajstor

52

Sebenarnya, inilah yang Anda harapkan. Mari kita uraikan apa yang terjadi di sini:

Anda menulis

lst = [[1] * 4] * 3

Ini setara dengan:

lst1 = [1]*4
lst = [lst1]*3

Ini berarti lstdaftar dengan 3 elemen yang semuanya menunjuk ke lst1. Ini berarti dua baris berikut ini setara:

lst[0][0] = 5
lst1[0] = 5

Seperti lst[0]tidak lain adalah lst1.

Untuk mendapatkan perilaku yang diinginkan, Anda dapat menggunakan pemahaman daftar:

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

Dalam hal ini, ekspresi dievaluasi ulang untuk setiap n, yang mengarah ke daftar yang berbeda.


Hanya tambahan kecil untuk jawaban yang bagus di sini: terbukti bahwa Anda berurusan dengan objek yang sama jika Anda melakukannya id(lst[0][0])dan id(lst[1][0])atau bahkan id(lst[0])danid(lst[1])
Sergiy Kolodyazhnyy

36
[[1] * 4] * 3

atau bahkan:

[[1, 1, 1, 1]] * 3

Membuat daftar yang mereferensikan internal [1,1,1,1]3 kali - bukan tiga salinan daftar batin, jadi setiap kali Anda memodifikasi daftar (di posisi apa pun), Anda akan melihat perubahannya tiga kali.

Sama dengan contoh ini:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

di mana itu mungkin sedikit kurang mengejutkan.


3
Anda dapat menggunakan operator "is" untuk menemukan ini. ls [0] is ls [1] mengembalikan True.
mipadi

9

Di samping jawaban yang diterima yang menjelaskan masalah dengan benar, dalam pemahaman daftar Anda, jika Anda menggunakan penggunaan python-2.x xrange()yang mengembalikan generator yang lebih efisien ( range()dengan python 3 melakukan pekerjaan yang sama) _daripada variabel yang dibuang n:

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

Juga, sebagai cara yang jauh lebih Pythonic yang dapat Anda gunakan itertools.repeat()untuk membuat objek iterator elemen berulang:

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

PS Menggunakan numpy, jika Anda hanya ingin membuat array yang atau nol Anda dapat menggunakan np.onesdan np.zerosdan / atau untuk penggunaan nomor lainnya np.repeat():

In [1]: import numpy as np

In [2]: 

In [2]: np.ones(4)
Out[2]: array([ 1.,  1.,  1.,  1.])

In [3]: np.ones((4, 2))
Out[3]: 
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])

In [4]: np.zeros((4, 2))
Out[4]: 
array([[ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.]])

In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])

6

Wadah python berisi referensi ke objek lain. Lihat contoh ini:

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

Dalam hal ini badalah daftar yang berisi satu item yang merupakan referensi ke daftar a. Daftar aini bisa berubah.

Penggandaan daftar oleh integer setara dengan menambahkan daftar ke dirinya sendiri beberapa kali (lihat operasi urutan umum ). Jadi, lanjutkan dengan contoh:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

Kita dapat melihat bahwa daftar csekarang berisi dua referensi ke daftar ayang setara denganc = b * 2 .

FAQ Python juga berisi penjelasan tentang perilaku ini: Bagaimana cara membuat daftar multidimensi?


6

myList = [[1]*4] * 3membuat satu objek daftar [1,1,1,1]dalam memori dan menyalin rujukannya 3 kali lipat. Ini setara dengan obj = [1,1,1,1]; myList = [obj]*3. Setiap modifikasi objakan tercermin di tiga tempat, di mana pun objdireferensikan dalam daftar. Pernyataan yang benar adalah:

myList = [[1]*4 for _ in range(3)]

atau

myList = [[1 for __ in range(4)] for _ in range(3)]

Hal penting yang perlu diperhatikan di sini adalah bahwa *operator sebagian besar digunakan untuk membuat daftar literal . Meskipun 1tidak berubah, obj =[1]*4masih akan membuat daftar 1berulang 4 kali lipat untuk membentuk [1,1,1,1]. Tetapi jika ada referensi ke objek yang tidak dapat diubah dibuat, objek tersebut akan ditimpa dengan yang baru.

Ini berarti jika kita melakukannya obj[1]=42, maka objakan menjadi [1,42,1,1] tidak [42,42,42,42] seperti anggapan beberapa orang. Ini juga dapat diverifikasi:

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440

2
Ini bukan tentang literal. obj[2] = 42 menggantikan referensi pada indeks 2, sebagai lawan untuk bermutasi objek yang dirujuk oleh indeks itu, yang adalah apa myList[2][0] = ...( myList[2]adalah daftar, dan tugas mengubah referensi pada indeks 0 dalam daftar tha). Tentu saja, bilangan bulat tidak bisa berubah, tapi banyak jenis objek yang . Dan perhatikan bahwa [....]notasi tampilan daftar juga merupakan bentuk sintaks literal! Jangan bingung antara senyawa (seperti daftar) dan objek skalar (seperti bilangan bulat), dengan objek yang dapat berubah vs yang tidak dapat diubah.
Martijn Pieters

5

Dengan kata-kata sederhana ini terjadi karena dalam python semuanya bekerja dengan referensi , jadi ketika Anda membuat daftar daftar seperti itu pada dasarnya Anda berakhir dengan masalah seperti itu.

Untuk mengatasi masalah Anda, Anda dapat melakukan salah satu di antaranya: 1. Gunakan dokumentasi array numpy untuk numpy.empty 2. Tambahkan daftar saat Anda membuka daftar. 3. Anda juga dapat menggunakan kamus jika Anda mau


2

Biarkan kami menulis ulang kode Anda dengan cara berikut:

x = 1
y = [x]
z = y * 4

myList = [z] * 3

Kemudian setelah ini, jalankan kode berikut untuk membuat semuanya lebih jelas. Apa yang dilakukan kode pada dasarnya adalah mencetak idobjek yang diperoleh, yang

Kembalikan "identitas" suatu objek

dan akan membantu kami mengidentifikasi mereka dan menganalisis apa yang terjadi:

print("myList:")
for i, subList in enumerate(myList):
    print("\t[{}]: {}".format(i, id(subList)))
    for j, elem in enumerate(subList):
        print("\t\t[{}]: {}".format(j, id(elem)))

Dan Anda akan mendapatkan output berikut:

x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
    [0]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [1]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [2]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528

Jadi sekarang mari kita selangkah demi selangkah. Anda memiliki xyang mana 1, dan satu daftar elemen yang yberisi x. Langkah pertama Anda adalah y * 4yang akan memberi Anda daftar baru z, yang pada dasarnya [x, x, x, x], yaitu membuat daftar baru yang akan memiliki 4 elemen, yang merupakan referensi ke xobjek awal . Langkah bersih sangat mirip. Pada dasarnya Anda lakukan z * 3, yaitu [[x, x, x, x]] * 3dan kembali [[x, x, x, x], [x, x, x, x], [x, x, x, x]], untuk alasan yang sama seperti untuk langkah pertama.


2

Saya kira semua orang menjelaskan apa yang terjadi. Saya menyarankan satu cara untuk menyelesaikannya:

myList = [[1 for i in range(4)] for j in range(3)]

myList[0][0] = 5

print myList

Dan kemudian Anda memiliki:

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

2

Mencoba menjelaskannya dengan lebih deskriptif,

Operasi 1:

x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]

x[0][0] = 1
print(x) # [[1, 0], [0, 0]]

Operasi 2:

y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [1, 0]]

Melihat mengapa tidak memodifikasi elemen pertama dari daftar pertama tidak mengubah elemen kedua dari setiap daftar? Itu karena [0] * 2benar-benar daftar dua angka, dan referensi ke 0 tidak dapat dimodifikasi.

Jika Anda ingin membuat salinan klon, coba Operasi 3:

import copy
y = [0] * 2   
print(y)   # [0, 0]

y = [y, copy.deepcopy(y)]  
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [0, 0]]

cara lain yang menarik untuk membuat salinan klon, Operasi 4:

import copy
y = [0] * 2
print(y) # [0, 0]

y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]

y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]

2

@spelchekr dari multiplikasi daftar Python: [[...]] * 3 membuat 3 daftar yang saling mirror ketika dimodifikasi dan saya memiliki pertanyaan yang sama tentang "Mengapa hanya bagian luar * 3 yang membuat lebih banyak referensi sedangkan yang dalam tidak ? Kenapa tidak semuanya? "

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

Berikut ini penjelasan saya setelah mencoba kode di atas:

  • Bagian dalam *3juga membuat referensi, tetapi referensi itu tidak berubah, seperti [&0, &0, &0], lalu kapan harus berubah li[0], Anda tidak dapat mengubah referensi mendasar dari const int 0, jadi Anda bisa mengubah alamat referensi menjadi yang baru &1;
  • sementara ma=[&li, &li, &li]dan libisa berubah, jadi ketika Anda menelepon ma[0][0]=1, ma [0] [0] sama dengan &li[0], jadi semua &liinstance akan mengubah alamat 1 menjadi &1.

1

Dengan menggunakan fungsi daftar inbuilt yang dapat Anda lakukan seperti ini

a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list

a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number

a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting

a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list

1
Catatan, langkah keempat bisa dijatuhkan jika Anda membuat langkah kedua:a.insert(0,[5,1,1,1])
U10-Forward
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.