Alterações na lista de listas refletidas entre sublistas inesperadamente

645

Eu precisava criar uma lista de listas em Python, então digitei o seguinte:

myList = [[1] * 4] * 3

A lista era assim:

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

Então mudei um dos valores mais internos:

myList[0][0] = 5

Agora minha lista fica assim:

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

o que não é o que eu queria ou esperava. Alguém pode explicar o que está acontecendo e como contornar isso?

Charles Anderson
fonte

Respostas:

560

Quando você escreve [x]*3, obtém, essencialmente, a lista [x, x, x]. Ou seja, uma lista com 3 referências à mesma x. Quando você modifica este single, xele fica visível através das três referências a ele:

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

Para corrigi-lo, você precisa criar uma nova lista em cada posição. Uma maneira de fazer isso é

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

que reavaliará [1]*4cada vez em vez de avaliar uma vez e fazer 3 referências a 1 lista.


Você pode se perguntar por *que não consegue criar objetos independentes da mesma forma que a compreensão da lista. Isso ocorre porque o operador de multiplicação *opera em objetos, sem ver expressões. Quando você *multiplica [[1] * 4]por 3, *vê apenas a lista de 1 elemento [[1] * 4]avaliada, não o [[1] * 4texto da expressão. *não tem idéia de como fazer cópias desse elemento, não tem idéia de como reavaliar [[1] * 4]e não tem idéia de que você deseja cópias e, em geral, pode não haver uma maneira de copiar o elemento.

A única opção *é fazer novas referências à sublist existente, em vez de tentar criar novas sublistas. Qualquer outra coisa seria inconsistente ou exigiria uma grande reformulação das decisões fundamentais de design de linguagem.

Por outro lado, uma compreensão de lista reavalia a expressão do elemento em cada iteração. [[1] * 4 for n in range(3)]reavalia [1] * 4todas as vezes pelo mesmo motivo [x**2 for x in range(3)]reavalia x**2todas as vezes. Toda avaliação de [1] * 4gera uma nova lista, portanto a compreensão da lista faz o que você queria.

Aliás, [1] * 4também não copia os elementos de [1], mas isso não importa, pois os números inteiros são imutáveis. Você não pode fazer algo assim 1.value = 2e transformar 1 em 2.

CAdaker
fonte
24
Surpreende-me que nenhum organismo indique isso, a resposta aqui é enganosa. [x]*3armazenar 3 referências como[x, x, x] só é correto quando xé mutável. Este trabalho does't para, por exemplo a=[4]*3, onde depois a[0]=5,a=[5,4,4].
Allanqunzi
42
Tecnicamente, ainda está correto. [4]*3é essencialmente equivalente a x = 4; [x, x, x]. É verdade, porém, que isso nunca causará nenhum problema, pois 4é imutável. Além disso, seu outro exemplo não é realmente um caso diferente. a = [x]*3; a[0] = 5não causará problemas, mesmo quex seja mutável, pois você não está modificando x, apenas modificando a. Eu não descreveria minha resposta como enganosa ou incorreta - você não pode se dar um tiro no pé se estiver lidando com objetos imutáveis.
CAdaker
19
@Allanqunzi você está errado. Faça x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. O Python não faz distinção entre objetos mutáveis ​​e imutáveis ​​aqui.
timgeb
129
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Molduras e Objetos

Tutor do Python ao vivo Visualize

nadrimajstor
fonte
Então, por que se escrevermos matriz = [[x] * 2] não faz 2 elemnts para o mesmo objeto, como no exemplo que você descreve, parece ser o mesmo conceito, o que estou perdendo?
Ahmed Mohamed
@AhmedMohamed Na verdade, ele faz uma lista com dois elementos do mesmo objeto exato que xse refere. Se você fizer um objeto global exclusivo com x = object()e em seguida, fazer matrix = [[x] * 2]estes vem como verdadeira:matrix[0][0] is matrix[0][1]
nadrimajstor
@nadrimajstor então, por que a mudança na matriz [0] não afeta a matriz [1], como no exemplo acima, com a matriz 2D.
Ahmed Mohamed
A surpresa @AhmedMohamed vem quando você faz uma "cópia" da sequência mutável (no nosso exemplo, é a list), então, se um row = [x] * 2a matrix = [row] * 2onde a ambas as linhas são exatamente o mesmo objeto, e agora muda para uma linha, de matrix[0][0] = yrepente, reflete na outra(matrix[0][0] is matrix[1][0]) == True
nadrimajstor
@AhmedMohamed Dê uma olhada em Ned Batchelder - fatos e mitos sobre nomes e valores de Python, pois ele pode oferecer uma explicação melhor. :)
nadrimajstor
52

