Quelle est la différence entre i = i + 1 et i += 1 en un "pour" boucle? [dupliquer]
Cette question a déjà une réponse ici:
J'ai découvert une chose curieuse aujourd'hui et je me demandais si quelqu'un pouvait faire la lumière sur la différence ici?
import numpy as np
A = np.arange(12).reshape(4,3)
for a in A:
a = a + 1
B = np.arange(12).reshape(4,3)
for b in B:
b += 1
Après l'exécution de chaque for
boucle, A
n'a pas changé, mais B
a ajouté chaque élément. J'utilise en fait la version B
pour écrire dans un tableau NumPy initialisé dans une boucle for
.
6 réponses
La différence est que l'on modifie la structure de données elle-même (opération sur place) b += 1
tandis que l'autre ne fait que réaffecter la variable a = a + 1
.
Juste pour l'exhaustivité:
x += y
est-ce que ne fait pas toujours une opération sur place, il y a (au moins) trois exceptions:
Si
x
n'implémente pas une méthode__iadd__
alors l'instructionx += y
est juste un raccourci pourx = x + y
. Ce serait le cas six
était quelque chose comme unint
.Si
__iadd__
renvoieNotImplemented
, Python revient àx = x + y
.La méthode
__iadd__
pourrait théoriquement être implémentée pour ne pas fonctionner en place. Ce serait vraiment bizarre de le faire.
Comme il arrive, vos b
s sont numpy.ndarray
s qui implémente __iadd__
et se retournent afin que votre deuxième boucle modifie le tableau d'origine en place.
Vous pouvez en savoir plus à ce sujet dans la documentation Python de " Emulating Numeric Les Types".
Ces
__i*__
] méthodes sont appelées à mettre en œuvre l'augmentation des attributions de l'arithmétique (+=
,-=
,*=
,@=
,/=
,//=
,%=
,**=
,<<=
,>>=
,&=
,^=
,|=
). Ces méthodes devraient essayer de faire l'opération sur place (en modifiant self) et renvoyer le résultat (qui pourrait être, mais ne doit pas être, self). Si une méthode spécifique n'est pas définie, l'affectation augmentée revient aux méthodes normales. Par exemple, si x est un instance d'une classe avec une méthode__iadd__()
, {[2] } est équivalente àx = x.__iadd__(y)
. Sinon,x.__add__(y)
ety.__radd__(x)
sont considérées, à l'évaluation dex + y
. Dans certaines situations, l'affectation augmentée peut entraîner des erreurs inattendues (voir pourquoia_tuple[i] += ["item"]
déclenche-t-elle une exception lorsque l'addition fonctionne?), mais ce comportement fait en fait partie du modèle de données.
Dans le premier exemple, vous réaffectez la variable a
, tandis que dans le second, vous modifiez les données sur place, en utilisant l'opérateur +=
.
Voir la section sur 7.2.1. Instructions d'affectation augmentées :
Une expression d'affectation augmentée comme
x += 1
peut être réécrite commex = x + 1
pour obtenir un effet similaire, mais pas exactement égal. Dans la version augmentée, x n'est évalué qu'une seule fois. Aussi, lorsque cela est possible, l'opération est effectuée in-place , ce qui signifie que plutôt que de créer un nouvel objet et de l'affecter à la cible, l'ancien objet est modifié à la place.
+=
appels de l'opérateur __iadd__
. Cette fonction effectue le changement sur place, et seulement après son exécution, le résultat est remis à l'objet sur lequel vous "appliquez" le +=
.
__add__
d'autre part prend les paramètres et renvoie leur somme (sans les modifier).
Comme déjà souligné, b += 1
met à jour b
en place, tandis que a = a + 1
calcule a + 1
et assigne ensuite le nom a
au résultat (maintenant a
ne fait plus référence à une ligne de A
).
Pour comprendre correctement l'opérateur +=
, nous devons également comprendre le concept d'objetsmutables versusimmuables . Considérez ce qui se passe quand nous laissons de côté le .reshape
:
C = np.arange(12)
for c in C:
c += 1
print(C) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
, Nous voyons que C
est pas mise à jour, ce qui signifie que c += 1
et c = c + 1
sont équivalents. C'est parce que maintenant C
est un tableau 1D (C.ndim == 1
), et donc, lors de l'itération sur C
, chaque élément entier est retiré et est affecté à c
.
Maintenant en Python, les entiers sont immuables, ce qui signifie que les mises à jour sur place ne sont pas autorisées, transformant efficacement c += 1
en c = c + 1
, où c
fait maintenant référence à un nouveau entier, non couplé à C
en aucune façon. Lorsque vous effectuez une boucle sur les tableaux remodelés, des lignes entières (np.ndarray
s) sont affectées à b
(et a
) à un temps, qui sont des objets mutables , ce qui signifie que vous êtes autorisé à coller de nouveaux entiers à volonté, ce qui se produit lorsque vous faites a += 1
.
Il convient de mentionner que bien que +
et +=
soient liés comme décrit ci-dessus (et le sont généralement), tout type peut les implémenter comme il le souhaite en définissant les __add__
et __iadd__
méthodes, respectivement.
La forme courte (a += 1
) a la possibilité de modifier a
sur place, au lieu de créer un nouvel objet représentant la somme et de la relier au même nom(a = a + 1
).ainsi,la forme courte(a += 1
) est très efficace car elle n'a pas nécessairement besoin de faire une copie de a
contrairement à a = a + 1
.
Même s'ils produisent le même résultat, notez qu'ils sont différents car ce sont des opérateurs distincts: +
et +=
Tout d'abord: les variables a et b dans les boucles font référence aux objets numpy.ndarray
.
Dans la première boucle, a = a + 1
est évaluée comme suit: la __add__(self, other)
la fonction de numpy.ndarray
est appelé. Cela crée un nouvel objet et par conséquent, n'est pas modifié. Ensuite, la variable a
est définie pour faire référence au résultat.
Dans la deuxième boucle, aucun nouvel objet n'est créé. L'instruction b += 1
appelle la fonction __iadd__(self, other)
de numpy.ndarray
qui modifie l'objet ndarray
en place auquel b fait référence. Par conséquent, B
est modifier.
Un problème clé ici est que cette boucle itère sur les lignes (1ère dimension) de B
:
In [258]: B
Out[258]:
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
In [259]: for b in B:
...: print(b,'=>',end='')
...: b += 1
...: print(b)
...:
[0 1 2] =>[1 2 3]
[3 4 5] =>[4 5 6]
[6 7 8] =>[7 8 9]
[ 9 10 11] =>[10 11 12]
Ainsi, le +=
agit sur un objet mutable, un tableau.
Ceci est implicite dans les autres réponses, mais facilement manqué si vous vous concentrez sur la réaffectation a = a+1
.
Je pourrais aussi faire une modification sur place à b
avec [:]
indexation, ou même quelque chose de fantaisiste, b[1:]=0
:
In [260]: for b in B:
...: print(b,'=>',end='')
...: b[:] = b * 2
[1 2 3] =>[2 4 6]
[4 5 6] =>[ 8 10 12]
[7 8 9] =>[14 16 18]
[10 11 12] =>[20 22 24]
Bien sûr, avec un tableau 2d comme B
, nous n'avons généralement pas besoin d'itérer sur les lignes. De nombreuses opérations qui fonctionnent sur un seul de B
fonctionnent également sur l'ensemble. B += 1
, B[1:] = 0
, etc.