Question

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

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

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?

Était-ce utile?

La solution

Lorsque vous écrivez [x] * 3 , vous obtenez essentiellement la liste [x, x, x] . C'est-à-dire une liste avec 3 références au même x . Lorsque vous modifiez ensuite ce x , il est visible via ses 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 * agit sur les objets, sans voir les expressions. Lorsque vous utilisez * pour multiplier [[1] * 4] par 3, * ne voit que la liste d'éléments à 1 [[ 1] * 4] correspond au texte d'expression [[1] * 4 ]. * ne sait pas comment faire des copies de cet élément, comment réévaluer [[1] * 4] , et vous ne savez même pas que vous voulez des copies, et en général , il n’ya peut-être même pas moyen 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 pour n dans la plage (3)] réévalue [1] * 4 à chaque fois pour la même raison [x ** 2 pour x in range (3)] réévalue x ** 2 à chaque fois. Chaque évaluation de [1] * 4 génère une nouvelle liste. La compréhension de la liste fait donc 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.

Autres conseils

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

Cadres et objets

Tuteur Live Python Visualiser

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.

[[1] * 4] * 3

ou même:

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

Crée une liste qui référence le interne [1,1,1,1] 3 fois - pas trois copies de la liste interne, donc chaque fois que vous modifiez la liste (à 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.

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 () en python 3 effectue le même travail) _ à la place de la variable jetable n :

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

En outre, Pythonic , vous pouvez également 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]

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

En termes simples, cela se produit car, dans Python, tout fonctionne par référence . Ainsi, lorsque vous créez une liste de cette manière, vous vous retrouvez avec de tels problèmes.

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

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 , vous trouverez une liste contenant un élément faisant 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 qui équivaut à c = b * 2 .

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

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 de suite. . Ceci équivaut à obj = [1,1,1,1]; myList = [obj] * 3 . Toute modification de obj sera répercutée à trois endroits, partout où obj est 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 qu'il est important de noter ici est que l'opérateur * est généralement utilisé pour créer une liste de littéraux . Puisque 1 est un littéral, obj = [1] * 4 créera donc [1,1,1,1] où chaque 1 est atomique et pas une référence de 1 répétée 4 fois. Cela signifie que si nous faisons obj [2] = 42 , alors obj deviendra [1,1,42,1] non [42,42,42,42] , comme certains pourraient le croire.

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

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

myList = [z] * 3

Ensuite, lancez le code suivant pour que tout soit plus clair. En gros, le code affiche la id s des objets obtenus, qui

  

Renvoyez le & # 8220; identité & # 8221; 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, allons-y étape par étape. Vous avez x qui est 1 et une liste d'éléments uniques y contenant x . Votre première étape est y * 4 qui vous donnera une nouvelle liste z , qui est essentiellement [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 x initial. Le pas net est assez similaire. Vous faites essentiellement z * 3 , qui est [[x, x, x, x]] * 3 et renvoie [[x, x, x, x ], [x, x, x, x], [x, x, x, x]] , pour la même raison que pour la première étape.

Je suppose que tout le monde explique ce qui se passe. Je suggère un moyen de le résoudre:

myList = [[1 pour i dans la plage (4)] pour j dans la plage (3)]

myList[0][0] = 5

print myList

Et puis vous avez:

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

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 remarquez pourquoi la modification du premier élément de la première liste ne modifie pas le deuxième élément de chaque liste? En effet, [0] * 2 est en réalité une liste de deux nombres et une référence à 0 ne peut pas être modifiée.

Si vous souhaitez 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]]

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

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top