Na verdade, é exatamente isso que você esperaria. Vamos decompor o que está acontecendo aqui:

Você escreve

lst = [[1] * 4] * 3

Isso é equivalente a:

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

Isso significa que lsté uma lista com 3 elementos, todos apontando para lst1. Isso significa que as duas linhas a seguir são equivalentes:

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

Como lst[0]nada mais é lst1.

Para obter o comportamento desejado, você pode usar a compreensão da lista:

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

Nesse caso, a expressão é reavaliada para cada n, levando a uma lista diferente.

PierreBdR
fonte
Apenas uma pequena além da resposta agradável aqui: é evidente que você está lidando com o mesmo objeto, se você faz id(lst[0][0])e id(lst[1][0])ou mesmo id(lst[0])eid(lst[1])
Sergiy Kolodyazhnyy
36
[[1] * 4] * 3

ou até:

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

Cria uma lista que referencia as [1,1,1,1]três vezes internas - e não três cópias da lista interna; portanto, sempre que você modificar a lista (em qualquer posição), verá a alteração três vezes.

É o mesmo que este exemplo:

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

onde provavelmente é um pouco menos surpreendente.

Blair Conrad
fonte
3
Você pode usar o operador "is" para descobrir isso. ls [0] é ls [1] retorna True.
Mipadi 27/10/08
9

Juntamente com a resposta aceita que explicou o problema corretamente, dentro da compreensão da sua lista, se Você estiver usando o python-2.x, use xrange()um gerador que seja mais eficiente ( range()no python 3 faz o mesmo trabalho) em _vez da variável descartável n:

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

Além disso, como uma maneira muito mais Pythonic , você pode usar itertools.repeat()para criar um objeto iterador de elementos repetidos:

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

PS Usando numpy, se você só quer criar uma matriz de uns ou zeros que você pode usar np.onese np.zerose / ou para outro uso número 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])
Kasramvd
fonte
6

Contêineres Python contêm referências a outros objetos. Veja este exemplo:

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

Nisso b é uma lista que contém um item que é uma referência à lista a. A lista aé mutável.

A multiplicação de uma lista por um número inteiro é equivalente a adicionar a lista a si mesma várias vezes (consulte operações comuns de sequência ). Então, continuando com o exemplo:

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

Podemos ver que a lista c agora contém duas referências à lista aque são equivalentes a c = b * 2.

O FAQ do Python também contém explicações sobre esse comportamento: Como faço para criar uma lista multidimensional?

Zbyněk Winkler
fonte
6

myList = [[1]*4] * 3cria um objeto de lista [1,1,1,1]na memória e copia sua referência 3 vezes. Isso é equivalente a obj = [1,1,1,1]; myList = [obj]*3. Qualquer modificação objserá refletida em três locais, onde quer que objseja referenciada na lista. A afirmação correta seria:

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

ou

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

O ponto importante a ser observado aqui é que o *operador é usado principalmente para criar uma lista de literais . Embora 1seja imutável, obj =[1]*4ainda criará uma lista de 1repetidas 4 vezes para formar[1,1,1,1] . Mas se for feita qualquer referência a um objeto imutável, o objeto será substituído por um novo.

