Mudanças na lista de listas refletidas nas sublistas de forma inesperada

Oct 27 2008

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

614 CAdaker Oct 27 2008 at 22:03

Quando você escreve, [x]*3você 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 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]*4cada 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] * 4texto 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] * 4todas as vezes pelo mesmo motivo [x**2 for x in range(3)]reavalia x**2todas as vezes. Cada avaliação de [1] * 4gera uma nova lista, então a compreensão da lista faz o que você deseja.

Aliás, [1] * 4també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 = 2transformar 1 em 2.

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

Tutor Python ao vivo Visualize

56 PierreBdR Oct 27 2008 at 22:07

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.

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

10 Kasravnd Jun 18 2015 at 00:08

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

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 cagora contém duas referências à lista, aque é 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?

7 jerrymouse Apr 06 2017 at 12:36

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 em objserá refletida em três lugares, onde quer que objseja 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 1seja imutável, obj =[1]*4ainda criará uma lista de 1repetidos 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
5 bagrat Jun 10 2015 at 21:38

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 xqual é 1, e uma única lista de elementos ycontendo x. Seu primeiro passo é y * 4obter 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 xobjeto inicial . O passo líquido é bastante semelhante. Basicamente z * 3, você faz , o que é [[x, x, x, x]] * 3e retorna [[x, x, x, x], [x, x, x, x], [x, x, x, x]], pelo mesmo motivo da primeira etapa.

5 NeerajKomuravalli Jun 14 2016 at 13:36

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

4 awulll Apr 24 2016 at 20:31

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

@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 *3também cria referências, mas suas referências são imutáveis, algo como [&0, &0, &0], então, quando alterar li[0], você não pode alterar nenhuma referência subjacente de const int 0, portanto, você pode apenas alterar o endereço de referência para o novo &1;
  • while ma=[&li, &li, &li]e lié mutável, portanto, quando você chama ma[0][0]=1, ma [0] [0] é igual a &li[0], portanto, todas as &liinstâncias mudarão seu primeiro endereço para &1.
3 AdilAbbasi Aug 10 2016 at 14:09

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

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

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

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

Brian Oct 23 2020 at 02:57

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 shapeargumento numpy ) e 1.0é o elemento com o qual você deseja que a estrutura seja inicializada (funciona com None também). Observe que o initargumento é 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

mishsx Nov 23 2020 at 02:45

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