Diferença entre a - = b e a = a - b em Python

90

Recentemente, apliquei essa solução para calcular a média de todas as N linhas da matriz. Embora a solução funcione em geral, tive problemas quando aplicada a um array 7x1. Percebi que o problema é ao usar o -=operador. Para dar um pequeno exemplo:

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 produz:

[1 1 2]
[1 1 1]

Portanto, no caso de um array a -= bproduz um resultado diferente de a = a - b. Eu pensei até agora que essas duas formas são exatamente iguais. Qual é a diferença?

Por que o método que estou mencionando para somar todas as N linhas em uma matriz está funcionando, por exemplo, para uma matriz 7x4, mas não para uma matriz 7x1?

iasonas
fonte

Respostas:

80

Nota: o uso de operações in-loco em matrizes NumPy que compartilham memória não é mais um problema na versão 1.13.0 em diante (veja os detalhes aqui ). As duas operações produzirão o mesmo resultado. Esta resposta se aplica apenas a versões anteriores do NumPy.


A mutação de matrizes enquanto estão sendo usadas em cálculos pode levar a resultados inesperados!

No exemplo da pergunta, a subtração com -=modifica o segundo elemento de ae, em seguida, usa imediatamente esse segundo elemento modificado na operação do terceiro elemento de a.

Aqui está o que acontece com o a[1:] -= a[:-1]passo a passo:

  • aé a matriz com os dados [1, 2, 3].

  • Temos duas visões sobre esses dados: a[1:]é [2, 3]e a[:-1]é [1, 2].

  • A subtração no local -=começa. O primeiro elemento de a[:-1], 1, é subtraído do primeiro elemento de a[1:]. Isso mudou apara ser [1, 1, 3]. Agora temos que a[1:]é uma visão dos dados [1, 3]e a[:-1]é uma visão dos dados [1, 1](o segundo elemento do array afoi alterado).

  • a[:-1]é agora [1, 1]e NumPy deve agora subtrair seu segundo elemento que é 1 (não mais 2!) do segundo elemento de a[1:]. Isso dá a[1:]uma visão dos valores [1, 2].

  • aagora é uma matriz com os valores [1, 1, 2].

b[1:] = b[1:] - b[:-1]não tem esse problema porque primeiro b[1:] - b[:-1]cria uma nova matriz e, em seguida, atribui os valores nessa matriz a b[1:]. Ele não se modifica bdurante a subtração, portanto, as visualizações b[1:]e b[:-1]não mudam.


O conselho geral é evitar modificar uma visualização no local com outra se elas se sobreporem. Isso inclui os operadores -=, *=etc. e o uso do outparâmetro em funções universais (como np.subtracte np.multiply) para escrever de volta em um dos arrays.

Alex Riley
fonte
4
Eu prefiro esta resposta mais do que a atualmente aceita. Ele usa uma linguagem muito clara para mostrar o efeito da modificação de objetos mutáveis ​​no local. Mais importante ainda, o último parágrafo enfatiza diretamente a importância da modificação no local para visualizações sobrepostas, que deve ser a lição a ser aprendida com essa questão.
Reti43
43

Internamente, a diferença é que este:

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

é equivalente a isto:

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

enquanto isso:

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

mapeia para este:

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

Em alguns casos, __sub__()e __isub__()funcionam de forma semelhante. Mas os objetos mutáveis ​​devem sofrer mutação e retornar a si mesmos ao usar __isub__(), enquanto devem retornar um novo objeto com __sub__().

Aplicar operações de fatia em objetos numpy cria visualizações neles, portanto, usá-las acessa diretamente a memória do objeto "original".

glglgl
fonte
11

Os documentos dizem:

A ideia por trás da atribuição aumentada em Python é que não é apenas uma maneira mais fácil de escrever a prática comum de armazenar o resultado de uma operação binária em seu operando à esquerda, mas também uma maneira para o operando à esquerda em questão saiba que deve operar `sobre si mesmo ', em vez de criar uma cópia modificada de si mesmo.

Como regra geral, a subtração aumentada ( x-=y) é x.__isub__(y), para a operação IN -place SE possível, quando a subtração normal ( x = x-y) é x=x.__sub__(y). Em objetos não mutáveis, como inteiros, é equivalente. Mas para os mutáveis ​​como arrays ou listas, como no seu exemplo, eles podem ser coisas muito diferentes.

BM
fonte