View versus salinan

Slice sebagai view, bug mutasi klasik, dan kapan salinan dibuat.

Daftar isi

Perbedaan antara view dan salinan menentukan kapan sebuah perubahan data ikut terbawa dan kapan data asal tetap utuh, sehingga aturan ini menyangkut kebenaran data pada hampir semua kode NumPy. Topik ini menghimpun aturan tersebut secara lengkap, yaitu operasi mana yang menghasilkan view dan mana yang menghasilkan salinan, cara memeriksanya dengan alat bawaan NumPy, serta kebiasaan menulis yang mencegah mutasi bocor ke array asal. Sifat-sifat yang sudah muncul pada topik-topik lain dalam modul ini kini dihimpun sebagai satu sistem yang utuh.

View dan salinan#

Buffer data adalah blok memori yang menampung nilai elemen array. View adalah array yang berbagi buffer data dengan array lainnya, sedangkan salinan (copy) memiliki buffer datanya sendiri. Karena berbagi buffer, perubahan pada view terlihat pada array asalnya, dan perubahan pada array asal juga terlihat pada view. Salinan tidak memiliki ikatan apa pun, sehingga perubahan pada salah satunya tidak pernah terlihat pada yang lain. Diagram berikut menggambarkan perbedaannya, yaitu dua nama variabel yang menunjuk memori yang sama pada view, dan dua blok memori yang terpisah pada salinan.

Viewabb = a[1:4]bufferdataSalinanacc = a[1:4].copy()buffer abuffer c
View berbagi buffer data yang sama, sedangkan salinan memiliki buffer sendiri

NumPy memilih mekanisme view demi efisiensi, karena memotong array berukuran besar tidak perlu menyalin apa pun. Salinan hanya dibuat secara eksplisit melalui .copy() atau secara otomatis oleh operasi yang memang menuntut tata letak memori baru, seperti penyaringan dengan mask dan fancy indexing. Tabel berikut merangkum operasi yang sudah dibahas sepanjang modul ini.

OperasiHasilBerbagi memori dengan asalnya
a[1:4]viewya
a[1]viewya
a[[1, 3]]salinantidak
a[a > 20]salinantidak
a[1:4].copy()salinantidak

Kode berikut membuktikan pembagian tersebut pada satu array yang sama, dengan np.shares_memory sebagai alat pemeriksa yang mengembalikan True bila dua array berbagi memori.

python Contoh: membandingkan view dan salinan dengan shares_memory
import numpy as np

a = np.array([10, 20, 30, 40, 50])
b = a[1:4]
c = a[[1, 2, 3]]
d = a[a > 20]
print(np.shares_memory(a, b))
print(np.shares_memory(a, c))
print(np.shares_memory(a, d))
text Keluaran
True
False
False

Mutasi pada view terbawa ke array asal#

Karena view dan asalnya menunjuk buffer yang sama, mutasi pada salah satunya terlihat pada keduanya. Salah satu sumber bug yang lazim adalah fungsi yang mengubah argumennya, kemudian dipanggil dengan potongan array sebagai argumen. Pemanggil fungsi tersebut sering menganggap data yang dikirim berdiri sendiri, padahal yang dikirim adalah view.

python Contoh: mutasi pada view terbawa ke array asal
import numpy as np

def nolkan_seluruhnya(x):
    x[:] = 0

a = np.array([10, 20, 30, 40, 50])
nolkan_seluruhnya(a[1:4])
print(a)
text Keluaran
[10  0  0  0 50]

Penugasan x[:] = 0 mengisi nol pada seluruh buffer yang ditunjuk x, dan x adalah view atas elemen indeks 1 sampai 3 milik a, sehingga tiga nilai tengah array asal ikut menjadi nol. Tidak ada error maupun peringatan yang muncul karena perilaku ini memang disengaja oleh NumPy. Bila fungsi dimaksudkan bekerja pada data yang berdiri sendiri, kirimkan salinannya.

python Contoh: memutus ikatan dengan copy
import numpy as np

def nolkan_seluruhnya(x):
    x[:] = 0

a = np.array([10, 20, 30, 40, 50])
nolkan_seluruhnya(a[1:4].copy())
print(a)
text Keluaran
[10 20 30 40 50]

