web-dev-qa-db-fra.com

Différence entre a - = b et a = a - b dans Python

J'ai récemment appliqué une solution this pour faire la moyenne de toutes les N lignes de matrice. Bien que la solution fonctionne en général, j'ai eu des problèmes lorsqu'elle a été appliquée à un tableau 7x1. J'ai remarqué que le problème vient de l'utilisation de -= opérateur. Pour faire un petit exemple:

import numpy as np

a = np.array([1,2,3])
b = np.copy(a)

a[1:] -= a[:-1]
b[1:] = b[1:] - b[:-1]

print a
print b

qui génère:

[1 1 2]
[1 1 1]

Donc, dans le cas d'un tableau a -= b produit un résultat différent de a = a - b. Je pensais jusqu'à présent que ces deux façons sont exactement les mêmes. Quelle est la différence?

Comment se fait-il que la méthode que je mentionne pour additionner chaque N lignes dans une matrice fonctionne par exemple pour une matrice 7x4 mais pas pour un tableau 7x1?

88
iasonas

Remarque: l'utilisation d'opérations sur place sur les tableaux NumPy qui partagent la mémoire n'est plus un problème à partir de la version 1.13.0 (voir les détails ici ). Les deux opérations produiront le même résultat. Cette réponse ne s'applique qu'aux versions antérieures de NumPy.


La mutation des tableaux lorsqu'ils sont utilisés dans les calculs peut conduire à des résultats inattendus!

Dans l'exemple de la question, soustraction avec -= modifie le deuxième élément de a puis utilise immédiatement ce deuxième élément modifié dans l'opération sur le troisième élément de a.

Voici ce qui se passe avec a[1:] -= a[:-1] pas à pas:

  • a est le tableau contenant les données [1, 2, 3].

  • Nous avons deux vues sur ces données: a[1:] est [2, 3], et a[:-1] est [1, 2].

  • La soustraction en place -= commence. Le premier élément de a[:-1], 1, est soustrait du premier élément de a[1:]. Cela a modifié a en [1, 1, 3]. Nous avons maintenant que a[1:] est une vue des données [1, 3], et a[:-1] est une vue des données [1, 1] (le deuxième élément du tableau a a été modifié).

  • a[:-1] est maintenant [1, 1] et NumPy doivent maintenant soustraire son deuxième élément qui est 1 (plus 2 maintenant!) du deuxième élément de a[1:]. Cela fait a[1:] une vue des valeurs [1, 2].

  • a est maintenant un tableau avec les valeurs [1, 1, 2].

b[1:] = b[1:] - b[:-1] n'a pas ce problème car b[1:] - b[:-1] crée d'abord un nouveau tableau puis attribue les valeurs de ce tableau à b[1:]. Il ne modifie pas b lui-même pendant la soustraction, donc les vues b[1:] et b[:-1] ne changez pas.


Le conseil général est d'éviter de modifier une vue en place avec une autre si elles se chevauchent. Cela inclut les opérateurs -=, *=, etc. et en utilisant le paramètre out dans les fonctions universelles (comme np.subtract et np.multiply) pour réécrire dans l'un des tableaux.

79
Alex Riley

En interne, la différence est que ceci:

a[1:] -= a[:-1]

est équivalent à ceci:

a[1:] = a[1:].__isub__(a[:-1])
a.__setitem__(slice(1, None, None), a.__getitem__(slice(1, None, None)).__isub__(a.__getitem__(slice(1, None, None)))

alors que ce:

b[1:] = b[1:] - b[:-1]

correspond à ceci:

b[1:] = b[1:].__sub__(b[:-1])
b.__setitem__(slice(1, None, None), b.__getitem__(slice(1, None, None)).__sub__(b.__getitem__(slice(1, None, None)))

Dans certains cas, __sub__() et __isub__() fonctionnent de manière similaire. Mais les objets mutables doivent muter et se retourner lorsqu'ils utilisent __isub__(), alors qu'ils doivent renvoyer un nouvel objet avec __sub__().

L'application d'opérations de tranche sur des objets numpy crée des vues sur eux, donc leur utilisation accède directement à la mémoire de l'objet "d'origine".

43
glglgl

Les docs disent:

L'idée derrière l'affectation augmentée dans Python est que ce n'est pas seulement un moyen plus simple d'écrire la pratique courante de stocker le résultat d'une opération binaire dans son opérande de gauche, mais aussi un moyen pour l'opérande de gauche en question de savoir qu'il doit opérer "sur lui-même", plutôt que de créer une copie modifiée de lui-même.

En règle générale, la soustraction augmentée (x-=y) Est x.__isub__(y), pour [~ # ~] dans [~ # ~] - place l'opération [~ # ~] si [~ # ~] possible, lorsque la soustraction normale (x = x-y) est x=x.__sub__(y) . Sur les objets non mutables comme les entiers, c'est équivalent. Mais pour les fichiers mutables comme les tableaux ou les listes, comme dans votre exemple, ils peuvent être très différents.

11
B. M.