[Python]Modifier valeurs tableau

Fermé
x0oD-x/ - 30 oct. 2010 à 14:41
 christianf - 4 mai 2017 à 02:12
Bonjour,

Je débute en python et je souhaiterais créer un tableau ou chaque caractère égal à 0.

par exemple ce code :
for i in range(20):
   grille.append(list(range(20)))
for element in grille:
   print(element)

m'affiche 20x : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

et j'aimerais à la place qu'il m'affiche
20x : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

j'arrive pas trop à me visualiser comment faire :/

merci d'avance

A voir également:

2 réponses

heyquem Messages postés 759 Date d'inscription mercredi 17 juin 2009 Statut Membre Dernière intervention 29 décembre 2013 130
Modifié par heyquem le 31/10/2010 à 23:45
Je remplace 20 par 4.


x0oD-x/ veut que l'objet grille ait la valeur
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

or grille=[0]*4 donne la valeur
[0, 0, 0, 0]



À première vue, grille = 4*[ 4*[0] ] semble faire l'affaire.

Mais il faut se méfier d'une conséquence de la nature du "data model" de Python, mise en évidence par le code suivant:

grille = 4*[ 4*[0] ]  
grille[0][2] = 7 
print grille

qui affiche
[[0, 0, 7, 0], [0, 0, 7, 0], [0, 0, 7, 0], [0, 0, 7, 0]]
au lieu du résultat attendu
[[0, 0, 7, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]


Pourquoi ce résultat ?
Parce que:

1) une liste collationne en fait des adresses et non pas des objets

2) une liste Python est un objet mutable, c'est à dire qu'un de ses éléments peut changer sans que sa localisation en mémoire change.

3) si on ne fait pas attention à la façon de copier une liste, on peut se retrouver à manipuler des listes en les croyant différentes alors qu'elles ne sont en réalité que le même objet



Dit comme ça, ça reste obscur.
Il faut donc ajouter quelques explications à cette réponse courte.




------------------------------------------

Prenons ça à partir du début,
regardons ce qui se passe quand on manipule un objet non mutable et qu'on veut changer de valeur.
Prenons un entier comme exemple:

En Python, l'instruction X = 88 provoque les événements suivants:
- un objet "entier 88" est créé en mémoire
- une variable enregistre son adresse
- l'identifiant X est attaché à cette variable

Ainsi, en exprimant les choses de façon elliptique, on dira que X est le nom de l'objet "entier 88". Mais il faut bien réaliser et garder à l'esprit que X ne repère pas directement l'objet "entier 88".

En toute rigueur, X repère directement une variable contenant l'adresse de l'objet "entier 88", et seulement indirectement l'objet "entier 88".
C'est le "data model" de Python.

"Indirectement": cela veut dire que lorsque l'interpréteur Python rencontre l'identifiant X dans un code, l' "execution model" de Python est tel que l'interpréteur remplace X par la valeur de l'objet "entier 88", et non par la valeur de la variable repérée directement par X : heureusement puisque cette dernière valeur est une adresse en mémoire qui ne sert qu'à permettre à l'interpréteur de localiser l'objet "entier 88" en mémoire.

En C++, une variable qui contient une adresse est appelée un pointeur.
En Python, pour certaines raisons, une variable qui contient une adresse est appelée une référence.

On dira que
X est l'identifiant direct de la variable de type 'référence' qui contient l'adresse de l'objet pointé par cette référence
et que
X est l'identifiant indirect de cet objet.


C'est seulement en employant rigoureusement des notions claires qu'on peut espérer comprendre quelque chose au "data model" et à l' "execution model" de Python, qui lui sont spécifiques.





Soit maintenant
Y = 88
L'interpréteur Python ne se fatigue pas à créer un nouvel objet "entier 88" à une autre adresse que le premier. Il en a un sous la main, alors il crée simplement une deuxième référence avec le nom Y dans laquelle il place la même valeur que dans X, à savoir l'adresse de l'objet "entier 88".

L'interpréteur Python gère TOUS les objets en existence dans un programme, il n'a donc aucune difficulté à savoir s'il a déjà créé ou non un certain objet.





