Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

list __mul__

6 réponses
Avatar
Benoit Izac
Bonjour,

Il y a quelque chose qui m'échappe avec les lists et la multiplication ;
soit l'exemple suivant qui souhaite afficher un rectangle de (1, 2)
à (4, 4) dans une matrice de 7 par 8 :


nb_row, nb_col = (7, 8)

m = [['.'] * nb_col for _ in range(nb_row)]
for r in range(1, 4+1):
for c in range(2, 4+1):
m[r][c] = '#'

for r in range(nb_row):
for c in range(nb_col):
print(m[r][c], end="")
print()


Si je remplace
m = [['.'] * nb_col for _ in range(nb_row)]
par
m = [['.'] * nb_col] * nb_row
ça ne fonctionne plus, les colonnes 2 à 4 sont toutes remplies de '#'.
Je pense que « * » n'effectue pas une copie de « [['.'] * nb_col] » mais
un "pointeur" vers la structure. Mais du coup, pourquoi ça marche pour
« ['.'] * nb_col » et je ne suis pas obligé d'écrire
« ['.' for _ in range(nb_col)] » ?

[['.' for _ in range(nb_col)] for _ in range(nb_row)] fonctionne également.

--
Benoit Izac

6 réponses

Avatar
Alain Ketterlin
Benoit Izac writes:
Il y a quelque chose qui m'échappe avec les lists et la multiplicati on ;
soit l'exemple suivant qui souhaite afficher un rectangle de (1, 2)
à (4, 4) dans une matrice de 7 par 8 :
nb_row, nb_col = (7, 8)
m = [['.'] * nb_col for _ in range(nb_row)]
for r in range(1, 4+1):
for c in range(2, 4+1):
m[r][c] = '#'
for r in range(nb_row):
for c in range(nb_col):
print(m[r][c], end="")
print()
Si je remplace
m = [['.'] * nb_col for _ in range(nb_row)]
par
m = [['.'] * nb_col] * nb_row
ça ne fonctionne plus, les colonnes 2 à 4 sont toutes remplies de '#'.
Je pense que « * » n'effectue pas une copie de «  [['.'] * nb_col] » mais
un "pointeur" vers la structure. Mais du coup, pourquoi ça marche po ur
« ['.'] * nb_col » et je ne suis pas obligé d' écrire
« ['.' for _ in range(nb_col)] » ?

