web-dev-qa-db-fra.com

Liste des modifications de listes reflétées de manière inattendue dans les sous-listes

J'avais besoin de créer une liste de listes en Python, alors j'ai tapé ce qui suit:

myList = [[1] * 4] * 3

La liste ressemblait à ceci:

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

Puis j'ai changé l'une des valeurs les plus profondes:

myList[0][0] = 5

Maintenant ma liste ressemble à ceci:

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

ce qui n'est pas ce que je voulais ou attendais. Quelqu'un peut-il s'il vous plaît expliquer ce qui se passe et comment le contourner?

541
Charles Anderson

Lorsque vous écrivez [x]*3, Vous obtenez essentiellement la liste [x, x, x]. Autrement dit, une liste avec 3 références au même x. Lorsque vous modifiez ensuite ce simple x, il est visible via les trois références.

Pour résoudre ce problème, vous devez vous assurer de créer une nouvelle liste à chaque position. Une façon de le faire est

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

qui réévaluera [1]*4 à chaque fois au lieu de l’évaluer une fois et de faire 3 références à une liste.


Vous pourriez vous demander pourquoi * Ne peut pas créer d'objets indépendants de la même manière que la compréhension de la liste. En effet, l'opérateur de multiplication * Opère sur les objets sans voir d'expressions. Lorsque vous utilisez * Pour multiplier [[1] * 4] Par 3, * Ne voit que la liste d'éléments à 1 [[1] * 4] Évaluée, pas la [[1] * 4 texte d'expression. * Ne sait pas comment faire des copies de cet élément ni comment réévaluer [[1] * 4], Et vous ne savez même pas que vous voulez des copies. De manière générale, il peut ne pas être possible de copier l'élément.

La seule option * Consiste à faire de nouvelles références à la sous-liste existante au lieu d'essayer de créer de nouvelles sous-listes. Toute autre solution serait incohérente ou nécessiterait une refonte majeure des décisions fondamentales en matière de conception de langage.

En revanche, une compréhension de liste réévalue l'expression de l'élément à chaque itération. [[1] * 4 for n in range(3)] réévalue [1] * 4 à chaque fois pour la même raison que [x**2 for x in range(3)] réévalue x**2 à chaque fois. Chaque évaluation de [1] * 4 Génère une nouvelle liste, de sorte que la compréhension de la liste fait ce que vous vouliez.

Incidemment, [1] * 4 Ne copie pas non plus les éléments de [1], Mais cela n'a pas d'importance, car les entiers sont immuables. Vous ne pouvez pas faire quelque chose comme 1.value = 2 Et transformer un 1 en 2.

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

Frames and Objects

Live Python Tutor Visualize

116
nadrimajstor

En fait, c’est exactement ce que vous attendez. Décomposons ce qui se passe ici:

Vous écrivez

lst = [[1] * 4] * 3

Ceci est équivalent à:

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

Cela signifie que lst est une liste de 3 éléments pointant tous sur lst1. Cela signifie que les deux lignes suivantes sont équivalentes:

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

Comme lst[0] n'est rien d'autre que lst1.

Pour obtenir le comportement souhaité, vous pouvez utiliser la compréhension de liste:

lst = [ [1]*4 for n in xrange(3) ]

Dans ce cas, l'expression est ré-évaluée pour chaque n, conduisant à une liste différente.

45
PierreBdR
[[1] * 4] * 3

ou même:

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

Crée une liste qui référence le [1,1,1,1] Interne 3 fois - et non trois copies de la liste interne. Ainsi, chaque fois que vous modifiez la liste (quelle que soit sa position), vous verrez le changement trois fois.

C'est le même que cet exemple:

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

où c'est probablement un peu moins surprenant.

32
Blair Conrad

En plus de la réponse acceptée qui explique correctement le problème, dans la compréhension de votre liste, si vous utilisez python-2.x, utilisez xrange() qui renvoie un générateur plus efficace (range() in = python 3 fait le même travail) _ au lieu de la variable à jeter n:

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

En outre, de manière beaucoup plus Pythonic , vous pouvez utiliser itertools.repeat() ) pour créer un objet itérateur de type répété. éléments :

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

P.S. En utilisant numpy, si vous voulez seulement créer un tableau de uns ou de zéros, vous pouvez utiliser np.ones Et np.zeros Et/ou pour un autre nombre, utilisez 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
Kasrâmvd

En termes simples, cela se produit car dans python tout fonctionne par référence , par conséquent, lorsque vous créez une liste vous vous retrouvez avec de tels problèmes.

