Bab 11 — Modul dan Package: Meminjam Alat dari Gudang Python

Seri B3 — Program Kecil yang Berguna

CatatanDi bab ini, Anda akan:
  • Memakai import untuk meminjam alat siap pakai dari pustaka standar Python
  • Mengenal modul yang paling sering berguna: math, random, datetime, statistics, collections, itertools, pathlib
  • Membuat modul sendiri dan memahami if __name__ == "__main__"
  • Menyusun package kecil dengan __init__.py
  • Mengenal pip, pemasang pustaka tambahan, dan memulai proyek Seri B3

Semua skrip bab ini ada di folder kode/bab11/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).

Di Bab 10, Anda menulis fungsi sendiri. Tetapi petani tidak membuat cangkul dari besi mentah setiap kali mau mencangkul: ia mengambilnya dari gudang alat. Python punya gudang alat yang besar, dan setiap rak di dalamnya disebut modul: sebuah berkas Python berisi fungsi dan data siap pakai. Gudang bawaan yang ikut terpasang bersama Python disebut pustaka standar (standard library). Anda sudah mengintipnya di Bab 4 saat memakai import; sekarang kita masuk ke gudangnya.

Gudang modul: alat bawaan, alat buatan sendiri, dan alat tambahan dari pip, semuanya dipinjam dengan import.

Tiga cara memakai import

01_impor_dasar.py
# Bab 11 - Tiga cara memakai import
import math                        # cara 1: bawa seluruh modul, panggil dengan awalan math.
from math import sqrt              # cara 2: ambil satu alat saja
import statistics as stat          # cara 3: beri nama panggilan (alias)

print("pi          =", math.pi)
print("akar 144    =", sqrt(144))
print("rata-rata   =", stat.mean([32.5, 45.2, 28.0]))

# Luas tajuk pohon berbentuk lingkaran dengan jari-jari 3,5 m
luas = math.pi * 3.5 ** 2
print(f"Luas tajuk  = {luas:.2f} m2")
pi          = 3.141592653589793
akar 144    = 12.0
rata-rata   = 35.233333333333334
Luas tajuk  = 38.48 m2
  • import math membawa seluruh modul; alatnya dipanggil dengan awalan: math.pi.
  • from math import sqrt mengambil satu alat saja, lalu dipakai tanpa awalan.
  • import statistics as stat memberi nama panggilan (alias) agar lebih pendek.

Awalan itu berguna: math.pi langsung memberi tahu bahwa pi berasal dari modul math, bukan variabel Anda sendiri.

TipTIPS

Tulis semua import di bagian paling atas skrip. Pembaca langsung tahu alat apa yang dibutuhkan program.

math dan random

02_math_acak.py
# Bab 11 - math dan random (dengan benih supaya hasilnya bisa diulang)
import math
import random

print(math.ceil(4.2), math.floor(4.8), round(4.5), round(5.5))   # pembulatan

random.seed(42)                                  # benih: urutan "acak" jadi tetap
plot_terpilih = random.choice(["A", "B", "C"])
sampel = random.sample(range(1, 101), 5)         # 5 nomor pohon berbeda dari 1..100
cuaca = random.randint(24, 33)                   # bilangan bulat 24..33
print("Plot terpilih:", plot_terpilih)
print("Pohon contoh :", sampel)
print("Suhu tiruan  :", cuaca)

random.seed(42)                                  # benih yang sama -> hasil yang sama
print("Ulang        :", random.choice(["A", "B", "C"]), random.sample(range(1, 101), 5))
5 4 4 6
Plot terpilih: C
Pohon contoh : [15, 4, 95, 36, 32]
Suhu tiruan  : 27
Ulang        : C [15, 4, 95, 36, 32]

Baris pertama menunjukkan kejutan: round(4.5) menghasilkan 4, tetapi round(5.5) menghasilkan 6. Python membulatkan angka yang tepat di tengah ke bilangan genap terdekat, bukan selalu ke atas. Aturan ini disebut pembulatan bankir; yang perlu Anda ingat, jangan heran bila hasilnya begitu.

