Pendamping Bab 19: Proyek: Aplikasi Manajemen Kontak. Gunakan nomor contoh dan latihan yang sama dengan buku.

Tujuan Belajar

  • Menghubungkan input, fungsi, koleksi, dan penyimpanan.
  • Menguji tambah, cari, ubah, dan hapus kontak.
  • Memeriksa apakah data bertahan setelah program dijalankan ulang.

Persiapan

Gunakan Python di komputer dan folder proyek khusus. Unduh sumber lengkap serta baca petunjuk menjalankan. Gunakan nama dan nomor fiktif untuk pengujian.

Program dengan tombol Coba di IDE dapat dijalankan di browser. Untuk program lokal, unduh sumber, ekstrak ZIP, lalu ikuti README.txt.

Pembahasan

Ikuti pembahasan pada buku, lalu gunakan kegiatan dan materi pendamping berikut untuk memeriksa pemahaman.

Studi Kasus

19-01 – Menguji komponen data kontak

Budi ditambahkan lalu dihapus; nomor Ayu diperbarui. Pengujian kecil ini memeriksa model data sebelum menu dan file dihubungkan.

Coba di IDE Unduh Sumber
kontak = {"Ayu": "081234"}
kontak["Budi"] = "085678"
kontak["Ayu"] = "089999"
del kontak["Budi"]
for nama, nomor in kontak.items():
    print(f"{nama}: {nomor}")

Hasil yang diharapkan:

Ayu: 089999

Coba ubah: Tambahkan Citra dan periksa bahwa memperbarui Ayu tidak mengubah kontak lain.

19-P01 – Aplikasi Kontak Lengkap

Dua bagian code membentuk satu file kontak.py. Jalankan menu, simpan data, lalu buka kembali untuk memeriksa penyimpanan.

Unduh Sumber

Masukan uji, berurutan: '1', 'Ayu', '0812345', '3', 'ayu', '4', 'Ayu', '', '0812999', '2', '7'.

import os
import json  # Ditambahkan untuk opsi penyimpanan JSON

KONTAK_FILE_TXT = "data_kontak.txt"
KONTAK_FILE_JSON = "data_kontak.json"
DATA_FORMAT = "json"  # Bisa diganti ke "txt"


def load_kontak():
    kontak = {}
    if DATA_FORMAT == "json":
        file_to_load = KONTAK_FILE_JSON
    else:
        file_to_load = KONTAK_FILE_TXT

    if not os.path.exists(file_to_load):
        return kontak

    try:
        if DATA_FORMAT == "json":
            with open(file_to_load, "r", encoding="utf-8") as file:
                kontak = json.load(file)
                if not isinstance(kontak, dict) or not all(
                    isinstance(k, str) and isinstance(v, str)
                    for k, v in kontak.items()
                ):
                    raise ValueError(
                        "Data kontak harus pasangan teks."
                    )
        else:  # Format TXT
            with open(file_to_load, "r", encoding="utf-8") as file:
                for baris in file:
                    if baris.strip():  # Pastikan baris tidak kosong
                        try:
                            baris_bersih = baris.strip()
                            nama, no = baris_bersih.split(",", 1)
                            kontak[nama] = no
                        except ValueError:
                            msg = "Peringatan: Baris "
                            msg += f"'{baris.strip()}'"
                            msg += f" dalam {file_to_load}"
                            msg += " tidak valid dan akan dilewati."
                            print(msg)
    except (OSError, ValueError) as e:
        print(f"Error saat memuat kontak dari {file_to_load}: {e}")
        return {}
    return kontak
def simpan_kontak(kontak):
    if DATA_FORMAT == "json":
        file_to_save = KONTAK_FILE_JSON
    else:
        file_to_save = KONTAK_FILE_TXT
    try:
        if DATA_FORMAT == "json":
            with open(file_to_save, "w", encoding="utf-8") as file:
                json.dump(kontak, file, indent=4)
        else:  # Format TXT
            with open(file_to_save, "w", encoding="utf-8") as file:
                for nama, no in kontak.items():
                    file.write(f"{nama},{no}\n")
        return True
    except OSError as e:
        print(f"Error saat menyimpan kontak ke {file_to_save}: {e}")
        return False


