Bab 13 — File dan I/O Dasar: Data yang Tidak Hilang Saat Program Ditutup
Seri B3 — Program Kecil yang Berguna
Semua skrip bab ini ada di folder kode/bab13/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip). Skrip yang membuat berkas menulisnya ke subfolder _tmp/ di samping skrip, jadi folder Anda tidak berantakan.
Sejauh ini, semua data hidup di memori komputer, seperti catatan di papan tulis: begitu program ditutup, tulisannya terhapus. Petugas lapangan yang baik mencatat di buku, yaitu berkas (file) di disk, yang tetap ada walau komputer dimatikan. Bab ini mengajarkan program Anda menulis dan membaca buku itu. Istilah umumnya I/O (input/output): data masuk dari berkas, data keluar ke berkas.

Membaca berkas teks
02_baca_teks.py
# Bab 13 - Empat cara membaca berkas teks
from pathlib import Path
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
berkas = tmp / "catatan.txt"
berkas.write_text("Catatan lapangan 1 Oktober\nPlot A: 12 pohon jati\nPlot B: 9 pohon meranti\n",
encoding="utf-8")
with open(berkas, "r", encoding="utf-8") as f:
semua = f.read() # 1. seluruh isi sebagai satu teks
print(repr(semua))
with open(berkas, encoding="utf-8") as f:
print(repr(f.readline())) # 2. satu baris (dengan \n di ujung)
with open(berkas, encoding="utf-8") as f:
daftar = f.readlines() # 3. semua baris sebagai list
print(len(daftar), "baris")
with open(berkas, encoding="utf-8") as f:
for nomor, baris in enumerate(f, start=1): # 4. baris demi baris (hemat memori)
print(nomor, baris.rstrip())'Catatan lapangan 1 Oktober\nPlot A: 12 pohon jati\nPlot B: 9 pohon meranti\n'
'Catatan lapangan 1 Oktober\n'
3 baris
1 Catatan lapangan 1 Oktober
2 Plot A: 12 pohon jati
3 Plot B: 9 pohon meranti
Empat cara membaca, dari yang paling kasar ke yang paling hemat:
read(): seluruh isi sebagai satu teks. Perhatikan\ndi sela baris (reprmenampakkannya).readline(): satu baris, lengkap dengan\ndi ujung. Dipanggil lagi, ia memberi baris berikutnya.readlines(): semua baris sebagai list.- Perulangan
forlangsung atas berkas: satu baris per putaran, tanpa memuat semuanya ke memori. Ini pilihan terbaik untuk berkas besar.rstrip()membuang\ndi ujungnya, danenumerate(f, start=1)memberi nomor baris.
Mode: w, a, x
03_mode_tambah.py
# Bab 13 - Mode "w" menimpa, mode "a" menambah, mode "x" menolak menimpa
from pathlib import Path
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
berkas = tmp / "log_panen.txt"
with open(berkas, "w", encoding="utf-8") as f:
f.write("Panen 1: 245.5 kg\n")
with open(berkas, "a", encoding="utf-8") as f: # tambah di akhir
f.write("Panen 2: 310.0 kg\n")
print(berkas.read_text(encoding="utf-8"))
with open(berkas, "w", encoding="utf-8") as f: # menimpa: isi lama hilang
f.write("Panen 3: 198.5 kg\n")
print(berkas.read_text(encoding="utf-8"))
try:
with open(berkas, "x", encoding="utf-8") as f: # "x": gagal bila sudah ada
f.write("tidak jadi")
except FileExistsError:
print("Mode x menolak: berkas sudah ada")Panen 1: 245.5 kg
Panen 2: 310.0 kg
Panen 3: 198.5 kg
Mode x menolak: berkas sudah ada
| Mode | Artinya |
|---|---|
"r" |
baca (bawaan); error bila berkas tidak ada |
"w" |
tulis; menimpa isi lama |
"a" |
tambah di akhir; isi lama aman |
"x" |
buat baru; gagal bila berkas sudah ada |
Lihat hasilnya: setelah mode "a", ada dua panen. Setelah mode "w", panen 1 dan 2 hilang, tinggal panen 3. Mode "x" melindungi berkas penting dari tertimpa tanpa sengaja (ia memunculkan FileExistsError, yang kita tangkap dengan try dari Bab 12).
Mode "w" menghapus isi lama tanpa bertanya. Bila Anda ingin menambah catatan harian, pakai "a". Salah memilih satu huruf bisa menghilangkan data berhari-hari.
Mengapa with
04_with_menutup.py
# Bab 13 - Mengapa with: berkas ditutup otomatis, bahkan bila terjadi error
from pathlib import Path
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
berkas = tmp / "contoh.txt"
f = open(berkas, "w", encoding="utf-8") # cara lama: harus menutup sendiri
f.write("halo\n")
print("Sebelum close, tertutup?", f.closed)
f.close()
print("Sesudah close, tertutup?", f.closed)
try:
with open(berkas, "w", encoding="utf-8") as g:
g.write("halo lagi\n")
raise RuntimeError("ada masalah di tengah menulis")
except RuntimeError as e:
print("Error tertangkap:", e)
print("Berkas tetap tertutup rapi?", g.closed)Sebelum close, tertutup? False
Sesudah close, tertutup? True
Error tertangkap: ada masalah di tengah menulis
Berkas tetap tertutup rapi? True
Berkas yang sudah dibuka harus ditutup agar isinya benar-benar tersimpan dan tidak mengunci berkas bagi program lain. Cara lama: open() lalu f.close() sendiri; bila di tengah terjadi error, close() bisa terlewat. Dengan with, penutupan dijamin: pada contoh, error RuntimeError meledak di tengah menulis, tetapi g.closed tetap True.
Biasakan selalu with open(...). Anda hampir tidak pernah perlu menulis close() sendiri.
pathlib untuk berkas dan folder
Di Bab 11 Anda sudah berkenalan dengan Path. Sekarang ia bekerja pada disk sungguhan:
05_pathlib_berkas.py
# Bab 13 - pathlib untuk berkas dan folder sungguhan
from pathlib import Path
tmp = Path(__file__).parent / "_tmp" / "lapangan"
tmp.mkdir(parents=True, exist_ok=True) # buat folder bertingkat; tidak error bila sudah ada
for nama in ["plot_a.txt", "plot_b.txt", "ikan.csv"]:
(tmp / nama).write_text(f"isi {nama}\n", encoding="utf-8")
print("Ada folder?", tmp.is_dir())
print("Semua berkas :", sorted(p.name for p in tmp.iterdir()))
print("Hanya .txt :", sorted(p.name for p in tmp.glob("*.txt")))
print("Ukuran plot_a:", (tmp / "plot_a.txt").stat().st_size, "byte")
lama = tmp / "ikan.csv"
baru = lama.rename(tmp / "ikan_2026.csv")
print("Diganti nama :", baru.name)
baru.unlink() # hapus satu berkas (di folder _tmp milik kita)
print("Tersisa :", sorted(p.name for p in tmp.iterdir()))Ada folder? True
Semua berkas : ['ikan.csv', 'plot_a.txt', 'plot_b.txt']
Hanya .txt : ['plot_a.txt', 'plot_b.txt']
Ukuran plot_a: 16 byte
Diganti nama : ikan_2026.csv
Tersisa : ['plot_a.txt', 'plot_b.txt']
mkdir(parents=True, exist_ok=True)membuat folder bertingkat dan tidak protes bila sudah ada.write_text()danread_text()adalah jalan pintas untuk menulis atau membaca seluruh isi berkas kecil, tanpawith.iterdir()mendaftar isi folder;glob("*.txt")memilih berkas berpola. Kita menyusunnya dengansortedkarena urutan dari disk tidak dijamin.rename()mengganti nama,unlink()menghapus satu berkas,stat().st_sizememberi ukuran dalam byte.
Pakai unlink() dengan hati-hati: berkas yang dihapus dengan cara ini tidak masuk keranjang sampah. Dalam contoh, kita hanya menghapus berkas buatan kita sendiri di folder _tmp.
Encoding: teks yang berubah jadi simbol aneh
Disk hanya menyimpan byte (angka 0 sampai 255), bukan huruf. Encoding adalah aturan yang menerjemahkan huruf menjadi byte dan sebaliknya. Aturan paling dianjurkan adalah UTF-8, yang mampu menulis huruf apa pun, termasuk °, ±, dan aksara non-Latin.
06_encoding_benar.py
# Bab 13 - Teks disimpan sebagai byte; pakai UTF-8 dan tulis secara eksplisit
from pathlib import Path
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
berkas = tmp / "suhu.txt"
teks = "Suhu air 28 °C, Pontianak, hasil ± 5%"
berkas.write_text(teks, encoding="utf-8")
mentah = berkas.read_bytes() # isi berkas apa adanya: deretan byte
print("Jumlah karakter:", len(teks))
print("Jumlah byte :", len(mentah))
print("Byte lambang derajat:", "°".encode("utf-8"))
print("Dibaca balik :", berkas.read_text(encoding="utf-8"))Jumlah karakter: 37
Jumlah byte : 39
Byte lambang derajat: b'\xc2\xb0'
Dibaca balik : Suhu air 28 °C, Pontianak, hasil ± 5%
Teks itu 37 karakter tetapi 39 byte: lambang ° dan ± masing-masing memakai dua byte dalam UTF-8 (b'\xc2\xb0' untuk °). Itu sebabnya “jumlah karakter” dan “jumlah byte” bisa berbeda.
Sekarang yang terjadi bila berkas yang ditulis dengan satu aturan dibaca dengan aturan lain:
07_encoding_salah.py
# Bab 13 - Encoding SALAH: ditulis dengan satu aturan, dibaca dengan aturan lain
from pathlib import Path
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
berkas = tmp / "suhu_utf8.txt"
berkas.write_text("Suhu air 28 °C, hasil ± 5%", encoding="utf-8")
# cp1252 = aturan bawaan banyak komputer Windows berbahasa Barat
print("Dibaca sebagai cp1252:", berkas.read_text(encoding="cp1252"))
try:
berkas.read_text(encoding="ascii") # ascii hanya mengenal 128 lambang
except UnicodeDecodeError as e:
print("Dibaca sebagai ascii -> UnicodeDecodeError:", e.reason)
print("Dibaca sebagai utf-8 :", berkas.read_text(encoding="utf-8"))Dibaca sebagai cp1252: Suhu air 28 °C, hasil ± 5%
Dibaca sebagai ascii -> UnicodeDecodeError: ordinal not in range(128)
Dibaca sebagai utf-8 : Suhu air 28 °C, hasil ± 5%
Hasil °C dan ± itulah mojibake: dua byte UTF-8 dibaca sebagai dua huruf terpisah oleh aturan cp1252. Pada aturan ascii, Python malah menolak dengan UnicodeDecodeError. Hanya pembacaan UTF-8 yang mengembalikan teks asli.
Bila Anda tidak menyebut encoding=, Python memakai aturan bawaan sistem. Di komputer penulis buku ini (Windows), bawaannya cp1252; kami mengeceknya langsung dengan locale.getencoding(). Akibatnya berkas yang ditulis di satu komputer bisa rusak saat dibaca di komputer lain. Mulai Python 3.15 (dirilis 9 Oktober 2026), UTF-8 menjadi bawaan: kami membuktikannya dengan membuka berkas tanpa encoding= di Python 3.15.0, hasilnya utf-8, dan mematikannya dengan PYTHONUTF8=0 mengembalikan cp1252 (lihat Bab 47). Buku ini berpatokan ke Python 3.14, yang masih memakai aturan sistem, jadi tulislah encoding="utf-8" selalu, supaya kode Anda benar di semua versi dan komputer.
Berkas yang tidak ada
08_berkas_tak_ada.py
# Bab 13 - Jebakan: membuka berkas yang tidak ada
with open("data_pohon_tidak_ada.txt", encoding="utf-8") as f:
print(f.read())Traceback (most recent call last):
File "08_berkas_tak_ada.py", line 2, in <module>
with open("data_pohon_tidak_ada.txt", encoding="utf-8") as f:
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'data_pohon_tidak_ada.txt'
FileNotFoundError adalah error paling umum dalam urusan berkas: salah ketik nama, salah folder, atau memang belum dibuat. Dua cara menghadapinya dengan sopan:
09_berkas_tak_ada_aman.py
# Bab 13 - Menghadapi berkas yang tidak ada dengan sopan
from pathlib import Path
def baca_atau_cadangan(berkas, cadangan="(belum ada data)"):
try:
return Path(berkas).read_text(encoding="utf-8")
except FileNotFoundError:
print(f" [info] {Path(berkas).name} tidak ditemukan, memakai cadangan")
return cadangan
print(baca_atau_cadangan("data_pohon_tidak_ada.txt"))
# Cara kedua: periksa dulu dengan exists()
alamat = Path("data_pohon_tidak_ada.txt")
if alamat.exists():
print(alamat.read_text(encoding="utf-8"))
else:
print("Cek exists(): berkas belum ada, lewati") [info] data_pohon_tidak_ada.txt tidak ditemukan, memakai cadangan
(belum ada data)
Cek exists(): berkas belum ada, lewati
Cara pertama memakai try/except (disarankan, karena berkas bisa saja lenyap di antara “dicek” dan “dibuka”). Cara kedua memakai exists(): lebih mudah dibaca, tetapi hanya memeriksa keadaan saat itu.
Proyek Bersambung B3: menyimpan dan memuat data pohon
Sekarang kita gabungkan Bab 12 dan 13. Skrip menyimpan enam pohon ke pohon.txt (satu baris satu pohon, kolom dipisah |) dan sengaja menyisipkan satu baris rusak. Lalu fungsi muat_pohon membacanya kembali dengan tahan salah:
10_proyek_catatan.py
# Bab 13 - Proyek B3: data pohon disimpan ke berkas teks dan dibaca kembali
from pathlib import Path
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
berkas = tmp / "pohon.txt"
pohon = [
("A", "Jati", 32.5), ("A", "Meranti", 45.2), ("B", "Mahoni", 28.0),
("B", "Sengon", 51.5), ("C", "Jati", 36.0), ("C", "Meranti", 40.8),
]
# 1. Simpan: satu pohon satu baris, kolom dipisah tanda |
with open(berkas, "w", encoding="utf-8") as f:
for plot, jenis, diameter in pohon:
f.write(f"{plot}|{jenis}|{diameter}\n")
f.write("C|Jati|rusak\n") # satu baris rusak, sengaja disisipkan
# 2. Baca kembali dengan tahan salah (pelajaran Bab 12)
def muat_pohon(alamat):
hasil, ditolak = [], 0
try:
with open(alamat, encoding="utf-8") as f:
for baris in f:
try:
plot, jenis, diameter = baris.strip().split("|")
hasil.append((plot, jenis, float(diameter)))
except ValueError:
ditolak += 1
except FileNotFoundError:
print("Berkas belum ada, mulai dengan data kosong")
return hasil, ditolak
data, ditolak = muat_pohon(berkas)
print(f"Terbaca {len(data)} pohon, {ditolak} baris ditolak")
print("Rata-rata diameter:", round(sum(d for _, _, d in data) / len(data), 2), "cm")
print("Data pertama:", data[0])
print("Berkas yang tidak ada:", muat_pohon(tmp / "tidak_ada.txt"))Terbaca 6 pohon, 1 baris ditolak
Rata-rata diameter: 39.0 cm
Data pertama: ('A', 'Jati', 32.5)
Berkas belum ada, mulai dengan data kosong
Berkas yang tidak ada: ([], 0)
Enam pohon terbaca, baris C|Jati|rusak ditolak (diameternya bukan angka), rata-rata diameter 39,0 cm (cocok dengan hasil Bab 11). Berkas yang tidak ada menghasilkan data kosong dengan pesan ramah, bukan error. Tetapi format | buatan sendiri ini rapuh: bagaimana bila nama jenis mengandung |? Di Bab 14 kita pakai format standar (CSV dan JSON) yang sudah memecahkan masalah itu.
Latihan
- Tulis skrip yang menulis angka 1 sampai 10 (satu per baris) ke
angka.txt, lalu membacanya kembali dan mencetak jumlahnya. - Jalankan skrip pada latihan 1 dua kali dengan mode
"a"(bukan"w"). Berapa baris isi berkasnya, dan mengapa? - Ubah
07_encoding_salah.py: tulis denganencoding="utf-16", lalu baca dengan"utf-8". Apa yang terjadi? Tangkap error-nya dengantry. - Tantangan: tulis fungsi
catat_panen(berkas, tanggal, kg)yang menambah satu baristanggal|kgke berkas (mode"a"), dan fungsitotal_panen(berkas)yang menjumlahkan kolom kg sambil melewati baris rusak.
Ringkasan Bab
- Berkas menyimpan data di disk; program membacanya (
"r") dan menulisnya ("w","a","x"). - Selalu
with open(..., encoding="utf-8") as f:: berkas tertutup otomatis. - Membaca:
read(),readline(),readlines(), dan perulanganforatas berkas (hemat memori). pathlib.Path:mkdir,read_text,write_text,iterdir,glob,rename,unlink,exists.- Encoding salah menghasilkan simbol aneh atau
UnicodeDecodeError; tulisencoding="utf-8"selalu. - Berkas yang tidak ada: tangkap
FileNotFoundError.
Selanjutnya, di Bab 14: format data standar (CSV dan JSON) agar data kita rapi dan bisa dibaca program lain.