Diferencia entre a - = by a = a - b en Python

90

Recientemente apliqué esta solución para promediar cada N filas de matriz. Aunque la solución funciona en general, tuve problemas cuando se aplicó a una matriz de 7x1. He notado que el problema es al usar el -=operador. Para dar un pequeño ejemplo:

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

que salidas:

[1 1 2]
[1 1 1]

Entonces, en el caso de una matriz a -= bproduce un resultado diferente a a = a - b. Hasta ahora pensaba que estas dos formas son exactamente iguales. ¿Cuál es la diferencia?

¿Cómo es que el método que estoy mencionando para sumar cada N filas en una matriz funciona, por ejemplo, para una matriz de 7x4 pero no para una matriz de 7x1?

iasonas
fuente

Respuestas:

80

Nota: el uso de operaciones in situ en matrices NumPy que comparten memoria ya no es un problema a partir de la versión 1.13.0 (consulte los detalles aquí ). Las dos operaciones producirán el mismo resultado. Esta respuesta solo se aplica a versiones anteriores de NumPy.


¡La mutación de matrices mientras se utilizan en cálculos puede dar lugar a resultados inesperados!

En el ejemplo de la pregunta, la resta con -=modifica el segundo elemento de ay luego usa inmediatamente ese segundo elemento modificado en la operación sobre el tercer elemento de a.

Esto es lo que sucede con el a[1:] -= a[:-1]paso a paso:

  • aes la matriz con los datos [1, 2, 3].

  • Tenemos dos puntos de vista sobre estos datos: a[1:]es [2, 3]y a[:-1]es [1, 2].

  • -=Comienza la resta in situ . El primer elemento de a[:-1], 1, se resta del primer elemento de a[1:]. Esto ha cambiado apara ser [1, 1, 3]. Ahora tenemos que a[1:]es una vista de los datos [1, 3]y a[:-1]es una vista de los datos [1, 1](el segundo elemento de la matriz aha sido cambiado).

  • a[:-1]es ahora [1, 1]y NumPy ahora debe restar su segundo elemento, que es 1 (¡ya no 2!) del segundo elemento de a[1:]. Esto hace a[1:]una vista de los valores [1, 2].

  • aahora es una matriz con los valores [1, 1, 2].

b[1:] = b[1:] - b[:-1]no tiene este problema porque b[1:] - b[:-1]crea una nueva matriz primero y luego asigna los valores de esta matriz a b[1:]. No se modifica bdurante la resta, por lo que las vistas b[1:]y b[:-1]no cambian.


El consejo general es evitar modificar una vista in situ con otra si se superponen. Esto incluye los operadores -=, *=, etc. y usando el outparámetro en funciones universales (como np.subtracty np.multiply) para escribir de nuevo a uno de los arrays.

Alex Riley
fuente
4
Prefiero esta respuesta más a la actualmente aceptada. Utiliza un lenguaje muy claro para mostrar el efecto de modificar objetos mutables en su lugar. Más importante aún, el último párrafo enfatiza directamente la importancia de la modificación en el lugar para las vistas superpuestas, que debería ser la lección para aprender de esta pregunta.
Reti43
43

Internamente, la diferencia es que esto:

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

es equivalente a esto:

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)))

mientras esto:

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

mapas a esto:

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)))

En algunos casos, __sub__()y __isub__()funcionan de manera similar. Pero los objetos mutables deben mutar y volver a sí mismos cuando se usan __isub__(), mientras que deben devolver un nuevo objeto con __sub__().

La aplicación de operaciones de corte en objetos numpy crea vistas sobre ellos, por lo que su uso accede directamente a la memoria del objeto "original".

glglgl
fuente
11

Los doctores dicen:

La idea detrás de la asignación aumentada en Python es que no es solo una forma más fácil de escribir la práctica común de almacenar el resultado de una operación binaria en su operando de la izquierda, sino también una forma para que el operando de la izquierda en cuestión Sepa que debería operar "sobre sí mismo", en lugar de crear una copia modificada de sí mismo.

Como regla general, la sustracción aumentada ( x-=y) es x.__isub__(y), para la operación IN- place SI es posible, cuando la sustracción normal ( x = x-y) es x=x.__sub__(y). En objetos no mutables como enteros es equivalente. Pero para los mutables como matrices o listas, como en su ejemplo, pueden ser cosas muy diferentes.

BM
fuente