Mengapa import penghitung berhasil di satu folder tetapi gagal dengan ModuleNotFoundError di folder lain? Jawabannya ada pada daftar lokasi bernama sys.path yang ditelusuri interpreter setiap kali statement import berjalan. Memahami urutan daftar ini memberi kita kemampuan mendiagnosis sendiri semua keanehan import — termasuk yang paling menjengkelkan: program yang “tadi masih jalan” lalu rusak setelah kita menambahkan satu file latihan. Pada Chapter ini kita membedah daftar tersebut, mengenali cache __pycache__, dan mereproduksi sendiri kejadian penimpaan module.

Urutan Pencarian Module #

Ketika interpreter menemui statement import, interpreter menelusuri lokasi berikut secara berurutan dan berhenti pada temuan pertama:

  1. Folder tempat script yang dijalankan berada — atau direktori saat ini bila kita memakai REPL maupun python3 -c.
  2. Daftar folder pada environment variable PYTHONPATH — bila kita mengisinya (panduan praktisnya dibahas pada Chapter Memahami PYTHONPATH).
  3. Folder standard library milik instalasi Python.
  4. Folder site-packages tempat pip memasang package pihak ketiga.

Urutan inilah sumber segala kejutan: module yang kita tulis sendiri di folder script selalu menang atas module bawaan Python dengan nama yang sama — pertarungan yang tidak sehat dan akan kita buktikan di bagian akhir Chapter.

Mengaudit Isi sys.path #

Daftar lokasi tersebut tersimpan sebagai list sys.path di dalam module sys, sehingga bisa kita cetak kapan saja. Simpan kode berikut sebagai inspeksi_path.py — versi ini sengaja meringkas setiap entri menjadi nama foldernya saja supaya hasilnya tetap mudah dibaca:

python
import os
import sys

print("Jumlah entri sys.path:", len(sys.path))

for index, path in enumerate(sys.path):
    if path == "":
        print(index, " (direktori saat ini)")
    else:
        print(index, " ", os.path.basename(path))

Output yang Dihasilkan:

text
Jumlah entri sys.path: 5
0   modul_ku
1   python312.zip
2   python3.12
3   lib-dynload
4   site-packages

Mari kita baca hasilnya per baris:

  1. Entri 0 (modul_ku): folder tempat inspeksi_path.py berada — folder kerja kita sendiri. Inilah alasan file .py dalam folder yang sama bisa saling mengimpor.
  2. Entri 1-3 (python312.zip, python3.12, lib-dynload): lokasi-lokasi standard library tempat module bawaan seperti os dan sys tinggal.
  3. Entri 4 (site-packages): rumah bagi package yang dipasang pip.

Entri lengkap di komputer Anda berisi jalur folder instalasi Python masing-masing — panjang dan berbeda antar mesin — tetapi pola urutannya selalu sama: folder script dulu, standard library kemudian, site-packages terakhir.

Ada satu detail yang membedakan mode eksekusi. Saat kode dijalankan lewat python3 -c atau REPL, entri pertama berupa string kosong yang berarti “direktori saat ini”:

bash
$ python3 -c "import sys; print(repr(sys.path[0]))"
''

pycache dan File .pyc #

Saat module diimpor, interpreter tidak hanya menjalankan kodenya — interpreter juga menerjemahkan source code menjadi bytecode yang lebih cepat dieksekusi. Terjemahan tersebut disimpan sebagai file .pyc di dalam folder __pycache__ di sebelah file module, sehingga import berikutnya tidak perlu menerjemahkan ulang.

Kita amati prosesnya langsung. Siapkan file pembulatan.py:

python
def bulatkan_ke_ratusan(angka):
    return round(angka, -2)

Lalu file program.py yang mengimpornya:

python
import pembulatan

print(pembulatan.bulatkan_ke_ratusan(1234.56))

Output yang Dihasilkan:

text
1200.0

Setelah program berjalan, isi folder berubah menjadi:

text
cache-demo/
├── __pycache__
│   └── pembulatan.cpython-312.pyc
├── pembulatan.py
└── program.py

File pembulatan.cpython-312.pyc adalah bytecode hasil terjemahan untuk interpreter Python 3.12. Interpreter membandingkan waktu modifikasi file .py dengan .pyc di dalam __pycache__; bila source code berubah, terjemahan lama dibuang dan diganti yang baru. Folder __pycache__ boleh dihapus kapan saja tanpa efek apa pun selain import pertama yang sedikit lebih lambat — dan folder ini sebaiknya diabaikan oleh sistem kontrol versi seperti Git.

Module Shadowing: Ketika File Latihan Menimpa Standard Library #

Kini kita reproduksi kejadian yang menyeramkan tadi. Bayangkan kita membuat file latihan bernama json.py di folder kerja:

python
def tampilkan_pesan(data):
    print("Data:", data)

File lain di folder yang sama memakai module json bawaan seperti biasa:

python
import json

pesan = '{"kota": "Bandung"}'
hasil = json.loads(pesan)
print(hasil["kota"])

Output yang Dihasilkan:

text
Traceback (most recent call last):
  File "/home/budi/belajar/latihan-json/program.py", line 4, in <module>
    hasil = json.loads(pesan)
            ^^^^^^^^^^
AttributeError: module 'json' has no attribute 'loads'

Pesan lengkap berisi jalur file di komputer tempat program dijalankan — di mesin Anda akan berbeda, tetapi galatnya sama. ModuleNotFoundError tidak muncul karena import json berhasil — hanya saja yang dimuat adalah file latihan kita, bukan module standard library. Setiap module yang dimuat dari file menyimpan jalurnya di attribute __file__, jadi kita bisa membuktikan dari mana module itu datang:

python
import json

print("Module json dimuat dari:", json.__file__)

Output yang Dihasilkan:

text
Module json dimuat dari: /home/budi/belajar/latihan-json/json.py

Tanpa file latihan tersebut, json.__file__ menunjuk ke jalur panjang di dalam folder instalasi Python yang berakhir di json/__init__.py — petunjuk bahwa yang dimuat adalah package json bawaan. Karena folder script berada di urutan pertama sys.path, file latihan kita menang tanpa peringatan apa pun.

Dengan mekanisme pencarian ini, satu file .py hanyalah module tunggal. Program yang lebih besar butuh cara mengelompokkan puluhan module ke dalam folder — itulah package, topik Chapter berikutnya.