Ketika package pertama kita lahir, strukturnya terasa seadanya dan itu tidak masalah. Namun struktur yang dipilih di awal menentukan biaya perawatan bertahun-tahun ke depan: nama module yang buruk sulit diubah setelah banyak kode mengimpornya, dan folder yang tumbuh tanpa prinsip berubah menjadi tumpukan file yang tidak berani disentuh siapa pun. Pada Chapter penutup section ini kita merancang struktur package yang sehat — mulai dari aturan penamaan, prinsip kohesi, pilihan antara layout datar dan src layout, sampai mengenali penyakit struktural paling umum: circular import.

Aturan Penamaan Module dan Package #

Nama module menjadi bagian dari kode semua penggunanya — ditulis, dibaca, dan diketik setiap hari — sehingga pilihan nama adalah keputusan API, bukan urusan kosmetik. Aturan mainnya:

  • Semua huruf kecil, tanpa tanda hubung: analisis_nilai, bukan AnalisisNilai atau analisis-nilai.
  • Pendek dan deskriptif; garis bawah dipakai bila memang membantu keterbacaan (ini juga saran PEP 8).
  • Jangan bentrok dengan nama standard library — json.py, random.py, os.py — dan waspadai nama umum seperti test yang bisa menabrak alat lain.

Tanda hubung memang dilarang keras karena bukan identifier Python yang sah. Mencobanya langsung membuktikannya:

bash
$ python3 -c "import kasir-app"
  File "<string>", line 1
    import kasir-app
                ^
SyntaxError: invalid syntax

Penamaan folder proyek boleh memakai tanda hubung (repo GitHub penuh dengan nama seperti analisis-nilai), tetapi package yang diimpor di dalamnya harus tetap nama identifier yang sah — kompromi umumnya seperti pasangan nama repo analisis-nilai dengan package analisis_nilai.

Prinsip Kohesi: Satu Module, Satu Tanggung Jawab #

Struktur yang baik mengelompokkan kode berdasarkan tanggung jawab domain — produk, pembayaran, laporan — bukan berdasarkan jenis teknisnya seperti folder fungsi, data, atau helper. Pengelompokan teknis membuat satu perubahan fitur menyentuh banyak folder sekaligus; pengelompokan domain menjaga kode yang berubah bersama tetap bersebelahan.

Kita terapkan pada proyek kecil analisis nilai siswa dengan layout datar (flat layout):

text
analisis-nilai/
├── main.py
└── analisis_nilai/
    ├── __init__.py
    ├── hitung.py
    ├── laporan.py
    └── predikat.py

Setiap module memegang satu tanggung jawab yang bisa dijelaskan dalam satu kalimat. Isi hitung.py:

python
def rata_rata(nilai):
    return sum(nilai) / len(nilai)

def nilai_tertinggi(nilai):
    return max(nilai)

Isi predikat.py:

python
def predikat_nilai(nilai):
    if nilai >= 90:
        return "A"
    if nilai >= 80:
        return "B"
    if nilai >= 70:
        return "C"
    return "D"

Isi laporan.py, yang memakai dua module lain secara relative:

python
from .hitung import rata_rata
from .predikat import predikat_nilai

def buat_laporan(nama, nilai):
    rata = rata_rata(nilai)
    predikat = predikat_nilai(rata)
    return f"{nama}: rata-rata {rata:.1f}, predikat {predikat}"

File __init__.py menjadi etalase resmi package — hanya tiga baris:

python
from .hitung import rata_rata
from .predikat import predikat_nilai
from .laporan import buat_laporan

Kode program utama ditaruh di luar package, pada file main.py:

python
from analisis_nilai import buat_laporan

nilai_budi = [85, 90, 78]
nilai_siti = [92, 95, 88]

print(buat_laporan("Budi", nilai_budi))
print(buat_laporan("Siti", nilai_siti))

Output yang Dihasilkan:

text
Budi: rata-rata 84.3, predikat B
Siti: rata-rata 91.7, predikat A

Mari kita bedah keputusan strukturalnya:

  1. main.py di luar package: kode program (titik masuk) dipisahkan dari kode yang bisa diimpor — prinsip yang sama yang diperkuat pola if __name__ == "__main__" pada Chapter Main Module dan name.
  2. __init__.py sebagai etalase: pengguna cukup from analisis_nilai import buat_laporan tanpa perlu tahu ada tiga file di dalamnya; kita bebas menata ulang isi internal tanpa memecah kode pengguna.
  3. Import antar-module memakai bentuk relative: from .hitung import rata_rata menjaga package tetap utuh bila suatu hari diubah nama foldernya.

Flat Layout atau src Layout #

Layout di atas menaruh package analisis_nilai langsung di akar proyek — disebut flat layout. Alternatifnya, src layout menaruh package di dalam folder src/:

text
analisis-nilai/
├── pyproject.toml
└── src/
    └── analisis_nilai/
        ├── __init__.py
        ├── hitung.py
        ├── laporan.py
        └── predikat.py

File pyproject.toml berisi metadata proyek untuk proses pemasangan (topik section Packaging dan Deployment).

AspekFlat layoutsrc layout
Jalur packagelangsung di akar proyekdi dalam src/
Import dari akar proyeklangsung berhasilgagal — perlu instalasi (pip install -e .) atau PYTHONPATH
Risiko menguji kode yang belum dipasangada — akar proyek ikut sys.pathtidak ada
Cocok untukskrip dan proyek kecilpackage yang akan didistribusikan

Dengan src layout, import analisis_nilai dari akar proyek gagal — justru itu tujuannya: satu-satunya cara memakai package adalah memasangnya resmi lewat pip install -e ., sehingga pengujian selalu memakai kode yang akan didistribusikan. Untuk proyek latihan dan aplikasi skrip, flat layout lebih sederhana dan memadai; pilihan antar keduanya memang selera yang diperdebatkan sehat di komunitas.

Waspadai Circular Import #

Penyakit struktural paling umum: dua module saling mengimpor. Reproduksinya sederhana — layanan.py:

python
from penyimpanan import simpan_data

def proses_pesanan(pesanan):
    simpan_data(pesanan)
    return "selesai"

penyimpanan.py:

python
from layanan import proses_pesanan

def simpan_data(data):
    return "tersimpan"

Output yang Dihasilkan:

text
Traceback (most recent call last):
  File "/home/budi/belajar/siklus/layanan.py", line 1, in <module>
    from penyimpanan import simpan_data
  File "/home/budi/belajar/siklus/penyimpanan.py", line 1, in <module>
    from layanan import proses_pesanan
  File "/home/budi/belajar/siklus/layanan.py", line 1, in <module>
    from penyimpanan import simpan_data
ImportError: cannot import name 'simpan_data' from partially initialized module 'penyimpanan' (most likely due to a circular import) (/home/budi/belajar/siklus/penyimpanan.py)

Baca urutannya: layanan mulai dimuat, mengimpor penyimpanan, yang ternyata mengimpor balik layanan yang belum selesai dimuat — lingkaran itu berakhir di ImportError dengan pesan partially initialized module. Solusinya bukan trik import, melainkan perancangan ulang: pindahkan bagian yang saling dibutuhkan ke module ketiga di bawah keduanya, sehingga arah import kembali searah.

Section Modules dan Packages sampai di sini tuntas: kita bisa mengimpor, membuat module sendiri, menelusuri pencarian, menyusun package, membentuk API publik, memilih gaya import, menjalankan sekaligus mengimpor satu file, dan merancang struktur yang sehat. Ketika program kita menabrak kondisi tak terduga, Python menyediakan mekanisme penanganan yang layak dikuasai sejauh ini — section Exception Handling menunggu kita di babak berikutnya.