Parce que l'affectation uneliste[x] est destructive (remplace une valeur
mais ne change pas la liste), alors que l'affectation à une chaine de
caractères ne l'est pas (la chaine originale continue d'exister). En
clair, quand tu écris:
uneliste[0] = "hello"
uneliste[0] = "world"
la second affectation 1) modifie la liste "uneliste", mais 2) ne modifie
pas la chaine "hello" (d'ailleurs, il n'y a aucun moyen de changer une
chaine de caractères). Si tu avais:
uneliste[0] = "hello"
uneliste[1] = uneliste[0]
uneliste[0] = "world"
il y a toujours la chaine "hello" dans uneliste[1].
Pour ton exemple, quand tu écris :
m = [['.'] * col] * row
tu fais successivement :
1. création d'une liste de longeur col contenant des '.' (en fait
référant tous la chaine '.')
2. création d'une liste de longueurs row dont tous les éléme nts pointent
sur la liste précédente
En clair, il n'y a pas col*row chaines de caractères, mais seulement
une seule. Il n'y a pas 1+row listes, mais seulement deux. Tu peux vér ifier
cela avec
id(m[0]) == id(m[1])
qui va répondre vrai (les deux premiers éléments -- et tous les
autres -- sont en fait la meme liste).
Quand ensuite tu fais
m[i][j] = "#"
tu modifies la seule liste de chaines (qui est référencée en m[0], m[1],
etc).
Oui, ce sont des pointeurs, bien sur. Sauf que les listes sont mutables,
alors que les chaines ne le sont pas.
Si par contre, tu écris une liste en compréhension, l'expression est
réévalué pour chaque valeur de l'itérateur. Donc
['.' for i in range(col)]
devrait bien produire col chaines de caractères (mais comme les chaines
sont immuables, peut-etre qu'il y en a une seule). De la meme façon, si
tu écris :
[ ['.' for i in range(col)] for j in range(row) ]
l'expression interne ['.' for ...] est évaluée pour chaque j, et donc tu
as bien row listes différentes, donc au total col*row "cases"
différentes (et potentiellement row*col chaines différentes, mais le
compilo peut optimiser puisque les chaines sont immuables). Tu peux
mettre ce que tu veux dans une case sans affecter les autres.
-- Alain.
Avatar
Benoit Izac
Bonjour,
Le 01/08/2016 à 14:15, Alain Ketterlin a écrit dans le message
 :
Parce que l'affectation uneliste[x] est destructive (remplace une valeur
mais ne change pas la liste), alors que l'affectation à une chaine de
caractères ne l'est pas (la chaine originale continue d'exister). En
clair, quand tu écris:
uneliste[0] = "hello"
uneliste[0] = "world"
la second affectation 1) modifie la liste "uneliste", mais 2) ne modifie
pas la chaine "hello" (d'ailleurs, il n'y a aucun moyen de changer une
chaine de caractères). Si tu avais:
uneliste[0] = "hello"
uneliste[1] = uneliste[0]
uneliste[0] = "world"
il y a toujours la chaine "hello" dans uneliste[1].
Pour ton exemple, quand tu écris :
m = [['.'] * col] * row
tu fais successivement :
1. création d'une liste de longeur col contenant des '.' (en fait
référant tous la chaine '.')
2. création d'une liste de longueurs row dont tous les éléments pointent
sur la liste précédente
En clair, il n'y a pas col*row chaines de caractères, mais seulement
une seule. Il n'y a pas 1+row listes, mais seulement deux. Tu peux vérifier
cela avec
id(m[0]) == id(m[1])
qui va répondre vrai (les deux premiers éléments -- et tous les
autres -- sont en fait la meme liste).
Quand ensuite tu fais
m[i][j] = "#"
tu modifies la seule liste de chaines (qui est référencée en m[0], m[1],
etc).
Oui, ce sont des pointeurs, bien sur. Sauf que les listes sont mutables,
alors que les chaines ne le sont pas.
Si par contre, tu écris une liste en compréhension, l'expression est
réévalué pour chaque valeur de l'itérateur. Donc
['.' for i in range(col)]
devrait bien produire col chaines de caractères (mais comme les chaines
sont immuables, peut-etre qu'il y en a une seule). De la meme façon, si
tu écris :
[ ['.' for i in range(col)] for j in range(row) ]
l'expression interne ['.' for ...] est évaluée pour chaque j, et donc tu
as bien row listes différentes, donc au total col*row "cases"
différentes (et potentiellement row*col chaines différentes, mais le
compilo peut optimiser puisque les chaines sont immuables). Tu peux
mettre ce que tu veux dans une case sans affecter les autres.

Merci pour ta réponse. J'arrive à saisir le fonctionnement en jouant
avec <http://www.pythontutor.com/visualize.html> (mais je m'y perds un
peu avec entre variable de "stockage" et pointeur) :
# ce n'est pas vraiment un caractère mais une chaîne de caractère
c = '.'
# une nouvelle chaîne qui contient cinq copies de c concaténées
s = c * 5
# deux manières équivalente de créer une liste contenant cinq '.'
l = list(s)
l_ = [c] * 5
# crée une liste contenant trois copies de l concaténées (15 * '.')
l1 = l * 3
# crée une liste contenant trois éléments, chacun pointant vers l
l2 = [l] * 3
# même chose
l3 = [l for _ in range(3)]
# crée une liste contenant 3 pointeurs vers une copie de l
l4 = [l[:] for _ in range(3)]
# même chose (j'ai découvert ça en testant)
l5 = [l * 1 for _ in range(3)]
# crée une liste contenant 3 listes chacune d'elles ayant leur premier
# élément pointant sur l
l6 = [[l] for _ in range(3)]
Je m'aperçois également que list() et [] ne sont pas équivalents :
s = '.' * 5
s



'.....'
list(s)



['.', '.', '.', '.', '.']
[s]



['.....']
list((s,))



['.....']
[c for c in s]



['.', '.', '.', '.', '.']
--
Benoit Izac
Avatar
Alain Ketterlin
Benoit Izac writes:
[...]
J'arrive à saisir le fonctionnement en jouant avec
<http://www.pythontutor.com/visualize.html> (mais je m'y perds un peu
avec entre variable de "stockage" et pointeur) :

En python tous les noms sont des "pointeurs". D'autre part, une liste
contient des pointeurs, mais une chaine de caractères contient... des
caractères. La chaine peut se comporter comme une liste, mais c'est un
type différent.
# ce n'est pas vraiment un caractère mais une chaîne de caract ère
c = '.'
# une nouvelle chaîne qui contient cinq copies de c concaténà ©es
s = c * 5