Isso significa que, se o fizermos obj[1]=42, nãoobj será o que alguns podem supor. Isso também pode ser verificado:[1,42,1,1] [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
jerrymouse
fonte
2
Não se trata de literais. obj[2] = 42 substitui a referência no índice 2, em vez de alterar o objeto referenciado por esse índice, que é o que myList[2][0] = ...faz ( myList[2]é uma lista, e a atribuição altera a referência no índice 0 da lista). Obviamente, números inteiros não são mutáveis, mas muitos tipos de objetos são . E observe que a [....]notação de exibição da lista também é uma forma de sintaxe literal! Não confunda objetos compostos (como listas) e escalares (como números inteiros), com objetos mutáveis ​​vs. imutáveis.
Martijn Pieters
5

Em palavras simples, isso está acontecendo porque, em python, tudo funciona por referência ; portanto, quando você cria uma lista de listas dessa maneira, basicamente acaba com esses problemas.

Para resolver seu problema, você pode executar um dos seguintes procedimentos : 1. Use a documentação do array numpy para numpy.empty 2. Anexe a lista ao chegar a uma lista. 3. Você também pode usar o dicionário, se quiser

Neeraj Komuravalli
fonte
2

Vamos reescrever seu código da seguinte maneira:

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

myList = [z] * 3

Depois disso, execute o código a seguir para tornar tudo mais claro. O que o código faz é basicamente imprimir os ids dos objetos obtidos, o que

Retornar a "identidade" de um objeto

e nos ajudará a identificá-los e analisar o que acontece:

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

E você obterá a seguinte saída:

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

Então agora vamos passo a passo. Você tem xqual é 1e uma única lista de elementos que ycontém x. Seu primeiro passo é y * 4obter uma nova lista z, que é basicamente [x, x, x, x], ou seja, cria uma nova lista com 4 elementos, que são referências ao xobjeto inicial . O passo final é bem parecido. Você basicamente faz z * 3, o que é [[x, x, x, x]] * 3e retorna [[x, x, x, x], [x, x, x, x], [x, x, x, x]], pelo mesmo motivo do primeiro passo.

bagrat
fonte
2

Acho que todo mundo explica o que está acontecendo. Sugiro uma maneira de resolvê-lo:

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

myList[0][0] = 5

print myList

E então você tem:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
awulll
fonte
2

Tentando explicar de forma mais descritiva,

Operação 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]]

Operação 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]]

Percebeu por que a modificação do primeiro elemento da primeira lista não modificou o segundo elemento de cada lista? Isso porque [0] * 2realmente é uma lista de dois números e uma referência a 0 não pode ser modificada.

Se você deseja criar cópias clone, tente a Operação 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]]

Outra maneira interessante de criar cópias de clones, Operação 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]]
Adil Abbasi
fonte
2

@spelchekr da multiplicação de listas em Python: [[...]] * 3 faz 3 listas que se espelham quando modificadas e eu tive a mesma pergunta sobre "Por que apenas o externo * 3 cria mais referências enquanto o interno não? ? Por que não são todos os 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]]

Aqui está a minha explicação depois de tentar o código acima:

  • O interno *3também cria referências, mas suas referências são imutáveis, algo como [&0, &0, &0]: quando mudar li[0], você não poderá alterar nenhuma referência subjacente de const int 0; portanto, basta alterar o endereço de referência para o novo &1;
  • while ma=[&li, &li, &li]e lié mutável; portanto, quando você liga ma[0][0]=1, ma [0] [0] é igual a &li[0], então todas as &liinstâncias mudam seu primeiro endereço para &1.
ouxiaogu
fonte
1

Ao usar a função de lista embutida, você pode fazer assim

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
anand tripathi
fonte
1
Observe que o quarto passo pode ser eliminado se você fizer o segundo passo:a.insert(0,[5,1,1,1])
U10-Forward