def tampilkan_menu():
    print("\n--- Menu Aplikasi Kontak ---")
    print("1. Tambah Kontak Baru")
    print("2. Lihat Semua Kontak")
    print("3. Cari Kontak")
    print("4. Edit Kontak")
    print("5. Hapus Kontak")
    format_info = f"6. Ganti Format Penyimpanan "
    format_info += f"(Saat ini: {DATA_FORMAT.upper()})"
    print(format_info)
    print("7. Keluar")


def tambah_kontak(kontak):
    nama = input("Masukkan Nama Kontak: ").strip()
    if not nama:
        print("Nama tidak boleh kosong.")
        return
    if nama in kontak:
        print("Kontak dengan nama tersebut sudah ada.")
        print("Gunakan fitur edit jika ingin mengubah.")
        return
    no = input("Masukkan No HP: ").strip()
    if not (
        no.isascii() and no.isdecimal()
    ):  # Validasi nomor HP harus angka
        print("Nomor HP harus berupa angka.")
        return
    kontak[nama] = no
    print(f"Kontak '{nama}' berhasil ditambahkan.")


def tampilkan_kontak(kontak):
    if not kontak:
        print("Belum ada kontak yang tersimpan.")
        return
    print("\n--- Daftar Kontak ---")
    # Menggunakan enumerate untuk nomor urut yang lebih rapi
    for i, (nama, no) in enumerate(kontak.items(), 1):
        print(f"{i}. {nama}: {no}")


def cari_kontak(kontak):
    if not kontak:
        print("Belum ada kontak untuk dicari.")
        return
    prompt = "Masukkan nama kontak yang ingin dicari: "
    nama_dicari = input(prompt).strip()
    if nama_dicari in kontak:
        hasil = f"Kontak ditemukan -> {nama_dicari}: "
        hasil += kontak[nama_dicari]
        print(hasil)
    else:
        # Pencarian case-insensitive sederhana
        ketemu_case_insensitive = False
        for nama, no in kontak.items():
            if nama.lower() == nama_dicari.lower():
                print(f"Kontak ditemukan -> {nama}: {no}")
                ketemu_case_insensitive = True
                break
        if not ketemu_case_insensitive:
            print(f"Kontak '{nama_dicari}' tidak ditemukan.")


def edit_kontak(kontak):
    if not kontak:
        print("Belum ada kontak untuk diedit.")
        return
    prompt = "Masukkan nama kontak yang ingin diedit: "
    nama_edit = input(prompt).strip()
    if nama_edit not in kontak:
        print(f"Kontak '{nama_edit}' tidak ditemukan.")
        return

    print(f"Kontak saat ini: {nama_edit}: {kontak[nama_edit]}")
    prompt_nama = f"Masukkan nama baru (kosongkan jika tidak "
    prompt_nama += f"ingin mengubah nama '{nama_edit}'): "
    nama_baru = input(prompt_nama).strip()
    target_nama = nama_baru or nama_edit
    prompt_no = f"Masukkan nomor HP baru untuk '{target_nama}' "
    prompt_no += "(kosongkan jika tidak ingin mengubah): "
    no_baru = input(prompt_no).strip()

    if no_baru and not (no_baru.isascii() and no_baru.isdecimal()):
        print("Nomor HP harus berupa angka 0-9.")
        return

    current_no = kontak[nama_edit]  # Simpan nomor HP saat ini

    # Logika untuk mengubah nama dan/atau nomor
    if nama_baru and nama_baru != nama_edit:
        if nama_baru in kontak:  # Cek apakah nama baru sudah ada
            print(f"Nama kontak '{nama_baru}' sudah ada.")
            print("Tidak bisa mengubah.")
            return
        # Hapus entri lama, tambahkan entri baru
        del kontak[nama_edit]
        kontak[nama_baru] = no_baru if no_baru else current_no
        print(f"Kontak '{nama_edit}' diubah menjadi '{nama_baru}'.")
    elif no_baru:
        kontak[nama_baru or nama_edit] = no_baru
        nama_target = nama_baru or nama_edit
        print(f"Nomor HP untuk '{nama_target}' berhasil diubah.")
    else:
        print("Tidak ada perubahan dilakukan.")


