Kerangka Bagian 1 sudah bisa menambah dan menampilkan tugas. Dua operasi tersisa — menandai selesai dan menghapus — justru bagian yang paling mudah salah: keduanya bekerja berdasarkan nomor yang diketik pengguna, dan nomor yang tidak valid tidak boleh membuat program berhenti. Pada bagian ini kita lengkapi kedua operasi tersebut sambil memperketat kehati-hatian dalam membaca dan mencari posisi elemen di dalam list.
Daftar dengan Penanda dan Pesan Kosong #
Fungsi tampilkan_daftar kita rombak menjadi dua hal: tampilkan [x] untuk tugas selesai dan [ ] untuk yang belum, serta beri pesan jelas ketika daftar masih kosong:
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']}")Tiga hal baru terjadi di sini:
if not daftar_tugas:: list kosong bernilaiFalsepada konteks boolean, jadi pemeriksaan ini menangkap kondisi belum ada tugas. Tanpa perlindungan ini, loopforhanya diam dan pengguna tidak tahu apakah program bekerja atau macet.returndini: begitu pesan kosong tercetak, fungsi berhenti — sisa kode di bawahnya tidak perlu dijalankan. Struktur “periksa kondisi aneh dulu, kerjakan kasus normal setelahnya” menjaga indentasi tetap dangkal."[x]" if tugas["selesai"] else "[ ]": conditional expression memilih penanda dalam satu baris. Nilaitugas["selesai"]selaluTrueatauFalsekarena kita yang membuat datanya — Bagian 4 nanti akan memvalidasi kenyataan itu ketika data datang dari luar.
Membaca Nomor dengan Aman #
Kedua operasi baru meminta satu angka dari pengguna. Masalahnya, input() selalu mengembalikan str, dan int("abc") membangkitkan ValueError:
def baca_nomor():
try:
return int(input("Nomor tugas: ").strip())
except ValueError:
print("Nomor tugas harus berupa angka.")
return NoneAlurnya: fungsi int() mencoba mengubah hasil input() menjadi bilangan bulat. Ketika teks bukan angka, interpreter Python membangkitkan ValueError, blok except menangkapnya, dan fungsi mengembalikan None sebagai tanda “tidak ada nomor yang valid”. Pemanggil kemudian memeriksa if nomor is not None sebelum melanjutkan — pola try/except ini dibahas tuntas pada materi try dan except.
Menandai Selesai dan Menghapus #
Kedua operasi mencari tugas berdasarkan id, bukan posisi dalam list. Nomor yang tampil di layar adalah id tugas, sehingga nomor tersebut tidak pernah bergeser ketika tugas lain dihapus:
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 FalsePerhatikan tiga keputusan pentingnya:
return True/return False: fungsi melaporkan apakah operasi benar-benar mengubah data. Bagian 3 akan memakai nilai kembalian ini untuk memutuskan kapan berkas penyimpanan perlu ditulis ulang.enumerate(daftar_tugas): fungsihapus_tugasbutuh indeks karena methodpop(indeks)menghapus berdasarkan posisi, bukan nilai.enumerate()menyediakan pasangan(indeks, elemen)sekaligus — materi enumerate() membahasnya lebih lanjut.- Menghapus lewat
pop(indeks)setelah loop menemukan target: menghapus elemen di tengah iterasi list yang sama berisiko melewatkan elemen berikutnya. Di sinipop()dipanggil tepat sebelumreturn True, sehingga loop langsung berhenti dan tidak melanjutkan iterasi pada list yang telah berubah.
Nomor yang Tidak Dipakai Ulang #
Perhitungan len(daftar_tugas) + 1 dari Bagian 1 kini rusak: setelah Beli susu (id 2) dihapus dari tiga tugas, panjang list tinggal dua dan angka 3 akan dipakai ulang — padahal Kirim laporan bulanan sudah memegang id 3. Solusinya, hitung nomor baru dari id terbesar yang masih ada:
def nomor_id_baru(daftar_tugas):
if not daftar_tugas:
return 1
return max(tugas["id"] for tugas in daftar_tugas) + 1Guard if not daftar_tugas: return 1 wajib ada karena max() pada sequence kosong membangkitkan ValueError. Setelah fungsi ini dipasang, tambah_tugas memanggilnya menggantikan len(daftar_tugas) + 1.
Program Lengkap #
Berikut main.py setelah seluruh perubahan Bagian 2. Dua cabang menu yang tadinya placeholder kini benar-benar bekerja — berkas yang sama juga tersedia pada bagian Berkas Sumber di bawah halaman:
"""Pengelola Daftar Tugas — program manajemen tugas berbasis terminal."""
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 = []
while True:
tampilkan_menu()
pilihan = input("Pilihan: ").strip()
if pilihan == "1":
judul = input("Judul tugas: ").strip()
if judul:
tambah_tugas(daftar_tugas, judul)
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:
tandai_selesai(daftar_tugas, nomor)
elif pilihan == "4":
nomor = baca_nomor()
if nomor is not None:
hapus_tugas(daftar_tugas, nomor)
elif pilihan == "5":
print("Sampai jumpa!")
break
else:
print("Pilihan tidak dikenal.")
if __name__ == "__main__":
main()Cabang 3 dan 4 kini membaca nomor lewat baca_nomor(), dan nilai None dilewati dengan pemeriksaan if nomor is not None — masukan salah hanya membuahkan pesan, bukan kegagalan program.
Sesi Uji #
Sesi berikut menyentuh semua jalur: operasi berhasil, nomor yang tidak ada, nomor yang bukan angka, penghapusan, lalu penambahan baru:
echo "1
Belajar Python dasar
1
Beli susu
1
Kirim laporan bulanan
3
1
3
9
3
abc
4
2
1
Telepon bengkel
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: Judul tugas: Tugas "Kirim laporan bulanan" tersimpan dengan id 3.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas 1 ditandai selesai.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas dengan nomor 9 tidak ditemukan.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Nomor tugas harus berupa angka.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas "Beli susu" dihapus.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Telepon bengkel" tersimpan dengan id 4.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: [x] 1. Belajar Python dasar
[ ] 3. Kirim laporan bulanan
[ ] 4. Telepon bengkel
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Sampai jumpa!Perhatikan tiga momen penting pada keluaran di atas: tugas id 1 tampil dengan penanda [x], nomor 9 dan abc hanya membuahkan pesan tanpa menghentikan program, dan tugas baru setelah penghapusan mendapat id 4 — id 2 yang telah dihapus tidak dipakai ulang karena nomor_id_baru menghitung dari id terbesar yang tersisa.
Empat operasi kini lengkap, tetapi data masih lenyap begitu program berakhir. Bagian 3 memindahkan penyimpanan ke berkas JSON agar daftar tugas bertahan antar eksekusi.
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."""
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 = []
while True:
tampilkan_menu()
pilihan = input("Pilihan: ").strip()
if pilihan == "1":
judul = input("Judul tugas: ").strip()
if judul:
tambah_tugas(daftar_tugas, judul)
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:
tandai_selesai(daftar_tugas, nomor)
elif pilihan == "4":
nomor = baca_nomor()
if nomor is not None:
hapus_tugas(daftar_tugas, nomor)
elif pilihan == "5":
print("Sampai jumpa!")
break
else:
print("Pilihan tidak dikenal.")
if __name__ == "__main__":
main()