web-dev-qa-db-fra.com

Quelle est la différence entre i = i + 1 et i + = 1 dans une boucle 'pour'?

J'ai découvert une chose curieuse aujourd'hui et je me demandais si quelqu'un pourrait nous éclairer sur la différence.

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 avoir exécuté chaque boucle for, A n'a pas changé, mais B en a ajouté une à chaque élément. J'utilise réellement la version B pour écrire dans un tableau NumPy initialisé dans une boucle for.

105
Adam Fjeldsted

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 vient de réaffecter la variable a = a + 1.


Juste pour être complet:

x += y est pas toujours en effectuant une opération sur place, il y a (au moins) trois exceptions:

  • Si x n'implémente pas une méthode __iadd__, l'instruction x += y n'est qu'un raccourci pour x = x + y. Ce serait le cas si x ressemblait à 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, cependant.

En l'occurrence, votre bs est numpy.ndarrays qui implémente __iadd__ et se retourne de sorte que votre seconde boucle modifie le tableau d'origine sur place.

Vous pouvez en lire plus à ce sujet dans Documentation Python sur "Emulation des types numériques" .

Ces méthodes [__i*__] sont appelées pour implémenter les attributions arithmétiques augmentées (+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=). Ces méthodes doivent tenter d'effectuer l'opération sur place (modification de soi) et renvoyer le résultat (ce qui pourrait être, mais ne doit pas nécessairement être, soi). 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 une instance d'une classe avec une méthode __iadd__(), x += y est équivalent à x = x.__iadd__(y). Sinon, x.__add__(y) et y.__radd__(x) sont pris en compte, comme pour l'évaluation de x + y. Dans certaines situations, l'affectation augmentée peut entraîner des erreurs inattendues (voir Pourquoi a_Tuple[i] += ["item"] levée une exception lorsque l'ajout fonctionne? ), mais ce comportement fait en fait partie du modèle de données.

110
MSeifert

Dans le premier exemple, vous réaffectez la variable a, tandis que dans le second, vous modifiez les données sur place, à l'aide de l'opérateur _+=_.

Voir la section sur 7.2.1. Déclarations d'affectation augmentées :

Une expression d'affectation augmentée telle que _x += 1_ peut être réécrite en tant que _x = x + 1_ pour obtenir un effet similaire, mais pas exactement identique. Dans la version augmentée, x n'est évalué qu'une fois. En outre, lorsque cela est possible, l'opération réelle est effectuée sur place , ce qui signifie que, plutôt que de créer un nouvel objet et de l'assigner à la cible, l'ancien objet est modifié à la place.

_+=_ appels opérateurs __iadd__ . Cette fonction effectue la modification sur place et ce n’est qu’après son exécution que le résultat est redéfini sur l’objet sur lequel vous "appliquez" le _+=_.

__add__ en revanche prend les paramètres et retourne leur somme (sans les modifier).

28
Maroun

Comme déjà indiqué, _b += 1_ met à jour b sur place, alors que _a = a + 1_ calcule _a + 1_ et attribue ensuite le nom a au résultat (maintenant a ne fait plus référence à une ligne de A).

Pour bien comprendre l'opérateur _+=_, nous devons également comprendre le concept d'objets mutable et immuable. Considérez ce qui se passe lorsque nous omettons 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 not ​​mis à jour, ce qui signifie que _c += 1_ et _c = c + 1_ sont équivalents. Ceci est dû au fait que maintenant C est un tableau 1D (_C.ndim == 1_). Ainsi, lors de l'itération sur C, chaque élément entier est extrait et affecté à c.

Désormais, en Python, les entiers sont immuables, ce qui signifie que les mises à jour sur place ne sont pas autorisées, transformant ainsi _c += 1_ en _c = c + 1_, où c fait maintenant référence à un new entier, non couplé à C de quelque manière que ce soit. Lorsque vous passez en boucle sur les tableaux remodelés, des lignes entières (_np.ndarray_) sont attribuées à b (et a) à la fois, qui sont mutable objets, 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 censés être 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 le ___add___ et __iadd__ méthodes, respectivement.

13
jmd_dk

La forme abrégée (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 renvoyer au même nom (a = a + 1). , La forme abrégée (a += 1) est beaucoup plus efficace, car elle n’a pas nécessairement besoin de copier a contrairement à a = a + 1.

De plus, même s'ils produisent le même résultat, notez qu'ils sont différents car ils sont des opérateurs distincts: + et +=

4
Inconnu

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é comme suit: la fonction __add__(self, other) de numpy.ndarray est appelée. Cela crée un nouvel objet et par conséquent, A n'est pas modifié. Ensuite, la variable a est définie pour faire référence au résultat.

Dans la seconde 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 à la place auquel b fait référence. Par conséquent, B est modifié.

3
Andi Kleve

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 impliqué dans les autres réponses, mais peut être facilement omis si vous vous concentrez sur la réaffectation a = a+1.

Je pourrais également apporter un changement sur place à b avec [:] indexation, ou même à un élément plus sophistiqué, 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 de parcourir les lignes. De nombreuses opérations qui fonctionnent sur l'un des B fonctionnent également sur l'ensemble. B += 1, B[1:] = 0, etc.

2
hpaulj