def hapus_kontak(kontak):
    if not kontak:
        print("Belum ada kontak untuk dihapus.")
        return
    prompt = "Masukkan nama kontak yang ingin dihapus: "
    nama_hapus = input(prompt).strip()
    if nama_hapus in kontak:
        del kontak[nama_hapus]  # Hapus kontak dari dictionary
        print(f"Kontak '{nama_hapus}' berhasil dihapus.")
    else:
        print(f"Kontak '{nama_hapus}' tidak ditemukan.")


def ganti_format_penyimpanan():
    global DATA_FORMAT
    print(f"Format saat ini: {DATA_FORMAT.upper()}")
    pilihan_format = input("Pilih format baru ('txt' atau 'json'): ")
    pilihan_format = pilihan_format.lower().strip()
    if pilihan_format in ["txt", "json"]:
        DATA_FORMAT = pilihan_format
        print(f"Format penyimpanan diubah ke: {DATA_FORMAT.upper()}")
        print("Perubahan format efektif saat menyimpan berikutnya.")
        print("Disarankan untuk memuat ulang data jika kamu")
        print("sudah memiliki file dalam format baru atau")
        print("ingin mengkonversi data lama.")
    else:
        print("Pilihan format tidak valid.")


def main():
    kontak = load_kontak()
    while True:
        tampilkan_menu()
        pilihan = input("Pilih menu (1-7): ").strip()

        if pilihan == "1":
            tambah_kontak(kontak)
        elif pilihan == "2":
            tampilkan_kontak(kontak)
        elif pilihan == "3":
            cari_kontak(kontak)
        elif pilihan == "4":
            edit_kontak(kontak)
        elif pilihan == "5":
            hapus_kontak(kontak)
        elif pilihan == "6":
            ganti_format_penyimpanan()
        elif pilihan == "7":
            if simpan_kontak(kontak):
                print("Data kontak berhasil disimpan. Sampai jumpa!")
                break
            print(
                "Data belum tersimpan. Periksa folder lalu coba lagi."
            )
        else:
            print("Pilihan tidak valid.")
            print("Silakan masukkan angka antara 1-7.")


if __name__ == "__main__":
    main()

Hasil yang diharapkan:


--- Menu Aplikasi Kontak ---
1. Tambah Kontak Baru
2. Lihat Semua Kontak
3. Cari Kontak
4. Edit Kontak
5. Hapus Kontak
6. Ganti Format Penyimpanan (Saat ini: JSON)
7. Keluar
Pilih menu (1-7): Masukkan Nama Kontak: Masukkan No HP: Kontak 'Ayu' berhasil ditambahkan.

--- Menu Aplikasi Kontak ---
1. Tambah Kontak Baru
2. Lihat Semua Kontak
3. Cari Kontak
4. Edit Kontak
5. Hapus Kontak
6. Ganti Format Penyimpanan (Saat ini: JSON)
7. Keluar
Pilih menu (1-7): Masukkan nama kontak yang ingin dicari: Kontak ditemukan -> Ayu: 0812345

--- Menu Aplikasi Kontak ---
1. Tambah Kontak Baru
2. Lihat Semua Kontak
3. Cari Kontak
4. Edit Kontak
5. Hapus Kontak
6. Ganti Format Penyimpanan (Saat ini: JSON)
7. Keluar
Pilih menu (1-7): Masukkan nama kontak yang ingin diedit: Kontak saat ini: Ayu: 0812345
Masukkan nama baru (kosongkan jika tidak ingin mengubah nama 'Ayu'): Masukkan nomor HP baru untuk 'Ayu' (kosongkan jika tidak ingin mengubah): Nomor HP untuk 'Ayu' berhasil diubah.

--- Menu Aplikasi Kontak ---
1. Tambah Kontak Baru
2. Lihat Semua Kontak
3. Cari Kontak
4. Edit Kontak
5. Hapus Kontak
6. Ganti Format Penyimpanan (Saat ini: JSON)
7. Keluar
Pilih menu (1-7): 
--- Daftar Kontak ---
1. Ayu: 0812999

