Daftar daftar berubah secara tidak terduga tercermin di seluruh sublist
Saya perlu membuat daftar daftar dengan Python, jadi saya mengetik yang berikut ini:
myList = [[1] * 4] * 3
Daftarnya terlihat seperti ini:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Kemudian 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 yang saya inginkan atau harapkan. Bisakah seseorang menjelaskan apa yang terjadi, dan bagaimana menyiasatinya?
Jawaban
Pada [x]*3
dasarnya, ketika Anda menulis, Anda mendapatkan daftarnya [x, x, x]
. Artinya, daftar dengan 3 referensi yang sama x
. Saat Anda kemudian memodifikasi single ini, single x
ini akan 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]*4
setiap kali alih-alih mengevaluasi sekali dan membuat 3 referensi untuk 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 [[1] * 4]
mengevaluasi ke, bukan [[1] * 4
teks ekspresi. *
tidak tahu bagaimana membuat salinan elemen itu, tidak tahu bagaimana mengevaluasi kembali [[1] * 4]
, dan tidak tahu Anda bahkan menginginkan salinan, dan secara umum, mungkin tidak ada cara untuk menyalin elemen.
Satu-satunya pilihan *
adalah membuat referensi baru ke sublist yang ada daripada mencoba membuat sublist baru. Hal lainnya akan menjadi tidak konsisten atau memerlukan desain ulang besar-besaran dari keputusan desain bahasa yang mendasar.
Sebaliknya, pemahaman daftar mengevaluasi ulang ekspresi elemen pada setiap iterasi. [[1] * 4 for n in range(3)]
mengevaluasi ulang [1] * 4
setiap kali untuk alasan yang sama [x**2 for x in range(3)]
mengevaluasi ulang x**2
setiap waktu. Setiap evaluasi [1] * 4
menghasilkan daftar baru, sehingga pemahaman daftar melakukan apa yang Anda inginkan.
Kebetulan, [1] * 4
juga tidak menyalin elemen [1]
, tetapi itu tidak masalah, karena bilangan bulat tidak dapat diubah. Anda tidak dapat melakukan sesuatu seperti 1.value = 2
dan mengubah 1 menjadi 2.
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
Visualisasi Python Tutor Langsung
Sebenarnya, inilah yang Anda harapkan. Mari kita uraikan apa yang terjadi di sini:
Anda menulis
lst = [[1] * 4] * 3
Ini sama dengan:
lst1 = [1]*4
lst = [lst1]*3
Artinya lst
adalah daftar dengan 3 elemen yang semuanya menunjuk 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 kasus ini, ekspresi dievaluasi ulang untuk setiap n, yang mengarah ke daftar yang berbeda.
[[1] * 4] * 3
atau bahkan:
[[1, 1, 1, 1]] * 3
Membuat daftar yang mereferensikan internal [1,1,1,1]
3 kali - bukan tiga salinan dari daftar dalam, jadi setiap kali Anda mengubah daftar (dalam posisi apa pun), Anda akan melihat perubahan tiga kali.
Ini 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]]
yang mungkin sedikit kurang mengejutkan.
Di samping jawaban yang diterima yang menjelaskan masalah dengan benar, dalam pemahaman daftar Anda, jika Anda menggunakan python-2.x, gunakan xrange()
yang mengembalikan generator yang lebih efisien ( range()
di python 3 melakukan pekerjaan yang sama) _
daripada variabel sekali pakai n
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
Juga, sebagai cara yang lebih Pythonic yang dapat Anda gunakan itertools.repeat()untuk membuat objek iterator dari 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 satu atau nol Anda dapat menggunakan np.ones
dan np.zeros
dan / atau untuk penggunaan nomor lain 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])
Kontainer Python berisi referensi ke objek lain. Lihat contoh ini:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
Dalam b
list ini terdapat salah satu item yang menjadi referensi list a
. Daftar a
ini bisa berubah.
Perkalian daftar dengan bilangan bulat sama dengan menambahkan daftar itu 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 tersebut c
sekarang berisi dua referensi ke daftar a
yang setara dengan c = b * 2
.
FAQ Python juga berisi penjelasan tentang perilaku ini: Bagaimana cara membuat daftar multidimensi?
myList = [[1]*4] * 3
membuat satu objek daftar [1,1,1,1]
dalam memori dan menyalin referensinya sebanyak 3 kali. Ini sama dengan obj = [1,1,1,1]; myList = [obj]*3
. Modifikasi apa obj
pun akan tercermin di tiga tempat, di mana pun obj
dirujuk dalam daftar. Pernyataan yang tepat 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 *
operator kebanyakan digunakan untuk membuat daftar literal . Meskipun 1
tidak dapat diubah, obj =[1]*4
masih akan membuat daftar 1
berulang 4 kali ke formulir [1,1,1,1]
. Tetapi jika ada referensi ke objek yang tidak berubah dibuat, objek tersebut ditimpa dengan yang baru.
Ini berarti jika kita melakukan obj[1]=42
, maka obj
akan menjadi [1,42,1,1]
tidak
seperti beberapa mungkin menganggap. Ini juga dapat diverifikasi:
[42,42,42,42]
>>> 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
Izinkan 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 ids dari objek yang diperoleh, yaitu
Kembalikan "identitas" dari 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 lanjutkan langkah demi langkah. Anda memiliki x
yang mana 1
, dan daftar elemen tunggal yang y
berisi x
. Langkah pertama Anda adalah y * 4
yang 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 x
objek awal . Langkah bersihnya sangat mirip. Anda pada dasarnya melakukannya z * 3
, yaitu [[x, x, x, x]] * 3
dan kembali [[x, x, x, x], [x, x, x, x], [x, x, x, x]]
, untuk alasan yang sama seperti untuk langkah pertama.
Dengan kata sederhana ini terjadi karena di 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 satunya: 1. Gunakan dokumentasi numpy array untuk numpy.empty 2. Tambahkan daftar saat Anda masuk ke daftar. 3. Anda juga bisa menggunakan kamus jika mau
Saya kira semua orang menjelaskan apa yang terjadi. Saya menyarankan satu cara untuk mengatasinya:
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]]
@spelchekr dari perkalian daftar Python: [[...]] * 3 membuat 3 daftar yang mencerminkan satu sama lain 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 1s? "
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 penjelasan saya setelah mencoba kode di atas:
- Bagian dalam
*3
juga membuat referensi, tetapi referensinya tidak dapat diubah, seperti[&0, &0, &0]
, kemudian kapan harus berubahli[0]
, Anda tidak dapat mengubah referensi yang mendasari const int0
, jadi Anda dapat mengubah alamat referensi menjadi yang baru&1
; - while
ma=[&li, &li, &li]
danli
bisa berubah, jadi ketika Anda memanggilma[0][0]=1
, ma [0] [0] sama dengan&li[0]
, jadi semua&li
instance akan mengubah alamat pertamanya menjadi&1
.
Mencoba menjelaskannya secara 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]]
Memperhatikan mengapa tidak mengubah elemen pertama dari daftar pertama tidak mengubah elemen kedua dari setiap daftar? Itu karena [0] * 2
sebenarnya adalah daftar dua angka, dan referensi ke 0 tidak dapat diubah.
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]]
Dengan menggunakan fungsi daftar bawaan Anda dapat melakukan 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
Pertanyaan-pertanyaan ini memiliki banyak jawaban, saya menambahkan jawaban saya untuk menjelaskan diagram yang sama.
Cara Anda membuat 2D, membuat daftar yang dangkal
arr = [[0]*cols]*row
Sebaliknya, jika Anda ingin memperbarui elemen daftar, Anda harus menggunakan
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
Penjelasan :
Seseorang dapat membuat daftar menggunakan:
arr = [0]*N
atau
arr = [0 for i in range(N)]
Dalam kasus pertama, semua indeks dari array mengarah ke objek integer yang sama
dan ketika Anda menetapkan nilai ke indeks tertentu, objek int baru dibuat, misalnya arr[4] = 5
membuat
Sekarang mari kita lihat apa yang terjadi ketika kita membuat daftar, dalam hal ini, semua elemen dari daftar teratas kita akan mengarah ke daftar yang sama.
Dan jika Anda memperbarui nilai indeks apa pun, objek int baru akan dibuat. Tetapi karena semua indeks daftar tingkat atas menunjuk ke daftar yang sama, semua baris akan terlihat sama. Dan Anda akan merasa bahwa memperbarui elemen berarti memperbarui semua elemen di kolom itu.
Penghargaan: Terima kasih kepada Pranav Devarakonda untuk penjelasan mudahnya di sini
Saya tiba di sini karena saya ingin melihat bagaimana saya bisa mengumpulkan daftar yang jumlahnya tidak tentu. Ada banyak penjelasan dan contoh spesifik di atas, tetapi Anda dapat menggeneralisasi daftar dimensi N dari daftar ... dengan fungsi rekursif berikut:
import copy
def list_ndim(dim, el=None, init=None):
if init is None:
init = el
if len(dim)> 1:
return list_ndim(dim[0:-1], None, [copy.copy(init) for x in range(dim[-1])])
return [copy.deepcopy(init) for x in range(dim[0])]
Anda melakukan panggilan pertama Anda ke fungsi seperti ini:
dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)
where (3,5,2)
adalah tupel dari dimensi struktur (mirip dengan shape
argumen numpy ), dan 1.0
merupakan elemen yang Anda inginkan untuk menginisialisasi struktur (bekerja dengan None juga). Perhatikan bahwa init
argumen hanya disediakan oleh panggilan rekursif untuk meneruskan daftar turunan bertingkat
keluaran di atas:
[[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]]
atur elemen tertentu:
l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'
keluaran yang dihasilkan:
[[[1.0, 1.0], ['abc', 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 56.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [(36+0j), 1.0], [1.0, 1.0], [1.0, 1.0]]]
sifat daftar yang tidak diketik ditunjukkan di atas
Perhatikan bahwa item dalam urutan tidak disalin; mereka direferensikan beberapa kali . Ini sering menghantui programmer Python baru; mempertimbangkan:
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
Yang terjadi adalah [[]]
daftar satu elemen berisi daftar kosong, jadi ketiga elemen tersebut [[]] * 3
adalah referensi ke daftar kosong tunggal ini. Memodifikasi salah satu elemen daftar mengubah daftar tunggal ini.
Contoh lain untuk menjelaskan hal ini adalah menggunakan array multi-dimensi .
Anda mungkin mencoba membuat array multidimensi seperti ini:
>>> A = [[**None**] * 2] * 3
Ini terlihat benar jika Anda mencetaknya:
>>> A
[[None, None], [None, None], [None, None]]
Namun saat Anda menetapkan nilai, nilai tersebut muncul di banyak tempat:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
Alasannya adalah mereplikasi daftar dengan *
tidak membuat salinan, itu hanya membuat referensi ke objek yang sudah ada. Angka 3 membuat daftar yang berisi 3 referensi ke daftar yang sama dengan panjang dua. Perubahan pada satu baris akan ditampilkan di semua baris, yang hampir pasti bukan yang Anda inginkan.