Teks bukan satu-satunya penghuni disk: gambar, audio, video, arsip zip, dan database menyimpan byte-byte yang tidak dimaksudkan sebagai teks. Membuka berkas tersebut dengan mode "r" biasa bukan hanya salah — prosesnya akan gagal di tengah jalan karena byte sembarangan tidak selalu membentuk teks yang sah. Chapter ini membuka berkas sebagaimana adanya: bytes mentah, dibaca dengan mode "rb" dan ditulis dengan mode "wb". Posisi ini juga melengkapi gambaran str vs bytes dari Chapter Unicode dan Encoding.
Mode rb dan wb #
Dalam mode biner, method write() menerima object bytes dan read() mengembalikan bytes — tanpa penerjemahan encoding, tanpa penerjemahan newline:
data = b"Halo, file biner!\nBaris kedua.\n"
with open("catatan-biner.dat", "wb") as berkas:
jumlah = berkas.write(data)
print("Byte yang ditulis:", jumlah)
with open("catatan-biner.dat", "rb") as berkas:
isi = berkas.read()
print(type(isi).__name__)
print(isi)
print(repr(isi))Output yang Dihasilkan:
Byte yang ditulis: 31
bytes
b'Halo, file biner!\nBaris kedua.\n'
b'Halo, file biner!\nBaris kedua.\n'Perhatikan dua hal:
- Baris 1 (
data = b"Halo, file biner!\nBaris kedua.\n"): prefixbmembuat literal menjadibytes, bukanstr. Mode"wb"menolak string biasa — konversistr↔bytesharus eksplisit lewatencode()/decode(). write()mengembalikan 31: di mode biner, nilai balik dihitung dalam byte, bukan karakter. Keduanya sama untuk teks ASCII, tetapi untuk karakter multi-byte jumlahnya berbeda — ingat kasuséyang memakan dua byte pada UTF-8.
Setiap berkas pada dasarnya adalah berkas biner; mode teks hanyalah lapisan penerjemah di atasnya. Ketika kita memilih "rb"/"wb", kita mematikan penerjemah dan bekerja dengan isi mentahnya.
Mengapa Berkas Biner Tidak Bisa Dibaca sebagai Teks #
Mencoba menterjemahkan byte sembarangan sebagai teks berakhir buruk:
>>> b"\x89PNG\r\n\x1a\n".decode("utf-8")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteByte pertama 0x89 bukan awal karakter UTF-8 yang sah, dan proses dekode langsung berhenti. Kejadiannya identik dengan galat berkas Latin-1 pada Chapter Encoding dan Newline — bedanya, kali ini tidak ada encoding “yang benar” untuk dicoba, karena byte tersebut memang bukan teks melainkan bagian struktur format biner. Satu-satunya cara membaca data biner adalah memahami format-nya: kesepakatan byte ke-berapa berisi apa.
struct — Membaca dan Menulis Angka Biner #
Format biner lazim menyimpan angka tetap lebar: 4 byte untuk integer besar, 2 byte untuk integer kecil. Module struct mengubah angka Python menjadi byte dan sebaliknya:
import struct
header = struct.pack("<4sHH", b"DATA", 7, 2025)
print(header, "panjang", len(header))
kode, versi, tahun = struct.unpack("<4sHH", header)
print(kode, versi, tahun)Output yang Dihasilkan:
b'DATA\x07\x00\xe9\x07' panjang 8
b'DATA' 7 2025String format "<4sHH" adalah kontrak yang dibaca dari kiri:
<: byte order little-endian — byte rendah dulu; simbol>berarti big-endian (byte tinggi dulu).4s: empat byte data mentah, cocok untuk kode sepertib"DATA".Hdua kali: dua integer 16-bit tanpa tanda — angka7menjadi\x07\x00dan angka2025menjadi\xe9\x07dalam little-endian.
Hasil pack() persis 8 byte, dan unpack() mengembalikan tuple dengan komponen-komponennya. Inilah cara program membaca header berkas, packet jaringan, dan rekaman data biner lain.
Bedah Header PNG #
Sebagai contoh nyata pertama, mari kita buat berkas PNG berukuran 69 byte — satu piksel merah — lalu membedah dua bagian pertamanya: signature dan chunk IHDR:
import struct
import zlib
def buat_png_merah():
header = struct.pack(">IIBBBBB", 1, 1, 8, 2, 0, 0, 0)
ihdr = struct.pack(">I", len(header)) + b"IHDR" + header
ihdr += struct.pack(">I", zlib.crc32(b"IHDR" + header))
piksel = zlib.compress(b"\x00\xff\x00\x00")
idat = struct.pack(">I", len(piksel)) + b"IDAT" + piksel
idat += struct.pack(">I", zlib.crc32(b"IDAT" + piksel))
iend = b"\x00\x00\x00\x00IEND" + struct.pack(">I", zlib.crc32(b"IEND"))
return b"\x89PNG\r\n\x1a\n" + ihdr + idat + iend
with open("piksel.png", "wb") as berkas:
berkas.write(buat_png_merah())
with open("piksel.png", "rb") as berkas:
signature = berkas.read(8)
panjang = berkas.read(4)
jenis = berkas.read(4)
print("Signature :", signature)
print("Chunk :", jenis, "ukuran", struct.unpack(">I", panjang)[0])
info = struct.unpack(">IIBBBBB", berkas.read(13))
print("Lebar :", info[0])
print("Tinggi :", info[1])
print("Detail : bit depth", info[2], ", color type", info[3])
print("Ukuran berkas:", len(open("piksel.png", "rb").read()), "byte")Output yang Dihasilkan:
Signature : b'\x89PNG\r\n\x1a\n'
Chunk : b'IHDR' ukuran 13
Lebar : 1
Tinggi : 1
Detail : bit depth 8 , color type 2
Ukuran berkas: 69 byteStruktur PNG terbaca langsung dari kode tersebut:
- 8 byte signature
b"\x89PNG\r\n\x1a\n": penanda khas yang sama pada semua berkas PNG — alat deteksi tipe berkas memakai byte ini, bukan sekadar ekstensi.png. - Setiap chunk berpola
[panjang][jenis][data][CRC32]: panjang 4 byte big-endian (>), jenis 4 huruf, data, lalu checksumzlib.crc32()untuk mendeteksi kerusakan. - Chunk
IHDRselebar 13 byte:>IIBBBBBberisi lebar, tinggi, bit depth, color type, dan tiga byte pengaturan. Nilai(1, 1, 8, 2, ...)membaca: gambar 1x1 piksel, 8 bit per kanal, color type 2 (RGB).
Fungsi buat_png_merah() mengemas scanline piksel merah \xff\x00\x00 lewat zlib.compress() — inti kompresi PNG — lalu menyusun ketiga chunk sesuai spesifikasi. Hasilnya berkas PNG sungguhan yang bisa dibuka aplikasi gambar apa pun: biner yang utuh, dibangun murni dari bytes dan struct.