Penambahan .copy() pada argumen membuat fungsi menerima buffer yang berdiri sendiri, sehingga array asal tetap utuh. Dengan satu pemanggilan metode, data asal menjadi aman dari mutasi yang terjadi di dalam fungsi.

Alat pemeriksa: base dan shares_memory#

NumPy menyediakan dua cara yang deterministik untuk memastikan ikatan memori, yaitu atribut .base dan fungsi np.shares_memory. Atribut .base menunjuk array pemilik buffer di balik sebuah array, sehingga view selalu memiliki .base bukan None, sedangkan array yang berdiri sendiri memiliki .base bernilai None. Fungsi np.shares_memory(a, b) membandingkan dua array tertentu dan mengembalikan True atau False.

python Contoh: memeriksa asal array dengan base
import numpy as np

a = np.array([10, 20, 30, 40, 50])
potongan = a[1:4]
salinan = potongan.copy()
print(potongan.base is a)
print(salinan.base is None)
text Keluaran
True
True

Keluaran menunjukkan bahwa .base milik potongan adalah a, yaitu array pemilik buffer, sedangkan .base milik salinan bernilai None sehingga salinan tersebut berdiri sendiri. Perlu diketahui bahwa .base hanya menunjuk satu tingkat ke atas, sehingga pada rantai operasi, misalnya slice dari slice, nilai .base bisa menunjuk array perantara dan bukan array pertama yang dibuat. Untuk membandingkan dua array tertentu, np.shares_memory lebih andal karena diperiksa berpasangan.

Kapan copy wajib digunakan#

Aturan baku menetapkan .copy() wajib dipanggil apabila array hasil pemotongan akan dimodifikasi dan data asal harus tetap utuh. Berikut adalah tiga situasi yang lazim membutuhkan aturan itu.

  1. Potongan dikirim ke fungsi yang mengubah argumennya. Tanpa salinan, fungsi sebenarnya menulis pada array asal, sebagaimana ditunjukkan pada bagian mutasi di atas.
  2. Potongan disimpan sebagai cadangan sebelum array asal diperbarui melalui penugasan elemen. Tanpa salinan, cadangan ikut berubah pada saat array asal diperbarui sehingga kehilangan fungsi catatan lamanya.
  3. Potongan diolah untuk keperluan lain yang sifatnya sementara, misalnya disusun ulang atau diisi ulang, sementara data mentah harus tetap dipertahankan sebagai rujukan.

Kesalahan umum#

  1. Memodifikasi hasil slice tanpa sadar. Data asal ikut berubah karena slice adalah view, dan kekeliruan ini baru ketahuan setelah nilai di bagian lain kode ikut berbeda. Perbaikannya adalah memanggil .copy() pada setiap potongan yang akan dimutasi.
  2. Mengharapkan mutasi pada hasil mask atau fancy indexing terbawa ke array asal. Kedua operasi menghasilkan salinan, sehingga perubahan pada hasilnya tidak pernah memengaruhi array asal. Perbaikannya adalah menuliskan kembali hasilnya melalui penugasan, misalnya a[mask] = nilai atau a[[0, 2]] = nilai.
  3. Memercayakan .base untuk semua kasus. Pada rantai slice, .base dapat menunjuk array perantara sehingga pemeriksaan terhadap array pertama memberikan hasil yang menyesatkan. Perbaikannya adalah menggunakan np.shares_memory untuk membandingkan dua array tertentu secara berpasangan.

Ringkasan#

  • Slice dan pengambilan baris menghasilkan view, sedangkan mask, fancy indexing, dan .copy() menghasilkan salinan.
  • Mutasi pada view terbawa ke array asal tanpa error maupun peringatan, sehingga potongan yang akan dimodifikasi wajib disalin lebih dahulu.
  • np.shares_memory(a, b) dan atribut .base memeriksa ikatan memori secara deterministik, dengan np.shares_memory lebih andal untuk membandingkan dua array tertentu.
  • Salinan dibuat secara eksplisit dengan .copy() atau otomatis oleh operasi yang menuntut tata letak memori baru.

Referensi#