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.
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.
| Operasi | Hasil | Berbagi memori dengan asalnya |
|---|---|---|
a[1:4] | view | ya |
a[1] | view | ya |
a[[1, 3]] | salinan | tidak |
a[a > 20] | salinan | tidak |
a[1:4].copy() | salinan | tidak |
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.
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))True
False
FalseMutasi 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.
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)[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.
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)[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.
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)True
TrueKeluaran 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.
- Potongan dikirim ke fungsi yang mengubah argumennya. Tanpa salinan, fungsi sebenarnya menulis pada array asal, sebagaimana ditunjukkan pada bagian mutasi di atas.
- 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.
- Potongan diolah untuk keperluan lain yang sifatnya sementara, misalnya disusun ulang atau diisi ulang, sementara data mentah harus tetap dipertahankan sebagai rujukan.
Kesalahan umum#
- 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. - 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] = nilaiataua[[0, 2]] = nilai. - Memercayakan
.baseuntuk semua kasus. Pada rantai slice,.basedapat menunjuk array perantara sehingga pemeriksaan terhadap array pertama memberikan hasil yang menyesatkan. Perbaikannya adalah menggunakannp.shares_memoryuntuk 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.basememeriksa ikatan memori secara deterministik, dengannp.shares_memorylebih andal untuk membandingkan dua array tertentu.- Salinan dibuat secara eksplisit dengan
.copy()atau otomatis oleh operasi yang menuntut tata letak memori baru.
Referensi#
- Copies and views — NumPy — Penjelasan resmi perbedaan view dan salinan beserta cara memutus ikatannya.
- numpy.shares_memory — NumPy — Dokumentasi fungsi pemeriksa berbagi memori antar dua array.
- Indexing routines — NumPy — Rujukan teknis operasi pengindeksan beserta sifat view atau salinan pada hasilnya.