Liste listelerinin, beklenmedik bir şekilde alt listelere yansıtılması


645

Python listeleri listesi oluşturmak gerekiyordu, bu yüzden aşağıdakileri yazdım:

myList = [[1] * 4] * 3

Liste şöyle görünüyordu:

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

Sonra en içteki değerlerden birini değiştirdim:

myList[0][0] = 5

Şimdi listem şuna benziyor:

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

istediğim veya beklediğim bu değil. Birisi neler olup bittiğini ve etrafta nasıl dolaşılacağını açıklayabilir mi?

Yanıtlar:


560

Yazdığınızda [x]*3, esasen liste alırsınız [x, x, x]. Yani, aynı 3 referansa sahip bir liste x. Daha sonra bu single'ı değiştirdiğinizde, bu xüç referans üzerinden de görülebilir:

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

Düzeltmek için, her konumda yeni bir liste oluşturduğunuzdan emin olmanız gerekir. Bunu yapmanın bir yolu

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

bir [1]*4kez değerlendirmek ve 1 listeye 3 referans yapmak yerine her seferinde yeniden değerlendirir.


Neden *bağımsız nesneleri liste kavramasının yaptığı gibi yapamayacağınızı merak edebilirsiniz. Çünkü çarpma operatörü *, ifadeleri görmeden nesneler üzerinde çalışır. 3 *ile çarpmak için kullandığınızda , ifade metnini değil [[1] * 4], *yalnızca 1 öğeli listenin [[1] * 4]değerlendirildiğini görür [[1] * 4. *o öğenin kopyalarını nasıl oluşturacağınız hakkında hiçbir fikre sahip değildir, nasıl yeniden değerlendireceğiniz hakkında [[1] * 4]hiçbir fikre sahip değildir ve kopyalarını bile istediğiniz hakkında hiçbir fikre sahip değildir ve genel olarak öğeyi kopyalamanın bir yolu bile olmayabilir.

Tek seçenek *, yeni alt listeler yapmaya çalışmak yerine mevcut alt listeye yeni referanslar yapmaktır. Başka her şey tutarsız olabilir veya temel dil tasarımı kararlarının büyük ölçüde yeniden tasarlanmasını gerektirir.

Buna karşılık, bir liste kavrama her yinelemede öğe ifadesini yeniden değerlendirir. [[1] * 4 for n in range(3)]reevaluates [1] * 4aynı nedenle her zaman [x**2 for x in range(3)]reevaluates x**2her zaman. Her değerlendirme [1] * 4yeni bir liste oluşturur, bu nedenle liste kavraması istediğinizi yapar.

Bu arada, tamsayı değişmez olduğu [1] * 4için, öğelerini de kopyalamaz [1], ancak bu önemli değildir. Gibi bir şey yapamazsınız 1.value = 2ve 1'i 2'ye çeviremezsiniz.


24
Hiçbir vücudun buna işaret etmediğine şaşırdım, buradaki cevap yanıltıcı. [x]*3gibi 3 referans depolamak [x, x, x]sadece xdeğiştirilebilir olduğunda doğrudur . Bu, örneğin a=[4]*3, sonra nerede işe yaramaz a[0]=5,a=[5,4,4].
Allanqunzi

42
Teknik olarak, hala doğru. [4]*3aslında eşittir x = 4; [x, x, x]. Bu herhangi sebep asla olsa da, doğru sorunu beri 4sabittir. Ayrıca, diğer örneğiniz gerçekten farklı bir durum değildir. Değiştirilebilir a = [x]*3; a[0] = 5olsanız bile sorunlara neden olmaz x, çünkü değiştirmediğinizden x, yalnızca değiştirdiğinizden a. Sadece - Ben yanıltıcı veya yanlış olarak benim yanıtı açıklayan olmaz olamaz sen sabit nesneler ile uğraşıyoruz kendinize tekme.
CAdaker

19
@Allanqunzi yanılıyorsunuz. Yap x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python, burada değiştirilebilir ve değişmez nesneler arasında ayrım yapmaz.
timgeb

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

Çerçeveler ve Nesneler

Canlı Python Eğitmeni Görselleştirme


Öyleyse, matrix = [[x] * 2] yazdığımızda neden tarif ettiğiniz örnekle aynı nesne için 2 eleme yapmazsak, aynı kavram gibi görünüyor, ne eksik?
Ahmed Mohamed

@AhmedMohamed Gerçekten de aynı nesnenin iki unsuru ile bir liste yapar x. Eğer global olarak eşsiz bir nesne x = object()yaparsanız ve sonra matrix = [[x] * 2]bunları gerçekleştirirseniz:matrix[0][0] is matrix[0][1]
nadrimajstor

@nadrimajstor bu yüzden matris [0] 'daki değişikliğin neden yukarıdaki 2d matrisli örnek gibi matrisi [1] etkilemediğini.
Ahmed Mohamed

Eğer (örneğimizde bu olduğunu değişken dizisinin bir "kopya" yaparken @AhmedMohamed Sürpriz gel list) eğer öyleyse bir row = [x] * 2bir daha matrix = [row] * 2iki satır tam olarak aynı nesne, ve şimdi tek bir satıra değişiklikleri nereye matrix[0][0] = yaniden diğerinde yansıtmak(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Python adları ve değerleri hakkında Ned Batchelder - Gerçekler ve Mitler'e bir göz atın , çünkü daha iyi bir açıklama sunabilir. :)
nadrimajstor

52

Aslında, tam olarak beklediğiniz budur. Burada olanları ayrıştıralım:

Sen yaz

lst = [[1] * 4] * 3

Bu şuna eşittir:

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

Bu lst, 3 öğeyi gösteren bir listedir lst1. Bu, aşağıdaki iki satırın eşdeğer olduğu anlamına gelir:

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

Gibi lst[0]başka bir şey değil lst1.

İstenen davranışı elde etmek için liste kavrama özelliğini kullanabilirsiniz:

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

Bu durumda, ifade her n için yeniden değerlendirilerek farklı bir listeye götürülür.


Burada güzel cevap sadece küçük bir ek: Yapman aynı nesneyle ediyoruz uğraşan eğer belirgin olduğunu id(lst[0][0])ve id(lst[1][0])hatta ya id(lst[0])veid(lst[1])
Sergiy Kolodyazhnyy

36
[[1] * 4] * 3

ya da:

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

Dahili listenin [1,1,1,1]üç kopyasına değil , dahili 3 kez referans veren bir liste oluşturur , böylece listeyi her değiştirdiğinizde (herhangi bir konumda), değişikliği üç kez görürsünüz.

Bu örnekle aynı:

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

muhtemelen biraz daha az şaşırtıcı.


3
Bunu bulmak için "is" operatörünü kullanabilirsiniz. ls [0] ls [1] True değerini döndürür.
mipadi

9

Sorunu doğru bir şekilde açıklayan kabul edilen cevabın yanı sıra, liste kavrayışınızda, fırlatma değişkeni yerine xrange()daha verimli ( range()python 3'te aynı işi yapar) bir jeneratör döndüren python-2.x kullanıyorsanız :_n

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

Ayrıca, çok daha Pythonic bir yol olarak itertools.repeat(), tekrarlanan öğelerin bir yineleyici nesnesi oluşturmak için kullanabilirsiniz :

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

Yalnızca olanlar veya sıfır kullanabilirsiniz dizisi oluşturmak istiyorsanız PS, numpy kullanma np.onesve np.zerosve / veya diğer numara kullanım için 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

Python kapları diğer nesnelere referanslar içerir. Bu örneğe bakın:

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

Bu blistede listeye referans olan bir öğe içeren bir listedir a. Liste adeğiştirilebilir.

Bir listenin bir tamsayı ile çarpılması listenin kendisine birçok kez eklenmesine eşdeğerdir (bkz. Ortak dizi işlemleri ). Örnekle devam etmek:

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

Listenin cartık listeye aeşdeğer olan iki referans içerdiğini görebiliyoruz c = b * 2.

Python SSS ayrıca bu davranışın açıklamasını da içerir: Çok boyutlu bir listeyi nasıl oluştururum?


6

myList = [[1]*4] * 3[1,1,1,1]bellekte bir liste nesnesi oluşturur ve başvurusunu 3 kez kopyalar. Bu eşdeğerdir obj = [1,1,1,1]; myList = [obj]*3. objYapılacak herhangi bir değişiklik obj, listede referans verilen her yerde üç yere yansıtılacaktır . Doğru ifade şöyle olacaktır:

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

veya

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

Burada dikkat edilmesi gereken önemli nokta, *operatörün çoğunlukla değişmezler listesi oluşturmak için kullanılmasıdır . 1Değişmez olmasına rağmen , obj =[1]*4yine de 14 kez tekrarlanan bir liste oluşturacaktır [1,1,1,1]. Ancak değişmez bir nesneye herhangi bir atıf yapılırsa, nesnenin üzerine yenisiyle yazılır.

Yaptığımız bu araçlar obj[1]=42, daha sonra objolacak [1,42,1,1] değil [42,42,42,42] bazı varsayabiliriz olarak. Bu ayrıca doğrulanabilir:

>>> 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
Bu gerçeklerle ilgili değil. bu indeks tarafından başvurulan nesnenin mutasyona uğratılmasının aksine , indeksteki obj[2] = 42 referansın yerini alır ( bu bir listedir ve atama bu listedeki indeks 0'daki referansı değiştirir). Tabii ki, tamsayılar değiştirilemez, ancak birçok nesne türü vardır . Ve liste gösterim gösteriminin de gerçek bir sözdizimi biçimi olduğunu unutmayın ! Bileşiği (listeler gibi) ve skaler nesneleri (tamsayılar gibi) değişebilir ve değişmez nesnelerle karıştırmayın. 2myList[2][0] = ...myList[2][....]
Martijn Pieters

5

Basit bir deyişle, bu gerçekleşiyor çünkü python'da her şey referans olarak çalışıyor , bu nedenle bir liste listesi oluşturduğunuzda, temelde bu tür sorunlarla karşılaşıyorsunuz.

Sorununuzu çözmek için bunlardan birini yapabilirsiniz: 1. numpy.empty için numpy dizi belgelerini kullanın 2. Listeye geldiğinizde listeyi ekleyin. 3. İsterseniz sözlüğü de kullanabilirsiniz


2

Kodunuzu aşağıdaki şekilde yeniden yazalım:

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

myList = [z] * 3

Ardından, her şeyi daha net hale getirmek için aşağıdaki kodu çalıştırın. Kodun yaptığı temelde idelde edilen nesnelerin s

Bir nesnenin “kimliğini” döndürme

ve onları tanımlamamıza ve neler olduğunu analiz etmemize yardımcı olacak:

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

Ve aşağıdaki çıktıyı alacaksınız:

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

Şimdi adım adım gidelim. Sen var xolan 1ve tek bir eleman listesi yiçeren x. İlk adımınız y * 4size yeni bir liste sunacaktır z, bu da temelde [x, x, x, x], yani ilk xnesneye referans olan 4 öğeye sahip yeni bir liste oluşturur . Net adım oldukça benzer. Temelde yapmak z * 3olan [[x, x, x, x]] * 3döner ve [[x, x, x, x], [x, x, x, x], [x, x, x, x]]ilk adım için aynı nedenden dolayı.


2

Sanırım herkes neler olduğunu açıklıyor. Bunu çözmenin bir yolunu öneririm:

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

myList[0][0] = 5

print myList

Ve sonra:

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

2

Daha açıklayıcı bir şekilde açıklamaya çalışarak,

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

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

İlk listenin ilk öğesini değiştirmenin neden her listenin ikinci öğesini değiştirmediğini fark ettiniz mi? O yüzden[0] * 2 gerçekten iki sayının bir listesi ve 0 değerine yapılan bir başvuru değiştirilemez.

Klon kopyalar oluşturmak istiyorsanız, İşlem 3'ü deneyin:

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

klon kopyalar oluşturmanın bir başka ilginç yolu, Operasyon 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

Python listesi çarpımından @spelchekr : [[...]] * 3 değiştirildiklerinde birbirini aynalayan 3 liste yapar ve ben "Sadece dış * 3 neden iç referans olmasa daha fazla referans oluşturur? Neden hepsi 1s değil? "

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

Yukarıdaki kodu denedikten sonra benim açıklama:

  • İç *3 da referanslar yaratır, ancak referanslar değişmez, [&0, &0, &0]o zaman ne zaman değiştirileceği gibi li[0]const int'in altta yatan referansını değiştiremezsiniz 0, böylece referans adresini yenisiyle değiştirebilirsiniz &1;
  • while ma=[&li, &li, &li]ve lideğişkendir, bu nedenle, aradığınızda ma[0][0]=1, ma [0] [0] eşittir &li[0], böylece tüm &liörnekler 1. adresini değiştirir &1.

1

Dahili liste fonksiyonunu kullanarak bunu yapabilirsiniz

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
Dikkat edin, ikinci adımı atarsanız dördüncü adım atılabilir:a.insert(0,[5,1,1,1])
U10-Forward
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.