Modul random menghasilkan angka “acak”. Dengan random.seed(42) kita menetapkan benih: titik awal urutan acak. Benih sama, urutan sama; lihat baris terakhir yang mengulang hasil C [15, 4, 95, 36, 32]. Inilah cara membuat data tiruan yang bisa diulang persis. Satu catatan jujur: dokumentasi Python hanya menjamin urutan random.random() tetap sama untuk benih yang sama di versi Python mendatang, sedangkan choice dan sample bisa berubah antarversi. Jadi angka di atas pasti sama di komputer Anda bila versi Python-nya sama, tetapi jangan membangun sistem yang bergantung pada angka itu.

Tanggal dengan datetime

03_tanggal.py
# Bab 11 - datetime: tanggal tanam, umur, dan perkiraan panen
from datetime import date, timedelta

tanam = date(2026, 3, 1)
panen = tanam + timedelta(days=120)          # padi contoh: 120 hari
umur = date(2026, 5, 15) - tanam             # selisih dua tanggal = timedelta

print("Tanam :", tanam)
print("Panen :", panen.strftime("%d-%m-%Y"))
print("Umur pada 15 Mei 2026:", umur.days, "hari")
print("Dari teks:", date.fromisoformat("2026-07-01"))

nama_hari = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"]
print("Hari panen jatuh pada:", nama_hari[panen.weekday()])
Tanam : 2026-03-01
Panen : 29-06-2026
Umur pada 15 Mei 2026: 75 hari
Dari teks: 2026-07-01
Hari panen jatuh pada: Senin

date(2026, 3, 1) membuat tanggal. Menambah timedelta(days=120) menggeser tanggalnya. Selisih dua tanggal adalah timedelta, dan .days mengambil jumlah harinya (75 hari dari 1 Maret sampai 15 Mei). strftime("%d-%m-%Y") mengatur tampilan tanggal. (Kita sengaja tidak memakai date.today() karena hasilnya berubah setiap hari.)

statistics dan collections

04_statistik.py
# Bab 11 - statistics: ringkasan angka tanpa menulis rumus sendiri
import statistics

bobot_g = [520, 480, 560, 540, 480, 505]      # bobot sampel ikan (angka contoh)
print("rata-rata :", round(statistics.mean(bobot_g), 2))
print("median    :", statistics.median(bobot_g))
print("modus     :", statistics.mode(bobot_g))
print("simpangan :", round(statistics.stdev(bobot_g), 2))
print("terkecil/terbesar:", min(bobot_g), max(bobot_g))
rata-rata : 514.17
median    : 512.5
modus     : 480
simpangan : 32.31
terkecil/terbesar: 480 560

Enam bobot ikan itu menghasilkan rata-rata 514,17 g dan median 512,5 g (nilai tengah setelah diurutkan). Modus 480 adalah angka yang paling sering muncul. stdev (simpangan baku) mengukur seberapa menyebar angkanya; makin besar, makin tidak seragam ikannya.

05_koleksi.py
# Bab 11 - collections: Counter dan defaultdict
from collections import Counter, defaultdict

jenis = ["Jati", "Meranti", "Jati", "Mahoni", "Jati", "Meranti"]
hitung = Counter(jenis)
print(hitung)
print("Terbanyak:", hitung.most_common(1))

per_plot = defaultdict(list)                  # kunci baru otomatis diberi list kosong
for plot, diameter in [("A", 32.5), ("B", 28.0), ("A", 45.2), ("B", 51.5)]:
    per_plot[plot].append(diameter)
print(dict(per_plot))
Counter({'Jati': 3, 'Meranti': 2, 'Mahoni': 1})
Terbanyak: [('Jati', 3)]
{'A': [32.5, 45.2], 'B': [28.0, 51.5]}

Counter menghitung berapa kali tiap nilai muncul: Jati ada 3. defaultdict(list) adalah dict yang otomatis membuat list kosong untuk kunci baru, sehingga Anda bisa langsung append tanpa memeriksa dulu apakah kuncinya sudah ada.

itertools dan pathlib

06_itertools.py
# Bab 11 - itertools: kombinasi dan kelompok
from itertools import combinations, product, groupby

kolam = ["A", "B", "C"]
print("Pasangan kolam:", list(combinations(kolam, 2)))
print("Kolam x pakan :", list(product(kolam[:2], ["pelet", "cacah"])))

