Bab 17 — Environment dan Dependency: Satu Kamar Python untuk Tiap Proyek
Seri I1 — Kode yang Rapi dan Terawat
Semua skrip bab ini ada di folder kode/bab17/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).
Di Bab 16, proyek inventaris lahir sebagai kumpulan kelas. Mulai Bab 18, ia akan memakai pustaka luar (pytest, ruff). Di sinilah masalah klasik muncul. Proyek A butuh pandas versi lama, proyek B butuh yang baru. Bila keduanya dipasang di satu Python yang sama, salah satu rusak. Itu seperti dua petani yang berbagi satu gudang pupuk: begitu yang satu mengganti isinya, yang lain terganggu.
Solusinya environment (lingkungan): “kamar” Python terpisah untuk tiap proyek, lengkap dengan paketnya sendiri. Dependency (ketergantungan) adalah paket luar yang dibutuhkan proyek Anda.

venv: membuat kamar baru
venv adalah modul bawaan Python untuk membuat environment. Perintah di terminal: python -m venv .venv (nama .venv hanya kebiasaan). Skrip berikut membuatnya lewat kode dan memeriksa isinya.
01_buat_venv.py
# Bab 17 - Membuat venv: "kamar" Python khusus untuk satu proyek
import os
import shutil
import subprocess
import sys
import venv
from pathlib import Path
kerja = Path(__file__).parent / "_tmp"
shutil.rmtree(kerja, ignore_errors=True)
kerja.mkdir()
lokasi = kerja / "venv_contoh"
venv.create(lokasi, with_pip=True) # sama dengan: python -m venv venv_contoh
folder_bin = lokasi / ("Scripts" if os.name == "nt" else "bin")
print("Isi folder venv:", sorted(p.name for p in lokasi.iterdir()))
print("Berkas penting ada:", [
nama for nama in ("python", "pip", "activate")
if any(p.stem == nama for p in folder_bin.iterdir())
])
cfg = (lokasi / "pyvenv.cfg").read_text(encoding="utf-8").splitlines()
print([b for b in cfg if b.startswith("include-system-site-packages")])
python_venv = folder_bin / ("python.exe" if os.name == "nt" else "python")
kode = "import sys; print(sys.prefix != sys.base_prefix)"
dalam = subprocess.run([python_venv, "-c", kode], capture_output=True, text=True).stdout.strip()
python_sistem = Path(sys.base_prefix) / ("python.exe" if os.name == "nt" else "bin/python3")
luar = subprocess.run([python_sistem, "-c", kode], capture_output=True, text=True).stdout.strip()
print("Python di dalam venv terpisah dari sistem?", dalam)
print("Python sistem (induk venv) terpisah?", luar)Isi folder venv: ['Include', 'Lib', 'Scripts', 'pyvenv.cfg']
Berkas penting ada: ['python', 'pip', 'activate']
['include-system-site-packages = false']
Python di dalam venv terpisah dari sistem? True
Python sistem (induk venv) terpisah? False
- Folder baru berisi
Scripts(di Linux/macOS bernamabin),Lib(tempat paket disimpan),Include, danpyvenv.cfg. DiScriptsadapython,pip, dan skripactivate. include-system-site-packages = falseberarti paket di Python sistem tidak ikut terlihat. Kamar ini benar-benar bersih.- Python di dalam venv melapor
sys.prefix != sys.base_prefix=True: ia tahu dirinya berada di venv. Python sistem melaporFalse.
Untuk memakainya di terminal, aktifkan dulu. Aktivasi menaruh folder Scripts di depan jalur pencarian perintah, sehingga python dan pip yang dipanggil adalah milik venv. Menurut dokumentasi resmi Python (dibaca 2026-10-10):
| Terminal | Perintah aktivasi |
|---|---|
| Windows, cmd | .venv\Scripts\activate.bat |
| Windows, PowerShell | .venv\Scripts\Activate.ps1 |
| Linux/macOS, bash/zsh | source .venv/bin/activate |
Untuk keluar, ketik deactivate. Aktivasi sifatnya opsional: Anda juga bisa memanggil langsung .venv\Scripts\python.exe, itulah yang dilakukan skrip-skrip di bab ini.
Jangan memindah atau menyalin folder venv ke tempat lain, dan jangan menyimpannya di git. Dokumentasi Python menjelaskan bahwa skrip di dalamnya memuat jalur absolut, jadi venv yang dipindah bisa rusak. Cara yang benar: buang, lalu buat ulang dari daftar paket (requirements.txt atau pyproject.toml). Python 3.13 ke atas bahkan otomatis menaruh .gitignore di dalam venv.
pip dan requirements.txt
pip adalah pemasang paket bawaan Python; paketnya diunduh dari PyPI (Python Package Index, gudang paket resmi). Skrip berikut membuktikan isolasi venv: paket requests dipasang di satu venv, lalu daftarnya dibekukan (pip freeze) ke requirements.txt dan dipakai membangun venv kedua.
02_isolasi_paket.py
# Bab 17 - pip di dalam venv: pasang, bekukan, pasang ulang dari requirements.txt
import os
import shutil
import subprocess
import venv
from pathlib import Path
kerja = Path(__file__).parent / "_tmp"
shutil.rmtree(kerja, ignore_errors=True)
kerja.mkdir()
def python_dari(lokasi):
return lokasi / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
def jalan(lokasi, *argumen):
return subprocess.run([python_dari(lokasi), *argumen], capture_output=True, text=True)
venv_a = kerja / "venv_a"
venv.create(venv_a, with_pip=True)
hasil = jalan(venv_a, "-c", "import requests")
print("Sebelum dipasang:", hasil.stderr.strip().splitlines()[-1])
jalan(venv_a, "-m", "pip", "install", "--quiet", "--disable-pip-version-check", "requests")
hasil = jalan(venv_a, "-c", "import requests; print('berhasil diimpor')")
print("Sesudah dipasang:", hasil.stdout.strip())
beku = jalan(venv_a, "-m", "pip", "freeze").stdout
(kerja / "requirements.txt").write_text(beku, encoding="utf-8")
print("--- requirements.txt ---")
print(beku.strip())
venv_b = kerja / "venv_b"
venv.create(venv_b, with_pip=True)
jalan(venv_b, "-m", "pip", "install", "--quiet", "--disable-pip-version-check",
"-r", str(kerja / "requirements.txt"))
beku_b = jalan(venv_b, "-m", "pip", "freeze").stdout
print("venv_b identik dengan venv_a?", beku_b == beku)Sebelum dipasang: ModuleNotFoundError: No module named 'requests'
Sesudah dipasang: berhasil diimpor
--- requirements.txt ---
certifi==2026.7.22
charset-normalizer==3.5.2
idna==3.20
requests==2.34.2
urllib3==2.8.0
venv_b identik dengan venv_a? True
Kode ini dijalankan sungguhan satu kali oleh penulis, lalu keluarannya disimpan, karena butuh internet dan memuat nomor versi yang terus berubah. Jadi nomor versi di atas adalah keadaan pada saat buku ini ditulis; di komputer Anda akan berbeda.
Telusuri hasilnya:
- Sebelum dipasang,
import requestsgagal denganModuleNotFoundError. Kamar baru memang kosong. - Sesudah
pip install requests, impor berhasil. pip freezemencatat lima paket, bukan satu:requestsmembawa empat dependensi lain (certifi,charset-normalizer,idna,urllib3). Masing-masing dikunci pada nomor versi persis (==).- Venv kedua yang dibangun dari
requirements.txtidentik dengan yang pertama (True). Itulah gunanya berkas ini: rekan kerja atau server dapat membuat kamar yang sama.
Pasang paket dengan python -m pip install ..., bukan hanya pip install .... Bentuk pertama memastikan pip yang dipakai adalah milik Python yang sedang Anda panggil. Itu menghindari kesalahan “terpasang di Python yang salah”.
pyproject.toml: kartu identitas proyek
requirements.txt hanya daftar paket. Proyek modern menyimpan semuanya dalam satu berkas, pyproject.toml: nama, versi, Python yang dibutuhkan, dependensi, dan pengaturan alat (pytest, ruff). Formatnya TOML: bagian ditulis [nama], isinya kunci = nilai. Ini berkas milik paket inventaris:
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "inventaris"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[project.optional-dependencies]
dev = ["pytest", "ruff"]
[tool.ruff]
line-length = 88[build-system]memberi tahu alat pemasang cara “membangun” paket (di sini memakai setuptools).[project]memuat identitas proyek.dependenciesadalah paket wajib (kosong:inventarishanya memakai pustaka bawaan).optional-dependenciesmemuat kelompok tambahan, di sinidevuntuk alat pengembang.[tool.xxx]adalah ruang pengaturan untuk alat, seperti[tool.ruff]dan[tool.pytest.ini_options](Bab 18).
Python 3.11 ke atas punya modul bawaan tomllib untuk membacanya:
03_baca_pyproject.py
# Bab 17 - Membaca pyproject.toml dengan tomllib (pustaka bawaan Python 3.11+)
import tomllib
from pathlib import Path
berkas = Path(__file__).parent / "proyek" / "pyproject.toml"
with open(berkas, "rb") as f: # tomllib mewajibkan mode biner
data = tomllib.load(f)
proyek = data["project"]
print("Nama proyek :", proyek["name"])
print("Versi :", proyek["version"])
print("Butuh Python :", proyek["requires-python"])
print("Dependensi :", proyek["dependencies"])
print("Dependensi dev :", proyek["optional-dependencies"]["dev"])
print("Alat build :", data["build-system"]["build-backend"])
print("Bagian [tool.*] :", sorted(data["tool"]))
print("Panjang baris ruff:", data["tool"]["ruff"]["line-length"])Nama proyek : inventaris
Versi : 0.1.0
Butuh Python : >=3.12
Dependensi : []
Dependensi dev : ['pytest', 'ruff']
Alat build : setuptools.build_meta
Bagian [tool.*] : ['ruff', 'setuptools']
Panjang baris ruff: 88
Ingat: TOML dibaca sebagai dict biasa. data["project"]["name"] adalah dict di dalam dict (Bab 9).
uv: pengelola paket generasi baru
uv adalah pengelola paket dan proyek Python buatan Astral (pembuat ruff yang kita pakai di Bab 19). Ia melakukan pekerjaan venv + pip + kunci versi dalam satu perintah, dan sangat cepat. Berdasarkan dokumentasi resmi uv (dibaca 2026-10-10) dan percobaan di komputer penulis:
| Perintah | Fungsi |
|---|---|
uv init |
membuat proyek baru (pyproject.toml, .python-version, main.py) |
uv add paket |
menambah dependensi ke pyproject.toml, membuat .venv dan uv.lock |
uv run perintah |
menjalankan perintah di environment proyek, memastikan environment mutakhir |
uv lock / uv sync |
memperbarui berkas kunci / menyelaraskan environment dengan kunci |
uv tree |
menampilkan pohon dependensi |
uv venv, uv pip ... |
pengganti python -m venv dan pip yang kompatibel |
04_uv_proyek_baru.py
# Bab 17 - uv: proyek baru dari nol (init, add, run, tree)
import shutil
import subprocess
from pathlib import Path
kerja = Path(__file__).parent / "_tmp" / "uv_demo"
shutil.rmtree(kerja, ignore_errors=True)
kerja.mkdir(parents=True)
def uv(*argumen):
hasil = subprocess.run(["uv", *argumen], cwd=kerja, capture_output=True, text=True)
return hasil.stdout.strip()
uv("init", "--name", "inventaris-demo", "--no-readme", "--vcs", "none", "--python", "3.12")
print("Berkas setelah uv init:", sorted(p.name for p in kerja.iterdir()))
uv("add", "requests")
print("Berkas setelah uv add :", sorted(p.name for p in kerja.iterdir()))
baris = (kerja / "pyproject.toml").read_text(encoding="utf-8").splitlines()
print("--- pyproject.toml (dependencies) ---")
print("\n".join(baris[baris.index("dependencies = [") : baris.index("]")+1]))
print("--- uv run ---")
print(uv("run", "python", "-c", "import requests; print('requests siap dipakai')"))
print("--- uv tree ---")
print(uv("tree"))Berkas setelah uv init: ['.python-version', 'main.py', 'pyproject.toml']
Berkas setelah uv add : ['.python-version', '.venv', 'main.py', 'pyproject.toml', 'uv.lock']
--- pyproject.toml (dependencies) ---
dependencies = [
"requests>=2.34.2",
]
--- uv run ---
requests siap dipakai
--- uv tree ---
inventaris-demo v0.1.0
└── requests v2.34.2
├── certifi v2026.7.22
├── charset-normalizer v3.5.2
├── idna v3.20
└── urllib3 v2.8.0
Ini juga dijalankan sungguhan dan keluarannya disimpan (butuh internet dan memuat nomor versi). Perhatikan:
uv inithanya membuat tiga berkas..venvdanuv.lockbaru lahir ketikauv adddijalankan.uv.lockadalah berkas kunci berisi versi persis semua paket, dikelola uv dan tidak boleh disunting tangan (dokumentasi uv).uv add requestsmenulis"requests>=2.34.2"kepyproject.toml: batas bawah, bukan harga mati. Versi persis ada diuv.lock.uv treememperlihatkan pohonnya:requestsdi atas, empat dependensi di bawahnya, sama dengan hasilpip freezetadi.
Dua jenis daftar punya tugas berbeda. pyproject.toml menyatakan “paket apa yang saya butuhkan” (longgar: >=2.34.2). Berkas kunci (uv.lock, requirements.txt hasil freeze) menyatakan “versi persis yang terpasang” supaya semua orang mendapat hasil yang sama.
Proyek Bersambung I1: memasang inventaris
Sekarang paket inventaris dari Bab 16 dipasang di environment sendiri memakai uv. Opsi -e (editable) membuat Python membaca kode langsung dari folder proyek, jadi perubahan kode langsung terlihat tanpa memasang ulang.
05_pasang_proyek.py
# Bab 17 - Proyek bersambung I1: memasang paket inventaris di environment sendiri
import os
import shutil
import subprocess
from pathlib import Path
induk = Path(__file__).parent
kerja = induk / "_tmp" / "pasang"
shutil.rmtree(kerja, ignore_errors=True)
kerja.mkdir(parents=True)
salinan = kerja / "proyek"
shutil.copytree(induk / "proyek", salinan)
lingkungan = kerja / "venv_inventaris"
subprocess.run(["uv", "venv", str(lingkungan), "--quiet"], check=True)
python = lingkungan / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
# -e = "editable": kode di folder proyek langsung terbaca tanpa memasang ulang
subprocess.run(["uv", "pip", "install", "--quiet", "--python", str(python), "-e", str(salinan)],
check=True)
kode = """
from importlib.metadata import version
import inventaris
from inventaris.contoh import buat_contoh
print("Terpasang: inventaris", version("inventaris"))
from pathlib import Path
print("Kode dibaca dari folder proyek?", Path(inventaris.__file__).parent.parent.name == "proyek")
for unit in buat_contoh():
print(unit.ringkasan())
"""
hasil = subprocess.run([python, "-c", kode], capture_output=True, text=True)
print(hasil.stdout.strip())Terpasang: inventaris 0.1.0
Kode dibaca dari folder proyek? True
Plot hutan A: 3 catatan, 2.0 pohon sehat
Kebun Kopi: 2 catatan, 555.5 kg
Sawah Hilir: 1 catatan, 1250.0 kg (2500 kg/ha)
Kolam A: 1 catatan, 432.4 kg biomassa
Kandang 1: 3 catatan, 1151.5 kg bobot
Jalan ceritanya: salinan proyek ditaruh di _tmp/, uv venv membuat kamar kosong, uv pip install -e memasang paket (uv mengunduh alat build setuptools, itulah sebabnya butuh internet, dan keluarannya disimpan dari satu kali jalan). Lalu di dalam kamar itu inventaris bisa diimpor dari mana saja dan hasilnya sama persis dengan Bab 16: plot hutan A, kebun kopi, sawah hilir, kolam, dan kandang.
Poetry: pesaing uv
poetry adalah pengelola proyek Python yang lebih dulu populer; ia juga memakai pyproject.toml dan membuat berkas kunci poetry.lock. Dari dokumentasi resmi poetry (dibaca 2026-10-10): poetry new membuat folder proyek, poetry add menambah dependensi dan memasangnya, poetry install memasang semua dependensi, dan poetry run menjalankan perintah di environment proyek. Dependensi ditulis di project.dependencies (standar PEP 621); poetry versi lama memakai tool.poetry.dependencies.
07_poetry.py
# Bab 17 - Alur dasar poetry (hanya contoh; tidak dijalankan saat buku dibuat)
import subprocess
perintah = [
["poetry", "new", "inventaris-poetry"], # membuat folder proyek baru
["poetry", "add", "requests"], # menambah dependensi + mengunci versinya (poetry.lock)
["poetry", "install"], # memasang semua dependensi proyek
["poetry", "run", "pytest"], # menjalankan perintah di environment proyek
]
for p in perintah:
print("$", " ".join(p))
subprocess.run(p, check=True)Kode ini tidak dijalankan saat buku dibuat karena poetry belum terpasang di komputer penulis (lihat hasil 06_cek_alat.py di bawah), jadi keluarannya tidak ditampilkan. Perintahnya dicocokkan dengan dokumentasi resmi, tetapi belum diuji langsung.
Anaconda dan conda
Anaconda adalah cara lain memasang dan mengelola Python, populer di dunia data science. Ada tiga istilah yang sering tertukar:
- conda: alat baris perintah untuk membuat environment dan memasang paket. Dokumentasinya menyebut conda mengelola “paket, dependensi, dan environment untuk bahasa apa pun”, jadi bukan hanya paket Python.
- Anaconda Distribution: paket pemasang yang membawa conda, Python, Anaconda Navigator (aplikasi berjendela untuk membuat environment dan membuka alat tanpa mengetik perintah), dan 600+ paket sains data siap pakai, sekitar 9,7 GB.
- Miniconda: versi ringkas: conda, Python, 130+ paket, tanpa Navigator, sekitar 900 MB.
(Angka ukuran dari halaman resmi Anaconda, dibaca 2026-10-10.)
Bedanya dengan python.org: pemasang python.org hanya memberi Python; selebihnya Anda urus dengan venv dan pip. Anaconda memberi Python plus perangkat pengelola environment dan banyak paket sekaligus. Paketnya diambil dari channel (saluran) conda, bukan dari PyPI.
Alurnya di terminal (dari dokumentasi resmi conda, dibaca 2026-10-10):
conda create --name inventaris python numpy # buat environment berisi Python dan numpy
conda activate inventaris # masuk ke environment
conda install --name inventaris matplotlib # pasang paket lain
conda info --envs # daftar environment
conda deactivate # keluar
08_conda.py
# Catatan: "conda activate inventaris" diketik langsung di terminal, bukan lewat skrip.
# Bab 17 - Alur dasar conda (hanya contoh; tidak dijalankan saat buku dibuat)
import subprocess
perintah = [
["conda", "create", "--name", "inventaris", "python", "numpy"], # membuat environment + isinya
["conda", "install", "--name", "inventaris", "numpy"], # memasang paket
["conda", "info", "--envs"], # daftar environment
]
for p in perintah:
print("$", " ".join(p))
subprocess.run(p, check=True)Kode ini tidak dijalankan saat buku dibuat karena conda belum terpasang di komputer penulis, jadi keluarannya tidak ditampilkan dan seluruh bagian conda di bab ini belum diuji langsung. (conda activate sengaja tidak ada dalam skrip: ia diketik di terminal.)
06_cek_alat.py
# Bab 17 - Memeriksa alat apa saja yang ada di komputer
import shutil
for alat in ["python", "pip", "uv", "git", "poetry", "conda"]:
jalur = shutil.which(alat)
print(f"{alat:7}", "ada" if jalur else "TIDAK ada")python ada
pip ada
uv ada
git ada
poetry TIDAK ada
conda TIDAK ada
Keluaran ini diambil sekali di komputer penulis dan berbeda bila dijalankan di komputer Anda. Di sini uv dan git ada, poetry dan conda tidak.
conda vs pip: pip memasang dari PyPI, conda dari channel conda. Dokumentasi conda menyarankan: bila perlu keduanya di satu environment, pasang sebanyak mungkin dengan conda dulu, baru pip (“Use pip only after conda”). conda-forge adalah channel yang dijalankan komunitas, terpisah dari channel defaults milik Anaconda, Inc. Dokumentasi conda-forge menyarankan Miniforge sebagai cara termudah memulainya.
Jupyter: halaman resmi Anaconda menyebut Jupyter Notebook dapat dibuka dari Navigator. (Halaman itu tidak merinci apakah Jupyter terpasang bawaan; periksa setelah memasang.)
Lisensi Anaconda. Anaconda tidak gratis untuk semua orang. Menurut Ketentuan Layanan Anaconda (https://www.anaconda.com/legal/terms/terms-of-service, tanggal berlaku tertulis 15 Juli 2025, dibaca 2026-10-10), penggunaan platform gratis bila Anda: (1) individu untuk keperluan pribadi nonkomersial, (2) terkait lembaga akademik yang memenuhi syarat, (3) terkait organisasi nirlaba dan riset yang memenuhi syarat, atau (4) untuk organisasi berorientasi laba dengan 200 karyawan/kontraktor atau kurang (termasuk afiliasi). Organisasi berorientasi laba dengan lebih dari 200 karyawan/kontraktor yang tidak termasuk kategori gratis harus berlangganan paket Business. Halaman harga (https://www.anaconda.com/pricing, dibaca 2026-10-10) menulis “200+” dan menyebut paket Free untuk yang belajar Python. Anaconda berhak meminta bukti kelayakan. Syarat ini bisa berubah, jadi baca halaman resminya sendiri sebelum memakai Anaconda di tempat kerja. Penulis tidak memeriksa apakah syarat itu berlaku bagi channel conda-forge; dokumen ketentuan tidak menyebutnya.
Kapan memilih yang mana? Rekomendasi penulis untuk pembaca buku ini: untuk proyek pengembangan umum (aplikasi, otomasi), venv + pip atau uv sudah cukup, ringan, dan tanpa urusan lisensi. Pertimbangkan Anaconda/conda bila Anda bekerja dengan pustaka sains yang sulit dipasang dengan pip, atau bila tim Anda sudah memakainya. Di tempat kerja, periksa dulu ketentuan lisensi di atas.
Pada Windows, PowerShell bisa menolak Activate.ps1 karena kebijakan eksekusi skrip. Dokumentasi Python menyarankan Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser. Ini mengubah pengaturan keamanan akun Anda sendiri, jadi lakukan hanya bila paham akibatnya; alternatifnya, panggil .venv\Scripts\python.exe langsung tanpa aktivasi.
Latihan
- Di terminal, buat venv
latihan_venvdenganpython -m venv latihan_venv, aktifkan, lalu ketikpython -c "import sys; print(sys.prefix)". Bandingkan dengan hasil tanpa aktivasi. - Pasang
six(paket kecil) di venv itu denganpython -m pip install six, lalu simpanpython -m pip freeze > requirements.txtdan lihat isinya. - Ubah
03_baca_pyproject.pyagar mencetak juga daftardev, satu paket per baris. - Tantangan: dengan
uv, buat proyek baru (uv init), tambahkanpytestsebagai dependensi pengembangan (uv add --dev pytest; periksauv add --help), lalu jalankanuv run pytest --version.
Ringkasan Bab
- Environment = kamar Python terpisah per proyek; dependency = paket luar yang dibutuhkan.
- venv membuat environment; aktivasi opsional; jangan dipindah atau dimasukkan ke git, cukup dibuat ulang.
- pip memasang dari PyPI;
pip freezemenghasilkanrequirements.txtberisi versi persis. pyproject.tomlmenyimpan identitas proyek, dependensi, dan pengaturan alat; dibaca dengantomllib.- uv menggabungkan venv, pip, dan berkas kunci (
uv.lock) dengan cepat. poetry adalah pesaingnya (tidak diuji di sini). - Anaconda/conda punya kegunaan dan lisensi sendiri; baca ketentuan resminya sebelum dipakai di tempat kerja. Bagian conda di buku ini belum diuji langsung.
Selanjutnya, di Bab 18: testing dasar dengan pytest, cara membuat kode yang memeriksa kode Anda sendiri.