Daftar daftar berubah secara tidak terduga tercermin di seluruh sublist

Oct 27 2008

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

614 CAdaker Oct 27 2008 at 22:03

Pada [x]*3dasarnya, ketika Anda menulis, Anda mendapatkan daftarnya [x, x, x]. Artinya, daftar dengan 3 referensi yang sama x. Saat Anda kemudian memodifikasi single ini, single xini 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]*4setiap 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] * 4teks 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] * 4setiap kali untuk alasan yang sama [x**2 for x in range(3)]mengevaluasi ulang x**2setiap waktu. Setiap evaluasi [1] * 4menghasilkan daftar baru, sehingga pemahaman daftar melakukan apa yang Anda inginkan.

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

140 nadrimajstor Aug 27 2013 at 06:17
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Visualisasi Python Tutor Langsung

56 PierreBdR Oct 27 2008 at 22:07

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

38 BlairConrad Oct 27 2008 at 22:02
[[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.

10 Kasravnd Jun 18 2015 at 00:08

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.onesdan np.zerosdan / 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])
7 ZbyněkWinkler Apr 06 2016 at 20:40

Kontainer Python berisi referensi ke objek lain. Lihat contoh ini:

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

Dalam blist ini terdapat salah satu item yang menjadi referensi list a. Daftar aini 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 csekarang berisi dua referensi ke daftar ayang setara dengan c = b * 2.

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

7 jerrymouse Apr 06 2017 at 12:36

myList = [[1]*4] * 3membuat 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 objpun akan tercermin di tiga tempat, di mana pun objdirujuk 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 1tidak dapat diubah, obj =[1]*4masih akan membuat daftar 1berulang 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 objakan 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
5 bagrat Jun 10 2015 at 21:38

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 xyang mana 1, dan daftar elemen tunggal 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 bersihnya sangat mirip. Anda pada dasarnya melakukannya 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.

5 NeerajKomuravalli Jun 14 2016 at 13:36

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

4 awulll Apr 24 2016 at 20:31

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]]
4 ouxiaogu Aug 09 2019 at 15:37

@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 *3juga membuat referensi, tetapi referensinya tidak dapat diubah, seperti [&0, &0, &0], kemudian kapan harus berubah li[0], Anda tidak dapat mengubah referensi yang mendasari const int 0, jadi Anda dapat mengubah alamat referensi menjadi yang baru &1;
  • while ma=[&li, &li, &li]dan libisa berubah, jadi ketika Anda memanggil ma[0][0]=1, ma [0] [0] sama dengan &li[0], jadi semua &liinstance akan mengubah alamat pertamanya menjadi &1.
3 AdilAbbasi Aug 10 2016 at 14:09

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] * 2sebenarnya 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]]
2 AnandTripathi Jul 15 2016 at 20:48

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
2 DeepakPatankar Jun 21 2020 at 18:34

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] = 5membuat

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

Brian Oct 23 2020 at 02:57

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 shapeargumen numpy ), dan 1.0merupakan elemen yang Anda inginkan untuk menginisialisasi struktur (bekerja dengan None juga). Perhatikan bahwa initargumen 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

mishsx Nov 23 2020 at 02:45

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 [[]] * 3adalah 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.