pohon = [("A", "Jati"), ("A", "Meranti"), ("B", "Mahoni"), ("B", "Jati")]
for plot, anggota in groupby(pohon, key=lambda p: p[0]):   # data harus sudah berurutan
    print(plot, [jenis for _, jenis in anggota])
Pasangan kolam: [('A', 'B'), ('A', 'C'), ('B', 'C')]
Kolam x pakan : [('A', 'pelet'), ('A', 'cacah'), ('B', 'pelet'), ('B', 'cacah')]
A ['Jati', 'Meranti']
B ['Mahoni', 'Jati']

combinations membuat semua pasangan, product membuat semua kombinasi dari dua daftar, dan groupby mengelompokkan data yang sudah berurutan. Perhatikan: groupby hanya menyatukan elemen yang berdampingan, jadi urutkan dulu datanya bila belum.

07_pathlib_awal.py
# Bab 11 - pathlib: alamat berkas sebagai objek (belum menyentuh disk)
from pathlib import Path

berkas = Path("data") / "pohon.csv"           # tanda / menyambung bagian alamat
print(berkas)
print("nama   :", berkas.name)
print("akhiran:", berkas.suffix)
print("tanpa akhiran:", berkas.stem)
print("folder :", berkas.parent)
print("ganti akhiran:", berkas.with_suffix(".json"))
print("ada di disk?", berkas.exists())
print("skrip ini bernama:", Path(__file__).name)
data\pohon.csv
nama   : pohon.csv
akhiran: .csv
tanpa akhiran: pohon
folder : data
ganti akhiran: data\pohon.json
ada di disk? False
skrip ini bernama: 07_pathlib_awal.py

pathlib.Path memperlakukan alamat berkas sebagai objek. Tanda / menyambung bagian alamat. Pada komputer Windows, pemisahnya tampil sebagai \; di macOS atau Linux tampil sebagai /. Itulah keuntungan Path: Anda menulis sekali, Python menyesuaikan dengan sistemnya. Di Bab 13 kita akan memakainya untuk membaca dan menulis berkas sungguhan.

Membuat modul sendiri

Modul hanyalah berkas .py. Modul kita ada di subfolder modul/:

# modul/ukuran_lapangan.py (dua fungsi pertamanya)
def luas_bidang_dasar_m2(diameter_cm): ...
def biomassa_kg(jumlah_ikan, bobot_rata_g): ...

Isi lengkapnya ada di berkas kode/bab11/modul/ukuran_lapangan.py. Skrip berikut memakainya:

08_modul_sendiri.py
# Bab 11 - Memakai modul buatan sendiri dari subfolder "modul"
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent / "modul"))   # beri tahu Python lokasi modulnya

import ukuran_lapangan as ukuran

print(f"Luas bidang dasar 32,5 cm: {ukuran.luas_bidang_dasar_m2(32.5):.4f} m2")
print(f"Biomassa kolam A        : {ukuran.biomassa_kg(800, 540.5):.1f} kg")
print("Nama modul ini:", ukuran.__name__)
Luas bidang dasar 32,5 cm: 0.0830 m2
Biomassa kolam A        : 432.4 kg
Nama modul ini: ukuran_lapangan

Luas bidang dasar adalah luas penampang batang; untuk diameter 32,5 cm hasilnya 0,0830 m². Python mencari modul di sejumlah folder; kita menambahkan folder modul/ ke daftarnya lewat sys.path.insert. Nama modul (ukuran_lapangan) sama dengan nama berkas tanpa .py.

if __name__ == "__main__"

Setiap modul punya variabel bawaan __name__. Bila berkas dijalankan langsung, __name__ berisi "__main__". Bila berkas di-import, __name__ berisi nama modulnya. Dengan begitu satu berkas bisa punya “uji cepat” yang hanya jalan saat dijalankan langsung:

09_main_atau_impor.py
# Bab 11 - if __name__ == "__main__": dijalankan langsung atau di-import
import subprocess
import sys
from pathlib import Path

modul = Path(__file__).parent / "modul"
sys.path.insert(0, str(modul))

import contoh_nama                           # di-import: __name__ = nama modul

print("--- sekarang dijalankan langsung ---")
hasil = subprocess.run([sys.executable, str(modul / "contoh_nama.py")],
                       capture_output=True, text=True)
print(hasil.stdout.strip())