--- Menu Aplikasi Kontak ---
1. Tambah Kontak Baru
2. Lihat Semua Kontak
3. Cari Kontak
4. Edit Kontak
5. Hapus Kontak
6. Ganti Format Penyimpanan (Saat ini: JSON)
7. Keluar
Pilih menu (1-7): Data kontak berhasil disimpan. Sampai jumpa!

Rangkuman

Proyek menggabungkan fungsi dengan dictionary, validasi, penyimpanan, dan penanganan error. Keberhasilan tidak cukup dinilai dari menu yang muncul: periksa perubahan data dan hasil setelah program dibuka kembali.

Cek Pemahaman

Lima pertanyaan ini sama dengan pertanyaan di buku. Periksa jawaban di kuis untuk melihat skor dan pembahasan.

  1. Mengapa nomor telepon disimpan sebagai string?
  2. Pengujian penyimpanan yang tepat adalah …
  3. Apa yang perlu diperiksa saat menghapus kontak?
  4. Mengapa fitur dipisahkan ke fungsi?
  5. Skenario penting saat memuat data adalah …
Kerjakan Kuis

Latihan Mandiri

Latihan 19-L01 – Menghitung kontak

Dasar. Buat fungsi jumlah_kontak(data) dan uji pada dictionary berisi Ayu dan Budi.

Kerjakan di IDE Unduh Latihan

Hasil yang diharapkan:

2
Lihat Petunjuk

Jumlah pasangan dictionary sama dengan jumlah kontak pada model ini.

Lihat Solusi
def jumlah_kontak(data):
    return len(data)


kontak = {"Ayu": "081234", "Budi": "085678"}
print(jumlah_kontak(kontak))

Fungsi dapat diuji sebelum dihubungkan ke menu.

Coba Solusi di IDEUnduh Solusi

Latihan 19-L02 – Pencarian tanpa membedakan kapital

Penerapan. Cari ayu pada dictionary dengan kunci Ayu. Cetak nomor jika ditemukan.

Kerjakan di IDE Unduh Latihan

Hasil yang diharapkan:

081234
Lihat Petunjuk

Gunakan casefold pada kedua teks.

Lihat Solusi
kontak = {"Ayu": "081234", "Budi": "085678"}
kata = "ayu"
for nama, nomor in kontak.items():
    if nama.casefold() == kata.casefold():
        print(nomor)
        break
else:
    print("Tidak ditemukan")

Normalisasi dilakukan untuk perbandingan, sedangkan nama asli tetap tersimpan.

Coba Solusi di IDEUnduh Solusi

Latihan 19-L03 – Simpan dan muat JSON

Pengembangan. Simpan dua kontak latihan ke kontak_uji.json, baca kembali, lalu cetak jumlah dan nomor Ayu.

Unduh Latihan

Hasil yang diharapkan:

2
081234
Lihat Petunjuk

Gunakan dump dan load untuk objek file.

Lihat Solusi
import json

kontak = {"Ayu": "081234", "Budi": "085678"}
with open("kontak_uji.json", "w", encoding="utf-8") as f:
    json.dump(kontak, f, ensure_ascii=False, indent=2)
with open("kontak_uji.json", encoding="utf-8") as f:
    hasil = json.load(f)
print(len(hasil))
print(hasil["Ayu"])

Nol awal tetap ada karena nomor disimpan sebagai string.

Unduh Solusi

Pengembangan

Kegiatan Tambahan