Soit maintenant
X = 2000

Les entiers sont des objets non mutables.
Concrètement, un objet non mutable, cela veut dire que l'agrégat de bits qui constitue en mémoire un objet non mutable ne peut pas subir de modifications des valeurs de certains de ses bits pour se mettre à représenter une autre valeur, même du même type.

On peut trouver ça bizarre, mais c'est ainsi: en Python, les agrégats de bits qui représentent physiquement en mémoire des entités conceptuelles sont tous des objets, avec une nature et des règles propres, et non pas des variables. C'est comme ça.

La seule solution pour que X se mette à représenter l'entier 2000 dans un code est la suivante:
- un objet "entier 2000" est créé en mémoire, ailleurs qu'à l'emplacement de l'objet "entier 88"
- la référence X prend pour nouvelle valeur l'adresse de ce nouvel objet

De son coté, la référence Y continue de pointer vers l'objet "entier 88".



-----------------------------------

Regardons maintenant ce qui se passe quand on veut changer la valeur d'un objet mutable.

Tous les objets mutables sont des objets composites.
(Du moins, je crois. Si ce n'est pas le cas, je serais fort intéressé d'apprender l'existence d'un objet mutable qui ne soit pas composite.)

Un objet composite est composé de plusieurs autres objets, ces derniers pouvant être mutables ou non.

Les objets non composites sont des objets de type basique: un entier, un caractère, un booléen.... Tous les objets de type basique sont, à ma connaissance, non mutables.

Comme exemple d'objet composite: une liste li = [0,10,20,30,40] est composée des objets basiques 0, 10, 20, 30 et 40.


Nota bene:
- les objets de type basique sont tous non mutables
- les objets mutables sont tous des objets composites.
Mais les tuples sont un cas particulier qui perturbe la netteté de la délimitation:
- bien qu'ils soient composites, ils sont quand même non mutables
ou si l'on préfère
- bien qu'ils soient non mutables, il sont quand même composites


Quand j'ai écrit plus haut:
une liste li = [0,10,20,30,40] est composée des objets basiques 0, 10, 20, 30 et 40.
il faut comprendre que c'est un raccourci.

En réalité, les éléments 0, 10, 20, 30 et 40 de la liste conceptuelle [0,10,20,30,40] sont représentés par des objets à part entière, c'est à dire des "entier 0", "entier 10", "entier 20", "entier 30", "entier 40" indépendants avec des adresses propres. Ces objets ne sont pas rassemblés dans un objet plus vaste qui aurait pour nom li: l'objet li n'est pas un agrégat de bits contigus dans lesquels les objets "entier 0", "entier 10", "entier 20", "entier 30", "entier 40" seraient représentés serrés les uns à coté des autres.

En réalité, l'objet "entier 0" est quelque part dans la mémoire, l'objet "entier 10" est quelque part n'importe où ailleurs dans la mémoire, l'objet "entier 20" est quelque part n'importe où encore ailleurs dans la mémoire,etc, et la liste li ne rassemble en fait pas directement les objets "entier 0", "entier 10", "entier 20", "entier 30", "entier 40", elle rassemble directement leurs ADRESSES.

Et là encore, lors d'une exécution, l'interpréteur Python "sait" que lorsqu'il rencontre la liste li, il doit aller chercher les valeurs 0, 10, 20, 30, 40 dans les objets "entier 0", "entier 10", "entier 20", "entier 30", "entier 40" qu'il localise grâce aux adresses qui sont les éléments réels directs de la liste li.



Vérifions un peu ces affirmations.

Le code suivant

print ('a,b,c = 0, 10, 20') 
print ("    I = ['tut','tot']") 
print ("    J = ['w','e','b']") 
print ("    K = ['C','P','U']") 
print ("li = [0,10,c,I,J,['C','P','U'],50]") 
a,b,c = 0, 10, 20 
I = ['tut','tot'] 
J = ['w','e','b'] 
K = ['C','P','U'] 
li = [0,10,c,I,J,['C','P','U'],50] 
print '\nids de a,b,c,I,J,K :\n',[id(a),id(b),id(c),id(I),id(J),id(K)] 
print 'map(id,li) :\n',map(id,li) 
print 'li :\n',li 

