Bonjour,
Entre :
&Code:a = [0,0]
Quelle affectation est la plus optimale?Code:a = [0]*2
Version imprimable
Bonjour,
Entre :
&Code:a = [0,0]
Quelle affectation est la plus optimale?Code:a = [0]*2
Salut,
Quand vous écrivez a = machin, l'assignation/affectation de l'objet machin à a n'a aucune raison de dépendre de la nature de l'objet machin.
Par contre la différence entre [0, 0], liste sous forme littérale contenant deux zéros, et [0]*2 qui fabrique une liste semblable à partir d'une liste réduite à 1 seul zéro saute au yeux: il suffit de regarder les opérations effectuées.
L'affectation coûte la même chose, la création/construction de l'objet à assigner, c'est autre chose. Ici, s'il y a une différence, elle sera "epsilonnesque" et n'aura d'importance que si l'opération sera répétée plusieurs millions de fois.
Maintenant, vous vous rendez aussi compte que fabriquer une liste à N éléments nuls sera possible avec [0]*N alors que sans connaitre N, impossible d'écrire çà sous forme littérale.
- W
Notez que l'affectation
est très dangereuse (et souvent ce n'est pas ce qu'on veut) si A n'est pas 0 comme dans votre exemple, mais plutôt un objet, ou plus généralement un mutable (une liste par exemple). Dans ces cas là, si on ne connait pas N à l'avance on devra instancier comme ceciCode:a = [A]*N
Code:a = [A for _ in range(N)]
suivit de:Code:A = []
produira le même résultat que:Code:a = [ A for _ in range(N)]
car j'assigne dans les 2 cas N fois la référence au même objet A.Code:a = [ A ] * N
Et çà n'a rien à voir avec:
est identique àCode:a = [ [] for _ in range(N)]
dans le premier cas, je crée un objet liste vide [] à chaque itération alors que dans le 2nd je le recopie N fois.Code:a = [ [] ] * N
Subtil?
- W
Effectivement, j'ai écrit comme cela (avec A) pour avoir une écriture plus compact, mais ca peut prêter à confusion.
Donc pour être clair, oui, dans ce que j'ai écrit il faut bien comprendre (pour l'exemple disons que A vaut [1,2,3]):
.vs.Code:a = [ [1,2,3] for _ in range(N)]
(qui produisent des résultats différents)Code:a = [ [1,2,3] ] * N
et non pas
puisCode:A=[1,2,3]
.vs.Code:a = [ A for _ in range(N)]
qui là en effet produisent la même chose.Code:a = [ A ] * N
Donc si je récapitule:
IDEM : À a & b est assignées 2 fois la référence de tt, soit:Code:
1
2
3
4 tt = [0,1] n =2 a = [tt for _ in range(n)] b = [tt] * n
Pour:Code:
1
2 [[0, 1], [0, 1]] [[0, 1], [0, 1]]
IDENTIQUE : c est construit par la référence de tt à chaque itération alors que la référence de tt est copié dans d 2 fois, soit:Code:
1
2 c = [[0,1] for _ in range(n)] d = [[0,1]] * n
Même procédé que pour c & d mais avec un résultat différent:Code:
1
2 [[0, 1], [0, 1]] [[0, 1], [0, 1]]
Soit:Code:
1
2 e = [[tt] for _ in range(n)] f = [[tt]] * n
Ai-je bien compris?Code:
1
2 [[[0, 1]], [[0, 1]]] [[[0, 1]], [[0, 1]]]
Non, je crois bien que vous ayez compris à l'envers !
Exécutez ces codes, et observer attentivement :
puis celui ci, qui donneras le meme résultatCode:
1
2
3
4
5
6
7
8
9
10 tt = [0,1] n =2 a = [tt for _ in range(n)] print(a) a[0][0] = 17 print(a) print(tt) tt[1]=100 print(a) print(tt)
Finalement externalisé tt n'a pas vraiment d'importance puisque le code suivant produit le même comportementCode:
1
2
3
4
5
6
7
8
9
10 tt = [0,1] n =2 a = [tt]*n print(a) a[0][0] = 17 print(a) print(tt) tt[1]=100 print(a) print(tt)
Mais ce dernier code, qui ne fait pas de modification collatérale, lui est différent, et est en général ce que l'on veut faire contrairement à tous les codes précédentsCode:
1
2
3
4
5 n =2 a = [[0,1]]*n print(a) a[0][0] = 17 print(a)
Je vous laisse observez ce qui se passe lorsque vous éxécutez tout cela.Code:
1
2
3
4
5 n =2 a = [[0,1] for _ in range(n) ] print(a) a[0][0] = 17 print(a)
Code:
1
2
3
4
5 n =2 a = [[0,1] for _ in range(n) ] print(a) a[0][0] = 17 print(a)
Ok, je pense que j'ai saisi.
pour :
a est construit à chaque itération.Code:
1
2
3
4
5 n =2 a = [[0,1] for _ in range(n) ] print(a) a[0][0] = 17 print(a)
Ce qui implique qu'à la première itération à l'indice 0 de a et à l'indice 0 de l'objet contenu dans a sera assigné 17 soit [17,1]. À la seconde itération comme aucune autre instruction est donnée, l'objet est construit dans sa valeur initiale soit [0,1]. On a donc:
Pour tous les autres blocs de code l'objet contenu dans a est copié n fois.Code:[[17, 1], [0, 1]]
Ce qui implique que si on assigne une nouvelle valeur à l'indice 0 de a et à l'indice 0 de l'objet contenu dans a on change la valeur de référence. On aura donc:
Merci beaucoup a vous deux pour cette excellente leçon :ccool:Code:[[17, 1], [17, 1]]
Ceci étant,
Pourquoi cette affectation est très dangereuse?Citation:
Notez que l'affectation
Code :
a = [A]*N
est très dangereuse
Bonjour
Ce n'est pas l'affectation en elle-même qui est dangereuse mais, et vous l'avez bien compris, que le résultat dépend de la nature de "A".
Si "A" est un simple int/double ça ira mais si "A" est un truc plus complexe et surtout mutable (liste, dico) alors il est simplement référencé N fois.
Et ensuite modifier a[0] reviendra à modifier une unique référence ce qui se répercute alors dans tous les a[x].
Et donc ce code suivant
produira le résultat suivantCode:
1
2
3
4 a=[[0,]] * 5 print(a) a[2][0]=18 print(a)
Code:
1
2 [[0], [0], [0], [0], [0]] [[18], [18], [18], [18], [18]]
Non. C'est 2 lignes là :
construisent chacune une liste.Code:
1
2 a=[[0,1] ] * 5 b=[[0,1] for _ in range(5) ]
Faire ca ensuite
ce n'est pas faire une 2eme itération dans la construction de votre liste. Non la liste est déjà créée. Faire ca, c'est assigner une nouvelle valeur a un élément de la liste (élément d'une sous liste pour être plus précis).Code:a[2][0]=18
En fait non.
ici [0,1] est littéralement copié 5 fois, et vous obtenez à la fin 5 copies indépendantesCode:b=[[0,1] for _ in range(5) ]
ici [0,1] n'est créé qu'une seul fois, et on met 5 références vers cette même liste. Ce qui explique pourquoi si vous en modifiez un, il sont tous modifié: il pointe tous sur la même liste d'origine.Code:a=[[0,1] ]*5
Merci à vous trois pour votre patience.
D'accord... donc,
avec en test,Code:
1
2
3
4 tt = [0,1] a1 = [[0,1]]*5 a2 = [tt for _ in range(5)] a3 = [tt]*5
ici [0,1] & tt sont créés 1 seule fois chacun pour 5 références vers 1 liste.Code:
1
2
3 a1[2][0]=18 a2[2][0]=18 a3[2][0]=18
Conséquence : la modification d'une seule référence entraîne la modification de toutes les autres car elles pointent toutes vers la même liste d'origine.
Cette liste d'origine étant [0,1] & tt selon l'exemple.
avec en test,Code:
1
2 b1 = [[0,1] for _ in range(5)] b2 = [0,1]*5
ici [0,1] est copié 5 fois chacun pour 5 copies indépendantes dans 1 liste.Code:
1
2 b1[2][0]=18 b2[4]=18
Ok, mais je suis incapable de lire une différence entre les assignations des a et des b même si je suis prête à admettre ces explications. Entre a2 et b1 par exemple il n'y a rien qui me saute aux yeux et encore moins entre a1 et b2, comment faites-vous pour voir ces différences?
On s'est déjà fait avoir assez souvent pour déceler ces différences et on a appris a y mettre des mots dessus:
Ici vous voyez (ou pas) qu'on crée 5 fois une référence au même objet que celui assigné à tt.Code:
1
2 tt = [0,1] a2 = [tt for _ in range(5)]
Par contre ici:
La variable a été remplacé par l'expression littérale [0, 1] qui dit crée une liste avec les nombres 0 et 1 dedans. Répété 5 fois, on aura 5 objets égaux mais différents.Code:b1 = [[0,1] for _ in range(5)]
- W
Attention à la syntaxe. Je présume que tu veux rendre b2 similaire à b1 pour tes tests. Or [0,1]*5 va copier 5 fois d'affilée la suite "0, 1" dans un simple tableau => b2=[0, 1, 0, 1, 0, 1, 0, 1, 0, 1] et ne correspondra absolument pas à b1 qui, lui, est un vrai tableau 2D.
Pour rendre b2 similaire à b1 il faut écrire b2=[[0, 1],]*5 indiquant qu'on veut créer un tableau contenant 5 fois le couple [0, 1]. Et la virgule avant le dernier crochet n'est pas obligatoire pour une liste mais l'est pour un tuple (ce n'est pas la parenthèse qui fait le tuple mais la virgule ainsi (0) est un simple int tandis que (0,) est lui un vrai tuple) donc perso je la mets tout le temps pour rester cohérent dans ma syntaxe.
Ben déjà on sait (et c'est bien ancré) que répéter un truc complexe c'est super dangereux donc quand on se lance, on fait super gaffe. Ensuite si on se trompe malgré tout, le programme part en cacahouette dès le premier test, donc on met un print() et on voit immédiatement qu'on s'est gauffrés. Et puis bon, ce n'est pas non plus une syntaxe dont on a besoin tous les 4 matins...