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.

100
demandé sur Peter Mortensen 2017-01-03 18:27:00

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'instruction x += y est juste un raccourci pour x = x + y. Ce serait le cas si x était quelque chose comme un int.

  • Si __iadd__ renvoie NotImplemented, 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) et y.__radd__(x) sont considérées, à l'évaluation de x + y. Dans certaines situations, l'affectation augmentée peut entraîner des erreurs inattendues (voir pourquoi a_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.

106
répondu MSeifert 2017-01-03 17:14:32

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 comme x = 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).

27
répondu Maroun 2017-01-04 08:03:34

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.

12
répondu jmd_dk 2018-03-26 18:23:19

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 +=

4
répondu Inconnu 2017-01-03 15:46:28

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.

3
répondu Andi Kleve 2017-01-03 15:56:24

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.

2
répondu hpaulj 2017-01-03 22:36:39