Exactement. Ici ce sont les caractères qui sont dupliqués, et le
résultat est une chaine de caractères.
# deux manières équivalente de créer une liste contenant c inq '.'
l = list(s)
l_ = [c] * 5

En fait, pas tout à fait. Dans le premier cas, s est considérà © comme un
itérable, qui fournit un caractère après l'autre (on peut écrire "for x
in s: ..."), et list() va chercher un élément après l'autre. Dans le
second cas, c'est le pointeur qui est répété 5 fois (pentupl iqué ?)
# crée une liste contenant trois copies de l concaténées ( 15 * '.')
l1 = l * 3

l1 contient 15 éléments (qui sont tous '.').
# crée une liste contenant trois éléments, chacun pointant vers l
l2 = [l] * 3
# même chose
l3 = [l for _ in range(3)]

Si ici tu fais l[0] = "#", tu verras que les "trois" éléments s ont modifiés.
# crée une liste contenant 3 pointeurs vers une copie de l
l4 = [l[:] for _ in range(3)]

En fait, 3 pointeurs vers _3_ copies de l (chaque l[:] crée une nouvel le
liste). Si tu fais :
l4[0][1] = "$"
tu verras que seul le premier élément de l4 est modifié.
# même chose (j'ai découvert ça en testant)
l5 = [l * 1 for _ in range(3)]

Ah oui tiens.
# crée une liste contenant 3 listes chacune d'elles ayant leur premi er
# élément pointant sur l
l6 = [[l] for _ in range(3)]

Exactement.
Je m'aperçois également que list() et [] ne sont pas équiv alents :
s = '.' * 5
s



'.....'
list(s)



['.', '.', '.', '.', '.']
[s]



['.....']

Oui, list(s) considère que son (unique) argument est un itérable, et
donc le parcourt pour récupérer les éléments un à un, alors que [] ne
s'occupe pas de savoir ce qu'on lui passe.
-- Alain.
Avatar
Benoit Izac
Bonjour,
Le 02/08/2016 à 10:09, Alain Ketterlin a écrit dans le message
 :
J'arrive à saisir le fonctionnement en jouant avec
<http://www.pythontutor.com/visualize.html> (mais je m'y perds un peu
avec entre variable de "stockage" et pointeur) :

En python tous les noms sont des "pointeurs". D'autre part, une liste
contient des pointeurs, mais une chaine de caractères contient... des
caractères. La chaine peut se comporter comme une liste, mais c'est un
type différent.

Ce qui me trouble, c'est que sur le site cité ci-dessus, lorsque j'écris
« s = '.' * 5 », s n'apparaît pas comme un pointeur vers un objet alors
que « l = ['.'] * 5 », on voit une liste contenant cinq fois '.' et
l qui pointe sur cette liste.
Pourquoi la représentation est-elle différente ? Je m'attends plutôt
à avoir un objet de type str() créé et s qui contient un pointeur vers
cet objet.
# ce n'est pas vraiment un caractère mais une chaîne de caractère
c = '.'
# une nouvelle chaîne qui contient cinq copies de c concaténées
s = c * 5

Exactement. Ici ce sont les caractères qui sont dupliqués, et le
résultat est une chaine de caractères.
# deux manières équivalente de créer une liste contenant cinq '.'
l = list(s)
l_ = [c] * 5

En fait, pas tout à fait. Dans le premier cas, s est considéré comme un
itérable, qui fournit un caractère après l'autre (on peut écrire "for x
in s: ..."), et list() va chercher un élément après l'autre. Dans le
second cas, c'est le pointeur qui est répété 5 fois (pentupliqué ?)

La manière de construire la liste est différente mais le résultat final
est strictement équivalent non ?
# crée une liste contenant 3 pointeurs vers une copie de l
l4 = [l[:] for _ in range(3)]

En fait, 3 pointeurs vers _3_ copies de l (chaque l[:] crée une nouvelle
liste). Si tu fais :
l4[0][1] = "$"
tu verras que seul le premier élément de l4 est modifié.

Oui, j'ai bien compris puisque c'est la structure que je cherchais
à faire (une liste dont chaque élément pointe sur une liste
indépendante).
Je m'aperçois également que list() et [] ne sont pas équivalents :
s = '.' * 5
s



'.....'
list(s)



['.', '.', '.', '.', '.']
[s]



['.....']

Oui, list(s) considère que son (unique) argument est un itérable, et
donc le parcourt pour récupérer les éléments un à un, alors que [] ne
s'occupe pas de savoir ce qu'on lui passe.

