Program kita kini lengkap secara fitur, tetapi seluruh daftar tugas lenyap begitu program berakhir — daftar_tugas hanyalah data di memori. Pada bagian ini kita memberi program ingatan permanen: setiap perubahan ditulis ke berkas tugas.json, dan setiap kali program dijalankan ulang, isi berkas tersebut dimuat kembali. Sekaligus kita memisahkan seluruh urusan penyimpanan ke module tersendiri agar main.py tetap fokus pada interaksi pengguna.
Module storage.py #
Semua kode yang membaca dan menulis berkas kita pindahkan ke storage.py:
"""Modul penyimpanan daftar tugas ke berkas JSON."""
import json
def simpan_tugas(daftar_tugas, nama_file):
with open(nama_file, "w", encoding="utf-8") as berkas:
json.dump(daftar_tugas, berkas, ensure_ascii=False, indent=2)
def muat_tugas(nama_file):
try:
with open(nama_file, "r", encoding="utf-8") as berkas:
return json.load(berkas)
except FileNotFoundError:
return []Pemisahan ini disebut separation of concerns: main.py menangani menu dan percakapan dengan pengguna, sedangkan storage.py mengetahui detail format JSON dan nama berkas. Bila kelak format penyimpanan berganti — misalnya ke SQLite — hanya storage.py yang perlu ditulis ulang. Materi Membuat Module Sendiri menjelaskan bahwa berkas Python apa pun adalah module; cukup diletakkan di folder yang sama dengan main.py agar pernyataan import storage berhasil. Kedua berkas tahap ini — main.py dan storage.py — juga tersedia pada bagian Berkas Sumber di bawah halaman.
Mari kita bedah kedua fungsinya:
with open(nama_file, "w", encoding="utf-8") as berkas:: mode"w"membuka berkas untuk ditulis dan menghapus isi lamanya; context managerwithmenutup berkas otomatis meski terjadi exception di tengah proses. Parameterencoding="utf-8"memastikan judul berbahasa Indonesia tersimpan tanpa bergantung pada pengaturan locale mesin.json.dump(daftar_tugas, berkas, ...): fungsidump()dari modulejsonmengubah list of dictionary menjadi teks JSON lalu menuliskannya langsung ke file object. Argumenensure_ascii=Falsemenjaga huruf tetap tersimpan sebagai UTF-8 yang terbaca manusia, sedangkanindent=2merapikan keluaran dengan indentasi dua spasi per tingkat.json.load(berkas)padamuat_tugasmelakukan kebalikannya: membaca seluruh isi berkas dan mengubah teks JSON kembali menjadi list of dictionary — bentuk data yang persis sama seperti sebelum disimpan.except FileNotFoundError: return []: pada eksekusi pertama,tugas.jsonbelum pernah dibuat, sehinggaopen()membangkitkanFileNotFoundError. Kondisi ini bukan galat yang perlu menghentikan program — berkas hilang berarti penyimpanan memang kosong, jadi fungsi mengembalikan list kosong.
Program Utama yang Tersambung #
main.py kini memanggil module tersebut. Berikut isi lengkapnya setelah perubahan Bagian 3 — seluruh fungsi operasi dari Bagian 2 tidak berubah, jadi perhatian kita tertuju pada tiga titik sambungan baru:
"""Pengelola Daftar Tugas — program manajemen tugas berbasis terminal."""
import storage
NAMA_BERKAS = "tugas.json"
def tampilkan_menu():
print("=== Pengelola Daftar Tugas ===")
print("1. Tambah tugas")
print("2. Daftar tugas")
print("3. Tandai selesai")
print("4. Hapus tugas")
print("5. Keluar")
def nomor_id_baru(daftar_tugas):
if not daftar_tugas:
return 1
return max(tugas["id"] for tugas in daftar_tugas) + 1
def tambah_tugas(daftar_tugas, judul):
id_baru = nomor_id_baru(daftar_tugas)
tugas_baru = {"id": id_baru, "judul": judul, "selesai": False}
daftar_tugas.append(tugas_baru)
print(f"Tugas \"{judul}\" tersimpan dengan id {id_baru}.")
def tampilkan_daftar(daftar_tugas):
if not daftar_tugas:
print("Belum ada tugas.")
return
for tugas in daftar_tugas:
penanda = "[x]" if tugas["selesai"] else "[ ]"
print(f"{penanda} {tugas['id']}. {tugas['judul']}")
def baca_nomor():
try:
return int(input("Nomor tugas: ").strip())
except ValueError:
print("Nomor tugas harus berupa angka.")
return None
def tandai_selesai(daftar_tugas, nomor):
for tugas in daftar_tugas:
if tugas["id"] == nomor:
tugas["selesai"] = True
print(f"Tugas {nomor} ditandai selesai.")
return True
print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
return False
def hapus_tugas(daftar_tugas, nomor):
for indeks, tugas in enumerate(daftar_tugas):
if tugas["id"] == nomor:
dihapus = daftar_tugas.pop(indeks)
print(f"Tugas \"{dihapus['judul']}\" dihapus.")
return True
print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
return False
def main():
daftar_tugas = storage.muat_tugas(NAMA_BERKAS)
while True:
tampilkan_menu()
pilihan = input("Pilihan: ").strip()
if pilihan == "1":
judul = input("Judul tugas: ").strip()
if judul:
tambah_tugas(daftar_tugas, judul)
storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)
else:
print("Judul tugas tidak boleh kosong.")
elif pilihan == "2":
tampilkan_daftar(daftar_tugas)
elif pilihan == "3":
nomor = baca_nomor()
if nomor is not None:
if tandai_selesai(daftar_tugas, nomor):
storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)
elif pilihan == "4":
nomor = baca_nomor()
if nomor is not None:
if hapus_tugas(daftar_tugas, nomor):
storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)
elif pilihan == "5":
print("Sampai jumpa!")
break
else:
print("Pilihan tidak dikenal.")
if __name__ == "__main__":
main()Tiga sambungan baru inilah pekerjaan Bagian 3:
import storagedanNAMA_BERKAS = "tugas.json": nama berkas penyimpanan disimpan pada satu konstanta agar mudah diganti tanpa menyentuh logika. Seluruh fungsi operasi dari Bagian 2 tetap berada di berkas yang sama tanpa perubahan.daftar_tugas = storage.muat_tugas(NAMA_BERKAS): pemuatan terjadi tepat sekali di awalmain(). Sejak baris ini,daftar_tugasberisi data dari eksekusi sebelumnya — atau list kosong pada eksekusi pertama.storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)setelah setiap mutasi: penyimpanan dipanggil hanya ketika data benar-benar berubah. Nilai kembalianTruedaritandai_selesai()danhapus_tugas()menjadi gerbangnya — inilah alasan fungsi operasi pada Bagian 2 sengaja melaporkan status keberhasilannya.
Sesi Uji: Data yang Bertahan #
Kita mulai dari folder kosong tanpa tugas.json, menambah dua tugas, menandai yang kedua selesai, lalu keluar:
echo "1
Belajar Python dasar
1
Beli susu
3
2
5" | python3 main.pyOutput yang Dihasilkan:
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Belajar Python dasar" tersimpan dengan id 1.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Beli susu" tersimpan dengan id 2.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas 2 ditandai selesai.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Sampai jumpa!Perhatikan: tidak ada pesan galat meski tugas.json belum ada — cabang FileNotFoundError bekerja. Isi berkas yang tercipta dapat diperiksa dengan cat:
cat tugas.jsonOutput yang Dihasilkan:
[
{
"id": 1,
"judul": "Belajar Python dasar",
"selesai": false
},
{
"id": 2,
"judul": "Beli susu",
"selesai": true
}
]Struktur di layar persis seperti bentuk data di memori: satu list berisi dictionary dengan kunci id, judul, dan selesai. Nilai True pada Python menjadi true pada JSON, dan sebaliknya json.load() mengubahnya kembali menjadi True. Sekarang jalankan program untuk kedua kalinya:
echo "2
5" | python3 main.pyOutput yang Dihasilkan:
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: [ ] 1. Belajar Python dasar
[x] 2. Beli susu
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Sampai jumpa!Kedua tugas kembali tampil — lengkap dengan penanda [x] pada Beli susu yang ditandai pada eksekusi sebelumnya. Data kini bertahan antar eksekusi.
Penyimpanan permanen telah berdiri: setiap perubahan langsung ditulis, dan setiap eksekusi memuat data terakhir. Yang tersisa adalah membuat proses pemuatan itu tahan terhadap berkas yang hilang, rusak, atau berisi data yang tidak sah — pekerjaan Bagian 4.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
main.py
Unduh berkas"""Pengelola Daftar Tugas — program manajemen tugas berbasis terminal."""
import storage
NAMA_BERKAS = "tugas.json"
def tampilkan_menu():
print("=== Pengelola Daftar Tugas ===")
print("1. Tambah tugas")
print("2. Daftar tugas")
print("3. Tandai selesai")
print("4. Hapus tugas")
print("5. Keluar")
def nomor_id_baru(daftar_tugas):
if not daftar_tugas:
return 1
return max(tugas["id"] for tugas in daftar_tugas) + 1
def tambah_tugas(daftar_tugas, judul):
id_baru = nomor_id_baru(daftar_tugas)
tugas_baru = {"id": id_baru, "judul": judul, "selesai": False}
daftar_tugas.append(tugas_baru)
print(f"Tugas \"{judul}\" tersimpan dengan id {id_baru}.")
def tampilkan_daftar(daftar_tugas):
if not daftar_tugas:
print("Belum ada tugas.")
return
for tugas in daftar_tugas:
penanda = "[x]" if tugas["selesai"] else "[ ]"
print(f"{penanda} {tugas['id']}. {tugas['judul']}")
def baca_nomor():
try:
return int(input("Nomor tugas: ").strip())
except ValueError:
print("Nomor tugas harus berupa angka.")
return None
def tandai_selesai(daftar_tugas, nomor):
for tugas in daftar_tugas:
if tugas["id"] == nomor:
tugas["selesai"] = True
print(f"Tugas {nomor} ditandai selesai.")
return True
print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
return False
def hapus_tugas(daftar_tugas, nomor):
for indeks, tugas in enumerate(daftar_tugas):
if tugas["id"] == nomor:
dihapus = daftar_tugas.pop(indeks)
print(f"Tugas \"{dihapus['judul']}\" dihapus.")
return True
print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
return False
def main():
daftar_tugas = storage.muat_tugas(NAMA_BERKAS)
while True:
tampilkan_menu()
pilihan = input("Pilihan: ").strip()
if pilihan == "1":
judul = input("Judul tugas: ").strip()
if judul:
tambah_tugas(daftar_tugas, judul)
storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)
else:
print("Judul tugas tidak boleh kosong.")
elif pilihan == "2":
tampilkan_daftar(daftar_tugas)
elif pilihan == "3":
nomor = baca_nomor()
if nomor is not None:
if tandai_selesai(daftar_tugas, nomor):
storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)
elif pilihan == "4":
nomor = baca_nomor()
if nomor is not None:
if hapus_tugas(daftar_tugas, nomor):
storage.simpan_tugas(daftar_tugas, NAMA_BERKAS)
elif pilihan == "5":
print("Sampai jumpa!")
break
else:
print("Pilihan tidak dikenal.")
if __name__ == "__main__":
main()
storage.py
Unduh berkas"""Modul penyimpanan daftar tugas ke berkas JSON."""
import json
def simpan_tugas(daftar_tugas, nama_file):
with open(nama_file, "w", encoding="utf-8") as berkas:
json.dump(daftar_tugas, berkas, ensure_ascii=False, indent=2)
def muat_tugas(nama_file):
try:
with open(nama_file, "r", encoding="utf-8") as berkas:
return json.load(berkas)
except FileNotFoundError:
return []