Proyek ini adalah dasar yang bagus. Berikut beberapa ide untuk mengembangkannya lebih jauh:

  1. Validasi Nomor HP Lebih Ketat: Gunakan ekspresi reguler (modul re) untuk memvalidasi format nomor HP yang lebih spesifik (misalnya, harus diawali 08 dan memiliki panjang tertentu).
  2. Tambah Field Data: Izinkan pengguna menyimpan informasi tambahan seperti email, alamat, tanggal lahir, atau catatan untuk setiap kontak. Ini akan memerlukan perubahan struktur data (mungkin dictionary di dalam dictionary) dan penyesuaian pada fungsi simpan/muat, terutama jika menggunakan JSON.
  3. Pengurutan Kontak: Tambahkan opsi untuk menampilkan kontak yang diurutkan berdasarkan nama (A-Z atau Z-A).
  4. Backup dan Restore: Buat fitur untuk mencadangkan data kontak ke file lain dan memulihkannya.
  5. Antarmuka Pengguna Grafis (GUI): Untuk pengalaman yang lebih interaktif, coba bangun GUI menggunakan pustaka seperti Tkinter (bawaan Python), PyQt, atau Kivy.
  6. Pencarian Lebih Lanjut: Izinkan pencarian berdasarkan nomor HP atau field lainnya.
  7. Konversi Data: Jika pengguna mengganti format penyimpanan, tawarkan untuk mengkonversi data dari format lama ke format baru secara otomatis.
  8. Grup Kontak: Tambahkan kemampuan untuk mengelompokkan kontak (misalnya, Keluarga, Teman, Kerja).
  9. Logging: Catat aktivitas pengguna (misalnya, kapan kontak ditambahkan atau dihapus) ke file log terpisah.

Kegiatan Tambahan

Untuk menguji pemahamanmu dan mengasah keterampilan, coba kerjakan latihan berikut:

  1. Implementasikan Fitur “Hitung Jumlah Kontak”:
    • Tambahkan opsi menu baru (misalnya, nomor 7, sehingga “Keluar” menjadi nomor 8).
    • Buat fungsi baru, misalnya hitung_total_kontak(kontak), yang menerima dictionary kontak dan mencetak jumlah total kontak yang tersimpan.
    • Panggil fungsi ini dari main() ketika pengguna memilih opsi menu tersebut.
  2. Tambahkan Field Email:
    • Modifikasi aplikasi agar setiap kontak juga bisa menyimpan alamat email (opsional).
    • Saat menambah atau mengedit kontak, minta input email. Jika pengguna tidak memasukkan email, simpan string kosong atau None.
    • Ubah cara data disimpan dan dimuat agar menyertakan email. Jika menggunakan format TXT, pikirkan pemisah yang aman (mungkin lebih baik fokus pada JSON untuk fitur ini).
    • Tampilkan email saat melihat detail kontak atau semua kontak.
  3. Pencarian Berdasarkan Nomor HP:
    • Modifikasi fungsi cari_kontak atau buat fungsi baru cari_kontak_via_nomor yang memungkinkan pengguna mencari kontak berdasarkan nomor HP mereka.
  4. Konfirmasi Sebelum Menghapus:
    • Dalam fungsi hapus_kontak, sebelum benar-benar menghapus, tanyakan konfirmasi kepada pengguna (misalnya, “Kamu yakin ingin menghapus [nama_kontak]? (y/n)”). Lanjutkan penghapusan hanya jika pengguna menjawab ‘y’.
  5. (Tantangan) Konversi Data Otomatis Saat Ganti Format:
    • Modifikasi fungsi ganti_format_penyimpanan. Setelah format diubah, jika file data dalam format lama ada, coba muat data dari format lama tersebut, lalu langsung simpan dalam format baru yang dipilih. Berikan pesan kepada pengguna mengenai proses ini.
    • Contoh: Jika format saat ini TXT dan ada data_kontak.txt, lalu pengguna ganti ke JSON. Aplikasi harus membaca data_kontak.txt, lalu menyimpan isinya ke data_kontak.json.

Proyek mini ini menunjukkan bagaimana berbagai konsep Python yang telah kamu pelajari dapat digabungkan untuk membuat aplikasi yang fungsional. Dengan terus berlatih dan bereksperimen, kamu akan menjadi semakin mahir dalam memecahkan masalah menggunakan Python.

Referensi dan Materi Pendamping

Langkah Selanjutnya

Bab 20 membahas Proyek: Game, Kalkulator, dan Chatbot. Sebelum melanjutkan, pastikan kamu dapat menjelaskan tujuan belajar dan menyelesaikan tiga latihan inti bab ini.

Edisi September 2026. Nomor contoh, pertanyaan, dan latihan digunakan bersama pada buku dan halaman ini.

Bab 18 | Bab 20