Mudanças na lista de listas refletidas nas sublistas de forma inesperada
Eu precisava criar uma lista de listas em Python, então digitei o seguinte:
myList = [[1] * 4] * 3
A lista era parecida com esta:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Então eu mudei um dos valores mais internos:
myList[0][0] = 5
Agora minha lista se parece com esta:
[[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?
Respostas
Quando você escreve, [x]*3
você obtém, essencialmente, a lista [x, x, x]
. Ou seja, uma lista com 3 referências à mesma x
. Quando você modifica este single, x
ele fica visível por meio de todas as 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 irá reavaliar [1]*4
cada vez em vez de avaliar uma vez e fazer 3 referências a 1 lista.
Você pode se perguntar por *
que não pode fazer objetos independentes da maneira que a compreensão de lista faz. Isso porque o operador de multiplicação *
opera em objetos, sem ver as expressões. Quando você usa *
para multiplicar [[1] * 4]
por 3, *
vê apenas a lista de 1 elemento [[1] * 4]
avaliada como, não o [[1] * 4
texto da expressão. *
não tem ideia de como fazer cópias daquele elemento, não tem ideia de como reavaliar [[1] * 4]
e não tem ideia de que você quer mesmo cópias e, em geral, pode nem haver uma maneira de copiar o elemento.
A única opção *
é fazer novas referências à sublista existente em vez de tentar fazer novas sublistas. Qualquer outra coisa seria inconsistente ou exigiria um grande redesenho das decisões fundamentais de design da linguagem.
Em contraste, uma compreensão de lista reavalia a expressão do elemento em cada iteração. [[1] * 4 for n in range(3)]
reavalia [1] * 4
todas as vezes pelo mesmo motivo [x**2 for x in range(3)]
reavalia x**2
todas as vezes. Cada avaliação de [1] * 4
gera uma nova lista, então a compreensão da lista faz o que você deseja.
Aliás, [1] * 4
também não copia os elementos de [1]
, mas isso não importa, já que os inteiros são imutáveis. Você não pode fazer algo como 1.value = 2
transformar 1 em 2.
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
Tutor Python ao vivo Visualize
Na verdade, isso é exatamente o 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 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 de 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, resultando em uma lista diferente.
[[1] * 4] * 3
ou mesmo:
[[1, 1, 1, 1]] * 3
Cria uma lista que faz referência ao interno [1,1,1,1]
3 vezes - não três cópias da lista interna, portanto, sempre que você modificar a lista (em qualquer posição), verá a mudança 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.
Junto com a resposta aceita que explicou o problema corretamente, dentro da compreensão de sua lista, se você estiver usando python-2.x, use xrange()
que retorna um gerador que é mais eficiente ( range()
em 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 pitônica , 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.ones
e np.zeros
e / 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])
Os contêineres Python contêm referências a outros objetos. Veja este exemplo:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
Nesta b
é uma lista que contém um item que é uma referência para a lista a
. A lista a
é mutável.
A multiplicação de uma lista por um inteiro é equivalente a adicionar a lista a si mesma várias vezes (consulte as operações de sequência comuns ). 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, a
que é equivalente a c = b * 2
.
Python FAQ também contém uma explicação sobre esse comportamento: Como faço para criar uma lista multidimensional?
myList = [[1]*4] * 3
cria 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 em obj
será refletida em três lugares, onde quer que obj
seja referenciado na lista. A afirmação certa seria:
myList = [[1]*4 for _ in range(3)]
ou
myList = [[1 for __ in range(4)] for _ in range(3)]
O que é importante observar aqui é que o *
operador é usado principalmente para criar uma lista de literais . Embora 1
seja imutável, obj =[1]*4
ainda criará uma lista de 1
repetidos 4 vezes para formar [1,1,1,1]
. Mas se qualquer referência a um objeto imutável for feita, o objeto será sobrescrito por um novo.
Isso significa que, se o fizermos obj[1]=42
, nãoobj
será como 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
Deixe-nos reescrever seu código da seguinte maneira:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
Então, tendo isso, execute o seguinte código para tornar tudo mais claro. O que o código faz é basicamente imprimir os ids dos objetos obtidos, que
Retorna 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
Agora, vamos passo a passo. Você tem x
qual é 1
, e uma única lista de elementos y
contendo x
. Seu primeiro passo é y * 4
obter uma nova lista z
, que é basicamente [x, x, x, x]
, isto é, criar uma nova lista que terá 4 elementos, que são referências ao x
objeto inicial . O passo líquido é bastante semelhante. Basicamente z * 3
, você faz , o que é [[x, x, x, x]] * 3
e retorna [[x, x, x, x], [x, x, x, x], [x, x, x, x]]
, pelo mesmo motivo da primeira etapa.
Em palavras simples, isso está acontecendo porque em python tudo funciona por referência , então, quando você cria uma lista de listas dessa forma, basicamente acaba com esses problemas.
Para resolver o seu problema, você pode fazer qualquer um deles: 1. Use a documentação do array numpy para numpy.empty 2. Anexe a lista conforme você acessa uma lista. 3. Você também pode usar o dicionário, se quiser
Acho que todo mundo explica o que está acontecendo. Eu sugiro uma maneira de resolver isso:
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]]
@spelchekr da multiplicação de lista do Python: [[...]] * 3 faz 3 listas que se espelham quando modificadas e eu tinha a mesma pergunta sobre "Por que apenas o externo * 3 cria mais referências enquanto o interno não ? Por que não é tudo 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á minha explicação depois de tentar o código acima:
- O interno
*3
também cria referências, mas suas referências são imutáveis, algo como[&0, &0, &0]
, então, quando alterarli[0]
, você não pode alterar nenhuma referência subjacente de const int0
, portanto, você pode apenas alterar o endereço de referência para o novo&1
; - while
ma=[&li, &li, &li]
eli
é mutável, portanto, quando você chamama[0][0]=1
, ma [0] [0] é igual a&li[0]
, portanto, todas as&li
instâncias mudarão seu primeiro endereço para&1
.
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] * 2
realmente é 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 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]]
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
Essas perguntas têm muitas respostas, estou adicionando minha resposta para explicar o mesmo em forma de diagrama.
A maneira como você criou o 2D, cria uma lista rasa
arr = [[0]*cols]*row
Em vez disso, se você deseja atualizar os elementos da lista, você deve usar
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
Explicação :
Pode-se criar uma lista usando:
arr = [0]*N
ou
arr = [0 for i in range(N)]
No primeiro caso, todos os índices da matriz apontam para o mesmo objeto inteiro
e quando você atribui um valor a um índice particular, um novo objeto int é criado, por exemplo, arr[4] = 5
cria
Agora vamos ver o que acontece quando criamos uma lista de lista, neste caso, todos os elementos de nossa lista superior irão apontar para a mesma lista
E se você atualizar o valor de qualquer índice, um novo objeto int será criado. Mas como todos os índices de lista de nível superior estão apontando para a mesma lista, todas as linhas terão a mesma aparência. E você terá a sensação de que atualizar um elemento é atualizar todos os elementos dessa coluna.
Créditos: Obrigado a Pranav Devarakonda pela explicação fácil aqui
Cheguei aqui porque estava procurando ver como poderia aninhar um número arbitrário de listas. Existem muitas explicações e exemplos específicos acima, mas você pode generalizar uma lista dimensional N de listas de listas de ... com a seguinte função recursiva:
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])]
Você faz sua primeira chamada para a função assim:
dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)
onde (3,5,2)
é uma tupla das dimensões da estrutura (semelhante ao shape
argumento numpy ) e 1.0
é o elemento com o qual você deseja que a estrutura seja inicializada (funciona com None também). Observe que o init
argumento é fornecido apenas pela chamada recursiva para levar adiante as listas de filhos aninhados
saída acima:
[[[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]]]
definir elementos específicos:
l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'
saída resultante:
[[[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]]]
a natureza não digitada das listas é demonstrada acima
Observe que os itens na sequência não são copiados; eles são referenciados várias vezes . Isso geralmente assombra novos programadores Python; considerar:
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
O que aconteceu é que [[]]
é uma lista de um elemento contendo uma lista vazia, portanto, todos os três elementos de [[]] * 3
são referências a essa lista única vazia. A modificação de qualquer um dos elementos das listas modifica essa lista única.
Outro exemplo para explicar isso é o uso de matrizes multidimensionais .
Você provavelmente tentou fazer uma matriz multidimensional como esta:
>>> A = [[**None**] * 2] * 3
Parece correto se você imprimir:
>>> A
[[None, None], [None, None], [None, None]]
Mas quando você atribui um valor, ele aparece em vários lugares:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
A razão é que replicar uma lista com *
não cria cópias, apenas cria referências aos objetos existentes. O 3 cria uma lista contendo 3 referências à mesma lista de comprimento dois. As alterações em uma linha serão mostradas em todas as linhas, o que quase certamente não é o que você deseja.