Pour résoudre votre problème, vous pouvez utiliser l’un des deux: 1. Utilisez numpy array documentation pour numpy.empty 2. Ajoutez la liste lorsque vous arrivez à une liste. 3. Vous pouvez également utiliser le dictionnaire si vous voulez

4
Neeraj Komuravalli

Les conteneurs Python contiennent des références à d'autres objets. Voir cet exemple:

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

Dans ce b est une liste qui contient un élément qui est une référence à la liste a. La liste a est modifiable.

La multiplication d'une liste par un entier revient à l'ajouter plusieurs fois à la liste (voir opérations de séquence courantes ). Donc, continuons avec l'exemple:

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

Nous pouvons voir que la liste c contient maintenant deux références à la liste a, ce qui équivaut à c = b * 2.

Python FAQ contient également une explication de ce problème: Comment créer une liste multidimensionnelle?

3
Zbyněk Winkler

myList = [[1]*4] * 3 crée un objet de liste [1,1,1,1] en mémoire et copie sa référence 3 fois. Ceci est équivalent à obj = [1,1,1,1]; myList = [obj]*3. Toute modification apportée à obj sera répercutée à trois endroits, chaque fois que obj sera référencé dans la liste. La bonne déclaration serait:

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

ou

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

Ce qui est important à noter ici est-ce que * l'opérateur est principalement utilisé pour créer un liste de littéraux. Puisque 1 est un littéral, d'où obj =[1]*4 va créer [1,1,1,1] où chacun 1 est atomique et not une référence de 1 répété 4 fois. Cela signifie que si nous faisons obj[2]=42, alors obj deviendra [1,1,42,1]pas [42,42,42,42] comme certains peuvent le supposer.

2
jerrymouse

Essayer de l'expliquer de manière plus descriptive,

Opération 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]]

Opération 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]]

Remarqué pourquoi la modification du premier élément de la première liste n'a-t-elle pas modifié le deuxième élément de chaque liste? C'est parce que [0] * 2 est vraiment une liste de deux nombres et une référence à 0 ne peut pas être modifiée.

Si vous voulez créer des copies de clonage, essayez l'opération 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]]

un autre moyen intéressant de créer des copies de clonage, opération 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]]
1
Adil Abbasi

Laissez-nous réécrire votre code de la manière suivante:

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

myList = [z] * 3

Ensuite, exécutez le code suivant pour que tout soit plus clair. En gros, le code affiche les id s des objets obtenus, qui

Renvoie “l'identité” d'un objet

et nous aidera à les identifier et à analyser ce qui se passe:

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

Et vous obtiendrez le résultat suivant:

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

Alors maintenant, laissez-nous aller étape par étape. Vous avez x qui est 1, et une liste d'éléments unique y contenant x. Votre première étape est y * 4 qui vous donnera une nouvelle liste z, qui est fondamentalement [x, x, x, x], c’est-à-dire qu’il crée une nouvelle liste qui aura 4 éléments, qui sont des références à l’objet initial x. Le pas net est assez similaire. Vous faites essentiellement z * 3, lequel est [[x, x, x, x]] * 3 et retourne [[x, x, x, x], [x, x, x, x], [x, x, x, x]], pour la même raison que pour la première étape.

1
bagrat

Je suppose que tout le monde explique ce qui se passe. Je suggère une façon de le résoudre:

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

myList[0][0] = 5

print myList

Et puis vous avez:

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

En utilisant la fonction de liste intégrée, vous pouvez faire comme ceci

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
0
anand tripathi

@spelchekr à partir de multiplication de liste Python: [[...]] * 3 crée 3 listes qui se reflètent l'une après l'autre lorsqu'elles sont modifiées pourquoi l’intérieur ne le fait pas? Pourquoi n’est-ce pas tous les 1? "

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

Voici mon explication après avoir essayé le code ci-dessus:

  • L'intérieur *3 _ crée également des références, mais ses références sont immuables, quelque chose comme [&0, &0, &0], alors quand changer li[0], vous ne pouvez modifier aucune référence sous-jacente de const int 0, vous pouvez donc changer l'adresse de référence dans la nouvelle &1;
  • tandis que ma=[&li, &li, &li] et li est modifiable, donc lorsque vous appelez ma[0][0]=1, ma [0] [0] est également égal à &li[0], donc tous les &li _ instances changeront sa 1ère adresse en &1.
0
ouxiaogu