print("--- uji cepat di modul ukuran_lapangan (dijalankan langsung) ---")
hasil = subprocess.run([sys.executable, str(modul / "ukuran_lapangan.py")],
                       capture_output=True, text=True)
print(hasil.stdout.strip())
Di dalam contoh_nama, __name__ = contoh_nama
--- sekarang dijalankan langsung ---
Di dalam contoh_nama, __name__ = __main__
--- uji cepat di modul ukuran_lapangan (dijalankan langsung) ---
Uji cepat: 0.083 432.4

Baris pertama muncul saat contoh_nama di-import (nama modulnya contoh_nama). Setelah itu skrip menjalankan berkas yang sama sebagai program tersendiri, dan kali ini __name__ berisi __main__. Pada ukuran_lapangan.py, bagian di bawah if __name__ == "__main__": hanya tampil pada pemanggilan kedua; saat di-import di skrip 08, uji cepat itu tidak mengganggu.

PeringatanAWAS

Kode di tingkat paling luar modul dijalankan saat di-import. Karena itu jangan menaruh perintah panjang atau print di sana, kecuali di bawah if __name__ == "__main__":.

Package: folder berisi modul

Bila modul makin banyak, kumpulkan dalam package: folder yang berisi berkas __init__.py dan modul-modul. Package kita bernama inventaris:

inventaris/
    __init__.py      <- penanda package; boleh memuat daftar alat yang ditampilkan
    data_inti.py     <- data contoh lima sektor
    hitung.py        <- luas_bidang_dasar_m2, biomassa_kg
    ringkas.py       <- ringkas(angka)
10_paket_kecil.py
# Bab 11 - Paket kecil: folder "inventaris" berisi __init__.py dan modul-modulnya
import inventaris
from inventaris import biomassa_kg           # tersedia lewat __init__.py
from inventaris.data_inti import ikan        # ambil langsung dari modul di dalam paket

print("Isi paket (__all__):", inventaris.__all__)
print("Modul hitung berasal dari:", inventaris.hitung.__name__)

for kolam in ikan:
    kg = biomassa_kg(kolam["jumlah"], kolam["bobot_rata_g"])
    print(f"Kolam {kolam['kolam']}: {kg:.1f} kg")
Isi paket (__all__): ['luas_bidang_dasar_m2', 'biomassa_kg', 'ringkas']
Modul hitung berasal dari: inventaris.hitung
Kolam A: 432.4 kg
Kolam B: 375.0 kg
Kolam C: 288.0 kg

__init__.py mengimpor alat dari modul-modulnya sehingga pemakai cukup menulis from inventaris import biomassa_kg. Kolam A berisi 800 ikan seberat 540,5 g: 432,4 kg, persis seperti hasil di Bab 10. Variabel __all__ mencatat nama-nama yang sengaja ditampilkan paket.

pip: meminjam dari luar gudang

Bila alat yang Anda butuhkan tidak ada di pustaka standar, pip memasangnya dari PyPI, toko pustaka daring milik komunitas Python. Perintahnya diketik di terminal, bukan di dalam skrip. Perintah berikut hanya contoh dan tidak dijalankan saat buku ini dibuat, karena memasang paket mengubah komputer pembaca:

python -m pip install openpyxl     # memasang pustaka Excel (dipakai di Bab 15)
python -m pip list                 # melihat pustaka yang terpasang
python -m pip uninstall openpyxl   # mencopot

Menulis python -m pip (bukan sekadar pip) memastikan pip yang dipakai berasal dari Python yang sama dengan yang menjalankan skrip Anda. Bila Anda mencoba mengimpor pustaka yang belum terpasang, ini yang terjadi:

11_modul_tak_ketemu.py
# Bab 11 - Jebakan: mengimpor modul yang tidak ada
import pustaka_tak_ada

print("baris ini tidak pernah dicapai")
Traceback (most recent call last):
  File "11_modul_tak_ketemu.py", line 2, in <module>
    import pustaka_tak_ada
ModuleNotFoundError: No module named 'pustaka_tak_ada'

Pesan ModuleNotFoundError berarti Python tidak menemukan modul bernama itu: bisa karena salah ketik, atau belum dipasang dengan pip. Pembahasan lengkap pemasangan dan lingkungan terpisah (venv) ada di Bab 17.

