opérateur de multiplication appliqué à la liste (structure de données)
-
13-09-2019 - |
Question
Je lis Comment penser comme un informaticien qui est un texte d'introduction pour « Python Programmation » .
Je veux clarifier le comportement de l'opérateur de multiplication (de *
) lorsqu'il est appliqué à des listes.
Considérons la fonction make_matrix
def make_matrix(rows, columns):
"""
>>> make_matrix(4, 2)
[[0, 0], [0, 0], [0, 0], [0, 0]]
>>> m = make_matrix(4, 2)
>>> m[1][1] = 7
>>> m
[[0, 0], [0, 7], [0, 0], [0, 0]]
"""
return [[0] * columns] * rows
La sortie réelle est
[[0, 7], [0, 7], [0, 7], [0, 7]]
La version correcte de make_matrix est:
def make_matrix(rows, columns):
"""
>>> make_matrix(3, 5)
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
>>> make_matrix(4, 2)
[[0, 0], [0, 0], [0, 0], [0, 0]]
>>> m = make_matrix(4, 2)
>>> m[1][1] = 7
>>> m
[[0, 0], [0, 7], [0, 0], [0, 0]]
"""
matrix = []
for row in range(rows):
matrix += [[0] * columns]
return matrix
La raison pour laquelle la première version de make_matrix échoue (comme expliqué dans le livre à 9.8) est que
... chaque rangée est un alias des autres rangées ...
Je me demande pourquoi
[[0] * columns] * rows
cause ... chaque rangée est un alias des autres rangées ...
mais pas
[[0] * columns]
i.e.. pourquoi chaque [0]
dans une rangée ne sont pas un alias de l'autre élément de ligne.
La solution
TOUT en python sont des objets, et python ne fait jamais des copies à moins explicity demandé de le faire.
Quand vous faites
innerList = [0] * 10
vous créez une liste de 10 éléments, tous au même référant objet int
0
.
Comme des objets entiers sont immuable , quand vous faites
innerList[1] = 15
Vous changez le deuxième élément de la liste afin qu'il se réfère à un autre entier 15
. Cela fonctionne toujours à cause de int
objets immuabilité.
Voilà pourquoi
outerList = innerList * 5
va créer un objet list
avec 5 éléments, chacun d'eux est une référence à le même innerList
comme ci-dessus. Mais puisque les objets sont list
mutable :
outerList[2].append('something')
est le même que:
innerList.append('something')
Parce qu'ils sont deux références à même objet list
. Ainsi, l'élément se retrouve dans ce seul list
. Il semble dupliquer, mais le fait est qu'il n'y a qu'un seul objet list
, et de nombreuses références à elle.
Par contre si vous faites
outerList[1] = outerList[1] + ['something']
Ici vous créer une autre objet list
(en utilisant +
des listes est une copie explicite), et l'attribution d'une référence à elle dans la deuxième position de outerList
. Si vous « append » l'élément de cette façon (pas vraiment annexant, mais créer une autre liste), innerList
ne sera pas affecté.
Autres conseils
listes ne sont pas primitives, ils sont passés par référence. Une copie de la liste est un pointeur vers une liste (en C jargon). Tout ce que vous faites à la liste arrive à toutes les copies de la liste et les copies de son contenu, sauf si vous faites une copie superficielle.
[[0] * columns] * rows
Oops, nous avons juste fait une grande liste de pointeurs sur [0]. Changer un et vous les modifiez tous.
Entiers ne sont pas passés par référence, ils sont vraiment copiés, donc [0] * contenu fait vraiment beaucoup de nouveaux 0 et les annexant à la liste.