Pigé et je constate la même chose avec tuple(s)/(s,) ou set(s)/{s}.
--
Benoit Izac
Avatar
Alain Ketterlin
Benoit Izac writes:
J'arrive à saisir le fonctionnement en jouant avec
<http://www.pythontutor.com/visualize.html> (mais je m'y perds un peu
avec entre variable de "stockage" et pointeur) :

En python tous les noms sont des "pointeurs". D'autre part, une liste
contient des pointeurs, mais une chaine de caractères contient... d es
caractères. La chaine peut se comporter comme une liste, mais c'est un
type différent.

Ce qui me trouble, c'est que sur le site cité ci-dessus, lorsque j' écris
« s = '.' * 5 », s n'apparaît pas comme un poi nteur vers un objet alors
que « l = ['.'] * 5 », on voit une liste contenant cinq fois '.' et
l qui pointe sur cette liste.
Pourquoi la représentation est-elle différente ? Je m'attends p lutôt
à avoir un objet de type str() créé et s qui contient un p ointeur vers
cet objet.

En fait c'est bien cela, conceptuellement : un pointeur et un objet.
Mais j'imagine que les chaines sont traitées spécialement dans la
visualisation (peut-etre parce qu'elles sont immuables, ou qu'elles ne
sont pas considérées comme des containers tels que les listes). N e pas
en faire des objets séparés épargne des boites et des flà ¨ches
supplémentaires.
Cela dit, comme avec tous les immuables (par exemple les entiers, courts
ou longs), je pense que c'est l'implémentation qui décide si, qua nd tu
fais :
l = ['.'] * 5
il y a une ou cinq chaines. Les deux choix sont valables, et le
comportement du programme n'en dépend pas. Mais il y a bien cinq
pointeurs dans la liste, que tu peux rediriger individuellement.
-- Alain.
Avatar
Benoit Izac
Bonjour,
Le 02/08/2016 à 11:33, Alain Ketterlin a écrit dans le message
 :
J'arrive à saisir le fonctionnement en jouant avec
<http://www.pythontutor.com/visualize.html> (mais je m'y perds un peu
avec entre variable de "stockage" et pointeur) :

En python tous les noms sont des "pointeurs". D'autre part, une liste
contient des pointeurs, mais une chaine de caractères contient... des
caractères. La chaine peut se comporter comme une liste, mais c'est un
type différent.

Ce qui me trouble, c'est que sur le site cité ci-dessus, lorsque j'écris
« s = '.' * 5 », s n'apparaît pas comme un pointeur vers un objet alors
que « l = ['.'] * 5 », on voit une liste contenant cinq fois '.' et
l qui pointe sur cette liste.
Pourquoi la représentation est-elle différente ? Je m'attends plutôt
à avoir un objet de type str() créé et s qui contient un pointeur vers
cet objet.

En fait c'est bien cela, conceptuellement : un pointeur et un objet.
Mais j'imagine que les chaines sont traitées spécialement dans la
visualisation (peut-etre parce qu'elles sont immuables, ou qu'elles ne
sont pas considérées comme des containers tels que les listes). Ne pas
en faire des objets séparés épargne des boites et des flèches
supplémentaires.
Cela dit, comme avec tous les immuables (par exemple les entiers, courts
ou longs), je pense que c'est l'implémentation qui décide si, quand tu
fais :
l = ['.'] * 5
il y a une ou cinq chaines. Les deux choix sont valables, et le
comportement du programme n'en dépend pas. Mais il y a bien cinq
pointeurs dans la liste, que tu peux rediriger individuellement.

C'est ce que je viens de constater, ça n'a pas d'importance puisque
l'objet est non modifiable, on peut simplement modifier la variable qui
pointait vers lui pour la faire pointer vers un autre objet.
l = [[(0,0)] * 5 for _ in range(3)]
ne crée qu'un seul objet « (0, 0) » (mais j'ai bien compris qu'il
pourrait en créer 3 ou 15, ça ne changerait rien).
En revanche :
l2 = [[[0,0]] * 5 for _ in range(3)]
crée trois fois (un objets « [0, 0] » et une liste de 5 éléments
pointant chacun sur cet objet). C'est le "vrai" comportement sauf que
pour les objets immuables on (peut) optimiser en économisant la
recréation de l'objet puisque qu'il n'est pas modifiable.
Merci Alain, tout est clair, il ne me reste plus qu'à pratiquer pour que
ça rentre une bonne fois pour toute.
--
Benoit Izac