Differenza tra a - = be a = a - b in Python


90

Di recente ho applicato questa soluzione per calcolare la media di ogni N righe di matrice. Sebbene la soluzione funzioni in generale, ho avuto problemi quando è stata applicata a un array 7x1. Ho notato che il problema è quando si utilizza l' -=operatore. Per fare un piccolo esempio:

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

quali uscite:

[1 1 2]
[1 1 1]

Quindi, nel caso di un array, a -= bproduce un risultato diverso daa = a - b . Fino ad ora ho pensato che questi due modi fossero esattamente gli stessi. Qual è la differenza?

Come mai il metodo che sto citando per sommare ogni N righe in una matrice funziona, ad esempio, per una matrice 7x4 ma non per un array 7x1?

Risposte:


80

Nota: l'utilizzo di operazioni sul posto su array NumPy che condividono la memoria non è più un problema nella versione 1.13.0 in poi (vedere i dettagli qui ). Le due operazioni produrranno lo stesso risultato. Questa risposta si applica solo alle versioni precedenti di NumPy.


La mutazione di array mentre vengono utilizzati nei calcoli può portare a risultati inaspettati!

Nell'esempio nella domanda, la sottrazione con -=modifica il secondo elemento di ae quindi utilizza immediatamente quel secondo elemento modificato nell'operazione sul terzo elemento dia .

Ecco cosa succede a[1:] -= a[:-1]passo dopo passo:

  • aè l'array con i dati [1, 2, 3].

  • Abbiamo due punti di vista su questi dati: a[1:]è [2, 3]ed a[:-1]è [1, 2].

  • -=Inizia la sottrazione sul posto . Il primo elemento di a[:-1], 1, viene sottratto dal primo elemento di a[1:]. Questo è cambiato aper essere [1, 1, 3]. Ora abbiamo che a[1:]è una vista dei dati [1, 3], ed a[:-1]è una vista dei dati [1, 1](il secondo elemento dell'array aè stato cambiato).

  • a[:-1]è adesso [1, 1]e NumPy deve ora sottrarre il suo secondo elemento che è 1 (non più 2!) dal secondo elemento di a[1:]. Questo fa a[1:]una visione dei valori [1, 2].

  • aè ora un array con i valori [1, 1, 2].

b[1:] = b[1:] - b[:-1]non ha questo problema perché b[1:] - b[:-1]crea prima un nuovo array e poi assegna i valori in questo array a b[1:]. Non si modifica bdurante la sottrazione, quindi le viste b[1:]e b[:-1]non cambiano.


Il consiglio generale è di evitare di modificare una vista in posizione con un'altra se si sovrappongono. Ciò include gli operatori -=, *=ecc. E l'utilizzo del outparametro in funzioni universali (come np.subtracte np.multiply) per riscrivere su uno degli array.


4
Preferisco questa risposta più a quella attualmente accettata. Utilizza un linguaggio molto chiaro per mostrare l'effetto della modifica di oggetti mutabili sul posto. Ancora più importante, l'ultimo paragrafo sottolinea direttamente l'importanza della modifica sul posto per le viste sovrapposte, che dovrebbe essere la lezione da trarre a casa da questa domanda.
Reti43

43

Internamente, la differenza è che questo:

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

è equivalente a questo:

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

mentre questo:

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

mappato a questo:

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

In alcuni casi, __sub__()e __isub__()funzionano in modo simile. Ma gli oggetti modificabili dovrebbero mutare e restituire se stessi durante l'uso __isub__(), mentre dovrebbero restituire un nuovo oggetto con__sub__() .

L'applicazione di operazioni slice su oggetti numpy crea visualizzazioni su di essi, quindi utilizzarli si accede direttamente alla memoria dell'oggetto "originale".


11

I documenti dicono:

L'idea alla base dell'assegnazione aumentata in Python è che non è solo un modo più semplice per scrivere la pratica comune di memorizzare il risultato di un'operazione binaria nel suo operando di sinistra, ma anche un modo per l'operando di sinistra in questione di sappi che dovrebbe operare "su se stesso", piuttosto che creare una copia modificata di se stesso.

Come regola empirica, la sottrazione aumentata ( x-=y) è x.__isub__(y), per l' operazione IN -place SE possibile, quando la normale sottrazione ( x = x-y) è x=x.__sub__(y). Su oggetti non modificabili come gli interi è equivalente. Ma per quelli modificabili come array o elenchi, come nel tuo esempio, possono essere cose molto diverse.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.