Selisih antara a - = b dan a = a - b dengan Python

90

Saya baru-baru ini menerapkan solusi ini untuk rata-rata setiap baris matriks N. Meskipun solusinya berfungsi secara umum, saya mengalami masalah saat diterapkan pada array 7x1. Saya perhatikan bahwa masalahnya adalah saat menggunakan -=operator. Untuk membuat contoh kecil:

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

keluaran yang mana:

[1 1 2]
[1 1 1]

Jadi, dalam kasus array a -= bmenghasilkan hasil yang berbeda dari a = a - b. Saya pikir sampai sekarang kedua cara ini sama persis. Apa bedanya?

Kenapa metode yang saya sebutkan untuk menjumlahkan setiap baris N dalam matriks bekerja misalnya untuk matriks 7x4 tetapi tidak untuk array 7x1?

iasonas
sumber

Jawaban:

80

Catatan: menggunakan operasi di tempat pada array NumPy yang berbagi memori tidak lagi menjadi masalah di versi 1.13.0 dan seterusnya (lihat detailnya di sini ). Kedua operasi tersebut akan menghasilkan hasil yang sama. Jawaban ini hanya berlaku untuk versi NumPy sebelumnya.


Mutasi array saat digunakan dalam komputasi dapat menyebabkan hasil yang tidak terduga!

Dalam contoh dalam pertanyaan, pengurangan dengan -=memodifikasi elemen kedua adan kemudian segera menggunakan elemen kedua yang dimodifikasi itu dalam operasi pada elemen ketiga a.

Inilah yang terjadi dengan a[1:] -= a[:-1]langkah demi langkah:

  • aadalah larik dengan data [1, 2, 3].

  • Kami memiliki dua pandangan ke data ini: a[1:]is [2, 3], dan a[:-1]is [1, 2].

  • Pengurangan di tempat -=dimulai. Elemen pertama a[:-1], 1, dikurangi dari elemen pertama a[1:]. Ini telah diubah amenjadi [1, 1, 3]. Sekarang kita memiliki a[1:]tampilan data [1, 3], dan a[:-1]tampilan data [1, 1](elemen kedua dari array atelah diubah).

  • a[:-1]sekarang [1, 1]dan NumPy sekarang harus mengurangi elemen keduanya yaitu 1 (bukan 2 lagi!) dari elemen kedua a[1:]. Ini membuat a[1:]pandangan tentang nilai-nilai [1, 2].

  • asekarang menjadi array dengan nilai [1, 1, 2].

b[1:] = b[1:] - b[:-1]tidak memiliki masalah ini karena b[1:] - b[:-1]membuat larik baru terlebih dahulu dan kemudian menetapkan nilai dalam larik ini ke b[1:]. Itu tidak mengubah bdirinya sendiri selama pengurangan, jadi tampilan b[1:]dan b[:-1]tidak berubah.


Saran umumnya adalah menghindari memodifikasi satu tampilan dengan yang lain jika tumpang tindih. Ini termasuk operator -=, *=dll. Dan menggunakan outparameter dalam fungsi universal (seperti np.subtractdan np.multiply) untuk menulis kembali ke salah satu array.

Alex Riley
sumber
4
Saya lebih suka jawaban ini lebih dari yang diterima saat ini. Ini menggunakan bahasa yang sangat jelas untuk menunjukkan efek memodifikasi objek yang bisa berubah di tempat. Lebih penting lagi, paragraf terakhir secara langsung menekankan pentingnya modifikasi di tempat untuk tampilan yang tumpang tindih, yang seharusnya menjadi pelajaran untuk dibawa pulang dari pertanyaan ini.
Reti43
43

Secara internal, perbedaannya adalah ini:

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

setara dengan ini:

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

sementara ini:

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

memetakan ke ini:

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

Dalam beberapa kasus, __sub__()dan __isub__()bekerja dengan cara yang serupa. Tapi objek yang bisa berubah harus bermutasi dan mengembalikan dirinya sendiri saat digunakan __isub__(), sementara mereka harus mengembalikan objek baru dengan __sub__().

Menerapkan operasi slice pada objek numpy akan membuat tampilan padanya, jadi menggunakannya secara langsung mengakses memori objek "asli".

glglgl
sumber
11

Dokumen tersebut mengatakan:

Gagasan di balik penugasan tambahan dengan Python adalah bahwa ini bukan hanya cara yang lebih mudah untuk menulis praktik umum menyimpan hasil operasi biner di operan kiri, tetapi juga cara untuk operan kiri yang dimaksud untuk ketahuilah bahwa ia harus beroperasi 'sendiri', daripada membuat salinan dirinya yang dimodifikasi.

Sebagai aturan umum, augmented substraction ( x-=y) adalah x.__isub__(y), untuk operasi DI- tempat JIKA memungkinkan, jika substraksi normal ( x = x-y) adalah x=x.__sub__(y). Pada objek yang tidak bisa berubah seperti integer, itu setara. Tetapi untuk yang bisa berubah seperti array atau daftar, seperti dalam contoh Anda, mereka bisa menjadi hal yang sangat berbeda.

BM
sumber