c = 11 

I = 221 
J[0] = 'zinc' 
K[2] = 'BZTR' 
li[6] = 122 
print "\nc = 11\nI = 221\nJ[0] = 'zinc'\nK[2] = 'BZTR'\nli[5] = 122" 
print '\nids de a,b,c,I,J,K :\n',[id(a),id(b),id(c),id(I),id(J),id(K)] 
print 'map(id,li) :\n',map(id,li) 
print 'li :\n',li 

print '\nI =',I,id(I) 
print 'J =',J,id(J) 
print 'K =',K,id(K)


donne

a,b,c = 0, 10, 20 
    I = ['tut','tot'] 
    J = ['w','e','b'] 
    K = ['C','P','U'] 
li = [0,10,c,I,J,['C','P','U'],50] 

ids de a,b,c,I,J,K : 
[8776684, 8776564, 8776444, 17423496, 17541360, 17515184] 
map(id,li) : 
[8776684, 8776564, 8776444, 17423496, 17541360, 17541800, 8776084] 
li : 
[0, 10, 20, ['tut', 'tot'], ['w', 'e', 'b'], ['C', 'P', 'U'], 50] 

c = 11 
I = 221 
J[0] = 'zinc' 
K[2] = 'BZTR' 
li[5] = 122 

ids de a,b,c,I,J,K : 
[8776684, 8776564, 8776552, 8778000, 17541360, 17515184] 
map(id,li) : 
[8776684, 8776564, 8776444, 17423496, 17541360, 17541800, 8777204] 
li : 
[0, 10, 20, ['tut', 'tot'], ['zinc', 'e', 'b'], ['C', 'P', 'U'], 122] 

I = 221 8778000 
J = ['zinc', 'e', 'b'] 17541360 
K = ['C', 'P', 'BZTR'] 17515184



L'instruction
a,b,c = 0, 10, 20
crée 3 objets a,b,c.
l'adresse de l'objet "entier 0" en mémoire est 8776684
l'adresse de l'objet "entier 10" en mémoire est 8776564
l'adresse de l'objet "entier 20" en mémoire est 8776444

