Liste des changements de listes reflétés de manière inattendue dans les sous-listes


646

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

myList = [[1] * 4] * 3

La liste ressemblait à ceci:

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

Ensuite, j'ai changé l'une des valeurs les plus intimes:

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 ce que j'attendais. Quelqu'un peut-il expliquer ce qui se passe et comment le contourner?

Réponses:


561

Lorsque vous écrivez, [x]*3vous obtenez, essentiellement, la liste [x, x, x]. Autrement dit, une liste avec 3 références à la même chose x. Lorsque vous modifiez ensuite ce single, xil est visible via les trois références:

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

Pour y remédier, 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]*4chaque fois au lieu de l'évaluer une fois et de faire 3 références à 1 liste.


Vous vous demandez *peut- être pourquoi vous ne pouvez pas créer des objets indépendants comme le fait la compréhension de la liste. En effet, l'opérateur de multiplication *opère sur des objets, sans voir d'expressions. Lorsque vous utilisez *pour multiplier [[1] * 4]par 3, *seule la liste à 1 élément est [[1] * 4]évaluée, pas le [[1] * 4texte de l' expression. *n'a aucune idée de comment faire des copies de cet élément, aucune idée de la façon de réévaluer [[1] * 4], et aucune idée que vous voulez même des copies, et en général, il pourrait même ne pas y avoir de moyen de copier l'élément.

La seule option *est de faire de nouvelles références à la sous-liste existante au lieu d'essayer de faire de nouvelles sous-listes. Tout le reste serait incohérent ou nécessiterait une refonte majeure des décisions fondamentales de conception du 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] * 4chaque fois pour la même raison [x**2 for x in range(3)]réévalue à x**2chaque fois. Chaque évaluation de [1] * 4génère une nouvelle liste, donc la compréhension de la liste fait ce que vous vouliez.

Soit dit en passant, [1] * 4ne 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 = 2et transformer un 1 en 2.


24
Je suis surpris qu'aucun organisme ne le signale, la réponse ici est trompeuse. [x]*3stocker 3 références comme [x, x, x]c'est juste quand xest mutable. Cela ne fonctionne pas pour, par exemple a=[4]*3, où après a[0]=5,a=[5,4,4].
Allanqunzi

42
Techniquement, c'est toujours correct. [4]*3est essentiellement équivalent à x = 4; [x, x, x]. Il est vrai, cependant, que cela ne causera jamais de problème car il 4est immuable. De plus, votre autre exemple n'est pas vraiment différent. a = [x]*3; a[0] = 5ne causera pas de problèmes même s'il xest modifiable, puisque vous ne modifiez pas x, seulement modifiez a. Je ne décrirais pas ma réponse comme trompeuse ou incorrecte - vous ne pouvez tout simplement pas vous tirer une balle dans le pied si vous avez affaire à des objets immuables.
CAdaker

19
@Allanqunzi vous vous trompez. Faire x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python ne fait aucune distinction entre les objets mutables et immuables ici.
timgeb

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

Cadres et objets

Live Python Tutor Visualize


Alors, pourquoi si nous écrivons matrice = [[x] * 2] ne fait pas 2 elemnts pour le même objet comme l'exemple que vous décrivez, il semble être le même concept, qu'est-ce qui me manque?
Ahmed Mohamed

@AhmedMohamed En effet, il fait une liste avec deux éléments du même objet exact auquel il xfait référence. Si vous créez un objet unique au monde avec x = object(), puis matrix = [[x] * 2]matrix[0][0] is matrix[0][1]
réalisez

@nadrimajstor alors pourquoi le changement de matrice [0] n'affecte pas la matrice [1] comme l'exemple ci-dessus avec matrice 2d.
Ahmed Mohamed

@AhmedMohamed Surprise survient lorsque vous effectuez une "copie" de la séquence modifiable (dans notre exemple, il s'agit de a list), donc si a row = [x] * 2que a matrix = [row] * 2où les deux lignes sont exactement le même objet et se transforme maintenant en une ligne matrix[0][0] = yse reflète soudainement dans l'autre(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Jetez un oeil à Ned Batchelder - Faits et mythes sur les noms et les valeurs Python car il pourrait offrir une meilleure explication. :)
nadrimajstor

52

En fait, c'est exactement ce à quoi vous vous attendez. Décomposons ce qui se passe ici:

vous écrivez

lst = [[1] * 4] * 3

Cela équivaut à:

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

Cela signifie que lstc'est une liste avec 3 éléments pointant tous vers lst1. Cela signifie que les deux lignes suivantes sont équivalentes:

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

Tout lst[0]comme rien d'autre lst1.

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

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

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


Juste un petit ajout à la belle réponse ici: il est évident que vous avez affaire au même objet si vous le faites id(lst[0][0])et id(lst[1][0])ou même id(lst[0])etid(lst[1])
Sergiy Kolodyazhnyy

36
[[1] * 4] * 3

ou même:

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

Crée une liste qui référence les [1,1,1,1]3 fois internes - pas trois copies de la liste interne, donc chaque fois que vous modifiez la liste (dans n'importe quelle position), vous verrez le changement trois fois.

C'est la même chose 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.


3
Vous pouvez utiliser l'opérateur "is" pour le découvrir. ls [0] est ls [1] renvoie True.
mipadi

9

À côté de la réponse acceptée qui a expliqué le problème correctement, dans votre compréhension de la liste, si vous utilisez python-2.x, utilisez xrange()un générateur plus efficace ( range()en python 3 fait le même travail) _au lieu de la variable jetable n:

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

De plus, en tant que méthode beaucoup plus Pythonique , vous pouvez utiliser itertools.repeat()pour créer un objet itérateur d'éléments répétés:

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

PS En utilisant numpy, si vous voulez seulement créer un tableau de ceux ou de zéros que vous pouvez utiliser np.oneset np.zeroset / ou pour toute autre utilisation du numéro 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

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

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

Dans bc'est une liste qui contient un élément qui est une référence à la liste a. La liste aest modifiable.

La multiplication d'une liste par un entier équivaut à ajouter la liste à elle-même plusieurs fois (voir les 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 ccontient maintenant deux références à list aqui est équivalent à c = b * 2.

La FAQ Python contient également une explication de ce comportement: Comment créer une liste multidimensionnelle?


6

myList = [[1]*4] * 3crée un objet liste [1,1,1,1]en mémoire et copie sa référence 3 fois. C'est équivalent à obj = [1,1,1,1]; myList = [obj]*3. Toute modification à objsera reflétée à trois endroits, où qu'elle objsoit référencée 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)]

Une chose importante à noter ici est que l' *opérateur est principalement utilisé pour créer une liste de littéraux . Bien qu'il 1soit immuable, obj =[1]*4il créera toujours une liste de 1répétitions 4 fois pour se former [1,1,1,1]. Mais si une référence à un objet immuable est faite, l'objet est remplacé par un nouveau.

Cela signifie que si nous le faisons obj[1]=42, cela objne deviendra [1,42,1,1] pas [42,42,42,42] comme certains peuvent le penser. Cela peut également être vérifié:

>>> 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
Il ne s'agit pas de littéraux. obj[2] = 42 remplace la référence à l'index 2, par opposition à la mutation de l'objet référencé par cet index, ce qui est le myList[2][0] = ...cas ( myList[2]est une liste, et l'assignation modifie la référence à l'index 0 dans la liste). Bien sûr, les entiers ne sont pas modifiables, mais de nombreux types d'objets le sont . Et notez que la [....]notation d'affichage de liste est également une forme de syntaxe littérale! Ne confondez pas les objets composés (tels que les listes) et scalaires (tels que les entiers), avec des objets mutables et immuables.
Martijn Pieters

5

En termes simples, cela se produit car en python, tout fonctionne par référence , donc lorsque vous créez une liste de liste de cette façon, vous vous retrouvez avec de tels problèmes.

Pour résoudre votre problème, vous pouvez effectuer l'une des opérations suivantes: 1. Utilisez la documentation du tableau numpy pour numpy.empty 2. Ajoutez la liste au fur et à mesure que vous accédez à une liste. 3. Vous pouvez également utiliser le dictionnaire si vous le souhaitez


2

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. Ce que le code fait, c'est essentiellement imprimer les ids des objets obtenus, qui

Rendre «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 la sortie suivante:

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, allons-y étape par étape. Vous avez xqui est 1, et une seule liste d'éléments ycontenant x. Votre première étape consiste à y * 4obtenir une nouvelle liste z, c'est-à [x, x, x, x]-dire qu'elle crée une nouvelle liste qui comprendra 4 éléments, qui sont des références à l' xobjet initial . L'étape nette est assez similaire. Vous faites essentiellement z * 3, ce qui est [[x, x, x, x]] * 3et revient [[x, x, x, x], [x, x, x, x], [x, x, x, x]], pour la même raison que pour la première étape.


2

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

2

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

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

Si vous souhaitez créer des copies clonées, 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]]

une autre façon intéressante de créer des copies de clones, l'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]]

2

@spelchekr de la multiplication des listes Python: [[...]] * 3 fait 3 listes qui se reflètent lorsqu'elles sont modifiées et j'avais la même question à propos de "Pourquoi seulement l'extérieur * 3 crée-t-il plus de références alors que l'intérieur ne fait pas "Pourquoi ce n'est 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 *3cré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 pas changer une référence sous-jacente de const int 0, vous pouvez donc simplement changer l'adresse de référence en la nouvelle &1;
  • tant que ma=[&li, &li, &li]et liest mutable, donc lorsque vous appelez ma[0][0]=1, ma [0] [0] est également égal à &li[0], donc toutes les &liinstances changeront sa 1ère adresse en &1.

1

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

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
Remarque, la quatrième étape peut être abandonnée si vous effectuez la deuxième étape:a.insert(0,[5,1,1,1])
U10-Forward
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.