Satu ndarray pada dasarnya adalah blok memori datar berisi byte, ditambah metadata yang menjelaskan cara menafsirkannya: dtype, shape, dan strides. Pandangan ini yang membuat NumPy bisa bergandengan dengan dunia C — bahasa yang juga bekerja langsung atas blok memori. Ketika perpustakaan C, driver perangkat, atau file biner menyerahkan pointer dan jumlah byte, NumPy dapat membungkusnya menjadi array tanpa menyalin satu byte pun; sebaliknya, array NumPy dapat dioper ke fungsi C sebagai pointer biasa.

Bab ini membedah empat perangkat jembatan itu: strides sebagai peta tata letak, np.frombuffer untuk membaca byte mentah, ctypes untuk mengoper pointer, dan buffer protocol Python. Fondasi konsep tata letaknya sudah disiapkan pada Chapter Memahami Memory Layout dan Strides dan Memory Access.

Memory Layout dan Strides #

Atribut strides menyatakan berapa byte yang harus dilompati untuk berpindah satu langkah di setiap sumbu. Dua array berisi angka sama dapat punya strides berbeda karena tata letak memorinya berbeda:

python
import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
print(a.strides)
print(a.flags["C_CONTIGUOUS"])
f = np.asfortranarray(a)
print(f.strides)
print(f.flags["F_CONTIGUOUS"])

Output yang Dihasilkan:

text
(12, 4)
True
(4, 8)
True

Mari kita bedah:

  1. Baris 4 (a.strides): array berlayout baris (C order). Berpindah satu kolom melompati 4 byte (satu int32), sedangkan berpindah satu baris melompati 3 x 4 = 12 byte.
  2. Baris 5: flag C_CONTIGUOUS memastikan seluruh baris tersambung rapat di memori — kondisi yang dibahas pada Chapter Contiguous vs Non-contiguous.
  3. Baris 6-8: np.asfortranarray menyalin data ke layout kolom; stridesnya menjadi (4, 8) dan flag F_CONTIGUOUS yang aktif. Kode C yang mengharapkan matriks kolom-utama (konvensi Fortran/BLAS) membutuhkan bentuk ini.

frombuffer: Membaca Byte Mentah dari C #

Perpustakaan C biasanya menyerahkan data sebagai void* plus jumlah byte. Di sisi Python, data itu tiba sebagai objek bytes. Fungsi np.frombuffer menafsirkan byte tersebut sesuai dtype — termasuk structured dtype untuk record campuran. Contoh berikut memakai byte hasil struct C { int id; float suhu; } dua record:

python
import numpy as np

buffer_c = bytes([1, 0, 0, 0, 0, 0, 220, 65,
                  2, 0, 0, 0, 154, 153, 217, 65])
dt = np.dtype([("id", "i4"), ("suhu", "f4")])
data = np.frombuffer(buffer_c, dtype=dt)
print(data)
print(data["suhu"])

Output yang Dihasilkan:

text
[(1, 27.5) (2, 27.2)]
[27.5 27.2]

Bedah baris demi baris:

  1. Baris 3-4 (bytes([...])): enam belas byte mentah, dua record masing-masing 8 byte. Empat byte pertama setiap record adalah integer little-endian (1, 0, 0, 0 berarti 1), empat byte berikutnya adalah pola bit float32.
  2. Baris 5-6 (np.frombuffer(...)): interpreter membagi buffer per 8 byte dan memetakan field sesuai dtype. Panjang buffer harus kelipatan dt.itemsize, jika tidak ValueError dimunculkan.
  3. Baris 8 (data["suhu"]): hasilnya array structured biasa; field dapat dibaca dan dihitung seperti bab-bab sebelumnya. Catatan penting: array hasil frombuffer bersifat read-only terhadap buffer bytes.

ctypes: Mengoper Pointer ke Fungsi C #

Modul ctypes bawaan Python adalah jalur dua arah menuju fungsi C. Properti arr.ctypes membungkus array agar dapat dioper sebagai pointer dengan tipe eksplisit, dan perubahan lewat pointer itu terbaca langsung di array:

python
import ctypes
import numpy as np

v = np.array([10, 20, 30], dtype=np.int32)
print(type(v.ctypes))
p = v.ctypes.data_as(ctypes.POINTER(ctypes.c_int32))
print(p[0], p[1], p[2])
p[1] = 99
print(v)

Output yang Dihasilkan:

text
<class 'numpy._core._internal._ctypes'>
10 20 30
[10 99 30]

Penjelasan:

  1. Baris 5 (v.ctypes): objek pembungkus ini menyimpan alamat buffer array; alamatnya berbeda di tiap eksekusi, karena itu contoh mencetak tipenya, bukan alamatnya. Alamat mentah tersedia sebagai v.ctypes.data berupa bilangan bulat.
  2. Baris 6 (data_as(...)): data_as menghasilkan pointer bertipe int32* — bentuk yang persis diharapkan prototipe fungsi C seperti void proses(int32_t* data, int n).
  3. Baris 7-8: pembacaan p[0], p[1], p[2] menunjuk byte array secara langsung; penugasan p[1] = 99 menulis ke memori yang sama, dan v langsung berubah. Tidak ada salinan di jalur ini.

Buffer Protocol dengan memoryview #

Python punya mekanisme standar untuk berbagi memori antarobjek: buffer protocol. ndarray mendukungnya, sehingga array dapat dibungkus memoryview atau diterima fungsi mana pun yang mengharuskan objek buffer — tanpa konversi dan tanpa salinan:

python
import numpy as np

v = np.array([10, 20, 30], dtype=np.int32)
mv = memoryview(v)
print(mv.format, mv.shape)
print(bytes(mv) == v.tobytes())

Output yang Dihasilkan:

text
i (3,)
True

Bedah singkat:

  1. Baris 4 (memoryview(v)): mv menampung buffer yang sama dengan v; atribut format bernilai "i" (kode struct untuk int32) dan shape (3,).
  2. Baris 5: bytes(mv) mengekspor isi buffer; hasilnya identik dengan v.tobytes(). Objek seperti bytes, bytearray, dan array.array juga mendukung buffer protocol, sehingga pertukaran dua arah dengan array NumPy mengalir tanpa salinan.

Ekosistem di Sekitar Jembatan Ini #

JalurArahPeran utama
np.frombufferbyte luar -> arraymenafsirkan buffer read-only sesuai dtype
arr.ctypesarray -> pointer Cmengoper array ke fungsi ctypes
buffer protocoldua arahberbagi memori dengan objek Python
np.lib.formatfile .npyformat biner bawaan NumPy yang memuat dtype + shape