Ensuite, la liste li est créée. Cette création ne fait pas allusion aux objets a,b apparus dans l'instruction précédente.
Pourtant, quand on fait afficher non pas les valeurs des éléments de li mais leurs adresses, on obtient les mêmes adresses pour les deux premiers éléments 0 et 10 que pour a et b qui valent 0 et 10:
[8776684, 8776564,...

Ceci prouve que l'interpréteur Python crée une représentation, pour chacun des deux premiers éléments 0 et 10 de la liste li, non pas en créant un objet dédié mais en recourant aux objets déjà créés, et que pour y faire référence il utilise les adresses contenues dans leurs références a et b, c'est aussi simple que ça.

Les deux premiers éléments de li sont donc bien en réalité les adresses des deux objets "entier 0" et "entier 10".
Mais quand une instruction print li demande l'affichage de la valeur de li, l'interpréteur sait qu'il s'agit d'adresses et il va chercher les valeurs à afficher dans les objets situés à ces adresses.



--------------------------------------------

Les autres observations s'interprètent bien, elles aussi.

c = 11 crée un nouvel objet "entier 11" et la référence c prend une autre valeur 8776552
On pourrait penser que l'élément li[2] , qui a été défini comme étant c, va alors changer lui aussi de valeur.
Mais ce n'est pas le cas parce que l'élément li[2] a beau avoir été défini comme étant c, il a été en réalité enregistré comme la valeur dans la référence c, c'est à dire l'ADRESSE de l'objet pointé par c.
L'élément li[2] pointe donc toujours vers l'objet "ntier 20" mme si c ne pointe plus vers lui. L'adresse dans la référence c peut donc bien changer, la liste li n'en ressent aucune conséquence tant que la valeur li[2] n'est pas directement modifiée.


C'est la même chose qui se passe concernant I bien qu'il s'agisse cette fois du remplacement d'une liste par un entier. Le fait que l'objet remplacé soit cette fois mutable ne change rien à l'affaire.
li[3] a enregistré l'adresse de I'objet "liste ['tut','tot']" et la garde même quand l'instruction I = 221 fait pointer I vers un autre objet c'est à dire que la valeur contenue dans la référence I change; li[3] continue donc de pointer vers l'objet liste initial à l'adresse 17423496 tandis que c comporte maintenant l'adresse 8778000


Le cas de J est intéressant.
J est définie par J = ['w','e','b'] et comme 5ième élément de li.
J[0] = 'zinc' change le premier élément de J. Comme J est mutable, ce changement se fait "en place", c'est à dire sans créer un autre objet à une autre adresse pour représenter la nouvelle valeur: cela signifie que l'interpréteur Python arrive à modifier certains bits de l'objet J à l'endroit de la mémoire où ce dernier se trouve, pour obtenir une valeur 'zinc' pour son premier élément au lieu de 'w'.
L'objet situé à l'adresse id(J) qu'a enregistrée li[4] étant modifié, la valeur de la liste li se trouve cette fois modifiée par la modification de J
li à la fin:
[0, 10, 20, ['tut', 'tot'], ['zinc', 'e', 'b'], 122, 50]
Mais les adresses id(J) et id(li[4]) restent égales à 17541360 tout le long.


Le cas de K est aussi intéressant.
K est définie par K = ['C','P','U'] et li[5] est défini comme étant aussi ['C','P','U']
On pourrait donc penser qu'il va se passer la même chose que ce qu'on a vu quand on a fait X = 88 puis Y = 88 et qu'on a constaté que id(X) et id(Y) étaient identiques:
l'interpréteur Python devrait placer en li[5] l'adresse de l'objet K déjà défini avec une valeur identique.
Mais ce n'est pas le cas.
J'ai lu je ne sais plus où que lorsqu'on crée plusieurs objets composites avec la même valeur, Python crée en réalité effectivement des objets différents, c'est à dire avec des adresses différentes.

F = [23,679,82] 
G = [23,679,82] 
H = [23,679,82] 
print (id(F)) 
print (id(G)) 
print (id(H))

donne

17541880 
17540920 
17514824

Ainsi
K = ['C','P','U']
puis
li = [0,10,c,I,J,['C','P','U'],50]
ne font pas de K et de l'objet pointé par li[5] le même objet à une seule adresse. Dès le départ id(K) est 17515184 et id(li[5]) est 17541800
La valeur de K change ensuite mais les adresses ne changent pas et la valeur de l'objet pointé par li[5] ne change pas.



Pour li[6] qui vaut 50 au début, c'est un entier non mutable. Faire li[6] = 122 provoque donc la création d'un objet "entier 122" quelque part dans la mémoire vive et le remplacement de l'adresse de "entier 50" par l'adresse de "entier 122" à la position li[6]
Dans un tel cas d'un objet non mutable comme élément d'une liste, changer la valeur de l'élément implique que l'on change en fait l'adresse enregistrée à la position de l'élément puisque l'objet ne peut pas changer lui-même. D'autres positions peuvent avoir enregistré la même adresse, c'est à dire pointer vers ce même objet, mais ces autres adresses en d'autres positions de la liste ne changent absolument pas quand l'adresse enregistrée à la position de l'élément considéré change pour pointer vers un autre objet.
Il en va tout autrement quand l'élément est un objet mutable. Les autres adresses aux autres positions restent toujours aussi intangibles, mais cette fois changer la valeur d'un élément mutable dans une liste peut se faire sans modifier le moins du monde sa propre adresse enregistrée.
On peut donc se retrouver avec des objets mutables identiques, et qui se modifient de façons identiques, en plusieurs positions d'une liste tout simplement parce qu'ils sont en fait le même objet mutable repéré par une adresse unique qui se trouve enregistrée à ces plusieurs positions.
C'est donc bien, comme je le disais au début en 2) , parce qu'une liste est mutable qu'elle peut, en tant qu'élément d'une liste la contenant, donner lieu au problème de la diffusion, d'une modification l'affectant, à d'autres éléments de cette liste s'ils se réfèrent au même objet.






-----------------------------------

On peut enfin comprendre pourquoi grille = 4*[ 4*[0] ] ne permet pas d'obtenir une liste de listes correcte, c'est à dire une liste de listes dans laquelle la modification d'une valeur à un endroit ne se répercute pas à un autre endroit dans la liste de listes.

La portion 4*[0] de cette instruction crée un objet [0, 0, 0, 0] à la volée, c'est à dire sans identifiant indirect. Cependant, en interne, une référence est nécessairement créée par l'interpréteur pour repérer son adresse. Car ensuite, l'interpréteur réutilise cette adresse pour construire l'objet grille.
En effet l'expression 4*[truc] équivaut à
liste = [truc] 
for i in range(3): 
    liste.extend(truc)
L'interpréteur a donc besoin de l'adresse de truc pour construire grille en étendant à chaque fois avec le même objet truc.

Cependant, cette construction consiste non pas à étendre avec un vrai objet truc, mais, comme on l'a vu en long et en large, à enregistrer plusieurs fois des adresses. Et comme l'interpréteur étend à chaque fois avec le même objet truc qui vaut ici 4*[0] , il enregistre en réalité 4 fois son adresse.
La preuve par l'image:

xd = 4*[0] 
print 'xd :',xd,'  id(xd) :',id(xd) 
print 'on fait Lxd = 4*[xd]' 
Lxd = 4*[xd] 
print 'map(id,Lxd) :',map(id,Lxd) 
print 'Lxd :',Lxd 
print "on fait Lxd[1][2] = 'u'" 
Lxd[1][2] = 'u' 
print 'Lxd :',Lxd

donne

xd : [0, 0, 0, 0] id(xd) : 17542440
on fait Lxd = 4*[xd]
map(id,Lxd) : [17542440, 17542440, 17542440, 17542440]
Lxd : [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
on fait Lxd[1][2] = 'u'
Lxd : [[0, 0, 'u', 0], [0, 0, 'u', 0], [0, 0, 'u', 0], [0, 0, 'u', 0]]

Si on définit xd = [0, 0 , 0, 0] , le résultat est identique. Le problème vient du premier 4*... qui étend à chaque fois avec le même objet.


-----------------------------------

Voici la solution:

xd = 4*[0] 
SXD = [ xd[:] for _ in list(range(4)) ] 
print (SXD) 
print (map(id,SXD))


xd[:] crée une VRAIE copie de xd, c'est à dire un autre objet (= à une autre adresse) avec la même valeur que celle de xd
19
fiddy Messages postés 11069 Date d'inscription samedi 5 mai 2007 Statut Contributeur Dernière intervention 23 avril 2022 1 835
Modifié par fiddy le 31/10/2010 à 23:53
Quel roman !
J'ai pas tout lu mais cela semble très intéressant.
Effectivement, j'ai lu son post trop vite, mais vous avez rattrapé le coup.

Bravo pour cette patience ;-))

Cordialement,
0
Merci !!!
0
paulmea69 Messages postés 1 Date d'inscription mardi 22 septembre 2015 Statut Membre Dernière intervention 22 septembre 2015
22 sept. 2015 à 21:57
Bonjour j'aurais une question a propos de cette instruction : SXD = [ xd[:] for _ in list(range(4)) ]
Elle marche tres bien mais je ne comprend vraiment pas comment cela fonctionne surtout le >>for _ in list(range(4))>> en sachant qu'en remplaçant par >>for i in range(line)>> j'aboutie au meme resultat
merci :)
0
créer une nouvelle liste pour fixer ses valeurs
et éviter que des evolutions de variables ne se propagent ensuite dans la liste.
c'était l'idée qui me manquait.
merci
0
fiddy Messages postés 11069 Date d'inscription samedi 5 mai 2007 Statut Contributeur Dernière intervention 23 avril 2022 1 835
30 oct. 2010 à 15:29
Bonjour,
Tout simplement : grille=[0]*20

Cdlt,
1