Saat Anda menulis import x, Python mencari x berurutan di: modul bawaan yang sudah ada, folder tempat skrip dijalankan, lalu daftar folder lain di sys.path. Karena folder skrip ada di urutan awal, Anda sebaiknya tidak menamai berkas sendiri random.py atau statistics.py: modul buatan Anda akan menutupi modul asli dan menimbulkan error yang membingungkan.

Proyek Bersambung B3: ringkasan lima sektor

Selamat datang di proyek Seri B3. Di Seri B2, data lapangan tersimpan di dalam kode. Di seri ini kita akan menyimpannya ke file, membuatnya tahan salah, dan mengubahnya menjadi laporan. Langkah pertama: kemas alat-alat kita menjadi package inventaris (yang sudah Anda lihat), lalu pakai untuk meringkas data empat tabel dari lima sektor (hutan, kebun, pertanian, perikanan, peternakan). Angka-angkanya fiktif.

12_proyek_ringkasan.py
# Bab 11 - Proyek bersambung B3: ringkasan lima sektor memakai paket sendiri
from inventaris import ringkas
from inventaris.data_inti import pohon, tanaman, ikan, ternak

sektor = {
    "Hutan (diameter cm)": [p["diameter_cm"] for p in pohon],
    "Kebun+sawah (kg)": [t["hasil_kg"] for t in tanaman],
    "Perikanan (g/ekor)": [i["bobot_rata_g"] for i in ikan],
    "Peternakan (kg)": [t["bobot_kg"] for t in ternak],
}

for nama, angka in sektor.items():
    r = ringkas(angka)
    print(f"{nama:22} n={r['jumlah']}  total={r['total']:8.1f}  rata={r['rata_rata']:7.2f}  median={r['median']}")
Hutan (diameter cm)    n=6  total=   234.0  rata=  39.00  median=38.4
Kebun+sawah (kg)       n=4  total=  1030.0  rata= 257.50  median=260.75
Perikanan (g/ekor)     n=3  total=  1320.5  rata= 440.17  median=480.0
Peternakan (kg)        n=4  total=  1190.0  rata= 297.50  median=369.5

Tiap baris dibaca begini: sektor hutan punya 6 pohon, total diameter 234,0 cm, rata-rata 39,00 cm, median 38,4 cm. Kebun dan sawah (padi, jagung, kopi, kakao) menghasilkan 1030,0 kg. Rata-rata bobot ikan per kolam 440,17 g, dan ternak rata-rata 297,50 kg. Perhatikan bagaimana skrip ini pendek: seluruh pekerjaan berat dikerjakan oleh package.

Latihan

  1. Pakai datetime: hitung tanggal panen untuk kopi yang ditanam 15 Januari 2026 dan dipanen 270 hari kemudian, lalu cetak dengan format dd-mm-yyyy.
  2. Dengan random.seed(7), pilih 3 nomor kolam berbeda dari 1 sampai 12 memakai random.sample. Jalankan dua kali dan pastikan hasilnya sama.
  3. Tambahkan fungsi keliling_lingkaran_m(jari_jari_m) ke modul/ukuran_lapangan.py, lalu panggil dari skrip baru yang meng-import-nya.
  4. Tantangan: tambahkan modul inventaris/tanggal.py berisi fungsi tanggal_tanam(tanggal_panen, umur_hari), ekspor lewat __init__.py, lalu pakai untuk mencetak tanggal tanam tiap komoditas di data_inti.tanaman bila semuanya dipanen pada 1 Oktober 2026.

Ringkasan Bab

  • Modul = berkas Python berisi alat; package = folder modul dengan __init__.py.
  • Tiga bentuk impor: import x, from x import y, import x as z.
  • Pustaka standar berguna: math, random (pakai seed), datetime, statistics, collections, itertools, pathlib.
  • if __name__ == "__main__": memisahkan kode yang hanya jalan saat berkas dijalankan langsung.
  • python -m pip install <nama> memasang pustaka tambahan; ModuleNotFoundError berarti modul tidak ditemukan.
  • Proyek B3 dimulai: package inventaris meringkas data lima sektor.

Selanjutnya, di Bab 12: penanganan error, cara membuat program tidak langsung runtuh saat ada yang salah.