Soal 1File apa yang harus kamu tempatkan di sebuah folder agar dikenali sebagai package?
__init__.py dan Relative Import — Membundel File ke dalam Package
Pelajari peran __init__.py untuk membangun package Python dan kapan memilih absolute vs relative import.
Artikel sebelumnya membahas modul satu file dan cara kerja import. Begitu aplikasi membesar, kamu akan ingin membundel modul yang berkaitan di bawah satu folder sebagai package. Artikel ini menelusuri cara mengimpor package.
Package adalah Folder Berisi __init__.py
Sebuah package adalah folder yang berisi file khusus bernama __init__.py. Saat Python menemukannya, seluruh folder menjadi satu package dan kode lain bisa menariknya dengan import nama_folder. __init__.py adalah hal pertama yang berjalan saat package diimpor, dan tempat yang umum untuk mendeklarasikan fungsi dan class apa saja yang diekspos package.
- __init__.py — file yang berjalan pertama saat package diimpor
- calculation.py — sebuah modul (add / multiply)
- string_utils.py — sebuah modul (format_name, dll.)
my_package/ berisi __init__.py, jadi Python memperlakukan folder itu sebagai package. Modul internal seperti calculation.py diekspos ke luar lewat __init__.py.__init__.py boleh kosong. Bahkan file kosong sudah cukup — Python hanya butuh keberadaan file untuk mengenali folder sebagai package.
Mengimpor Tanpa Lewat __init__.py
Bahkan dengan __init__.py kosong, kamu masih bisa mengimpor modul langsung dari package. Bentuknya from nama_package.nama_modul import nama_fungsi — sambungkan lokasi file dengan titik.
# my_package/__init__.py <- kosong tidak masalah
# my_package/calculation.py
def add(a, b):
return a + b
# Di main.py
from my_package.calculation import add # eja nama file `calculation`
print(add(1, 2)) # 3
Kelemahannya: pemanggil perlu tahu file mana memuat apa. Jika kamu menata ulang package nanti (katakanlah, memecah calculation.py jadi dua), setiap pemanggil juga harus berubah. Pola di bagian berikutnya yang me-ekspor ulang lewat __init__.py memperbaikinya — pemanggil bisa merujuk dengan nama-pendek-langsung-dari-package.
Mengumpulkan API Publik di __init__.py
Tulis from .nama_modul import nama_fungsi di dalam __init__.py dan fungsinya akan tampak seakan tinggal langsung di bawah package. Misalnya, from .calculation import add, multiply di dalam __init__.py membuat pemanggil bisa menulis from my_package import add, multiply — pendek dan rapi.
from my_package import add tanpa memikirkan file mana (calculation) yang memuatnya.Kenapa Mengumpulkan API Publik di __init__.py
Jika pemanggil hanya pernah menyentuh apa yang diekspos __init__.py, kamu bisa menyusun ulang file internal nanti tanpa menyentuh kode pemanggil. Itu encapsulation klasik di tingkat package — pola dasar desain package.
from vs import — Dua Cara Menulisnya
Ada dua bentuk import: from package import nama dan import package. Keduanya menarik target masuk, tapi cara pemanggilan sesudahnya berbeda.
from package import nama membawa nama itu sendiri ke scope sehingga kamu bisa memakainya langsung. import package hanya membawa nama package ke scope; pemanggilan harus pakai jalur titik penuh.# Bentuk 1: from ... import ...
from my_package.calculation import add
print(add(1, 2)) # add bisa langsung dipakai
# Bentuk 2: import ...
import my_package.calculation
print(my_package.calculation.add(1, 2)) # panggil dengan jalur titik penuh
# Bentuk 2 + as alias (mempersingkat nama panjang)
import my_package.calculation as calc
print(calc.add(1, 2))
| Bentuk | Nama di scope | Cara memanggil |
|---|---|---|
| from my_package.calculation import add | add | add(1, 2) |
| import my_package.calculation | my_package | my_package.calculation.add(1, 2) |
| import my_package.calculation as calc | calc | calc.add(1, 2) |
Bentuk yang paling umum adalah from package import nama — nama fungsinya pendek dan kode di sisi main jadi enak dibaca. Tapi saat dua package berbeda mengekspor nama fungsi yang sama dan kamu butuh keduanya, bentuk import package menjaga nama modul tetap terlihat sehingga jelas yang mana yang sedang kamu panggil.
- add — nama itu sendiri masuk ke scope main
- Panggilan:
add(1, 2)langsung berfungsi - Pendek ditulis, tapi tidak jelas dari package mana
addberasal
- my_pkg — hanya nama package yang masuk ke scope main
- Panggilan:
my_pkg.calc.add(1, 2)dengan jalur titik penuh - Lebih panjang ditulis, tapi
my_pkg.calc.addmembuat asalnya jelas
from package import nama membawa nama itu sendiri ke scope main (= panggilan pendek). import package hanya membawa nama package, jadi panggilan pakai jalur titik penuh (= asalnya eksplisit).Absolute vs Relative Import
Bagaimana jika modul di dalam package perlu saling merujuk? Misalnya, utility/validator.py mau mengimpor dari utility/helper.py — ada dua cara menulisnya: absolute import dan relative import. Untuk memilih di antara keduanya, pertama-tama kamu perlu jelas tentang apa arti project root.
- main.py ← entry point yang kamu jalankan duluan
- config.py ← pengaturan seluruh aplikasi
- __init__.py ← berjalan saat
import my_appterjadi
- __init__.py
- validator.py
- helper.py
main.py (entry point) berada. my_app di absolute import from my_app.utility import validator merujuk ke folder bernama sama yang langsung di bawah root tersebut.Project root adalah folder yang memuat entry point yang kamu jalankan dengan python main.py. Saat kamu menulis absolute import from my_app.utility import validator, Python mencari my_app langsung di bawah root tersebut, lalu masuk ke utility/validator.py. Jalur titik mencerminkan struktur folder — itulah seluruh isi absolute import.
| Tipe | Bentuk | Apa artinya |
|---|---|---|
| Absolute import | from my_app.utility import helper | Jalur penuh dari project root |
| Relative import | from . import helper | Relatif terhadap file tempatmu berada |
| Relative import (induk) | from .. import config | Menargetkan file satu folder di atas |
. di from . import berarti "folder yang sama denganku".Pembagian biasa: entry point (main.py) memakai absolute import untuk masuk ke package, dan modul di dalam package memakai relative import untuk saling merujuk. Dengan relative import, mengganti nama folder package nanti tidak memaksa perubahan kode internal.
# my_app/utility/validator.py
# Relative import untuk helper.py di package yang sama
from .helper import log_message
def validate_user(user):
if user.name and user.email:
log_message("OK")
return True
log_message("masalah terdeteksi")
return False
# my_app/utility/helper.py
def log_message(message):
print("[LOG]", message)
# Untuk mencapai config.py di package lain,
# naik satu level dengan ..:
# from ..config import get_config
- config.py ← target dari
from ..config import ...
- validator.py ← menulis
from .helper import log_message - helper.py ← menyediakan
log_message(...)
from .helper import log_message di validator.py memakai . — "folder ini (= utility/)". config.py berada satu level di atas di my_app/, jadi dari sudut pandang validator kamu menjangkaunya sebagai ..config (.. = satu level di atas).Entry Point Tidak Bisa Pakai Relative Import
File yang kamu jalankan langsung dengan python main.py tidak bisa pakai relative import — kamu akan kena ImportError. Relative import hanya bekerja saat file dimuat sebagai anggota suatu package. Dari entry point, selalu pakai absolute import (mis., from my_app.utility import validate_user).
Layout Bergaya Proyek Nyata
Dengan aturan-aturan ini, kamu bisa menyusun struktur folder yang cocok untuk aplikasi nyata. Berikut layout yang dipecah ke tiga package — settings, database, dan utility:
- main.py — entry point (file yang kamu jalankan dengan python)
- config.py — pengaturan seluruh aplikasi
- __init__.py — API publik package my_app
- __init__.py
- connection.py / models.py
- __init__.py
- validator.py / helper.py
main.py menarik seluruh package my_app/; modul di dalamnya pakai relative import untuk saling menjangkau. Setiap subfolder punya __init__.py sendiri yang mengumpulkan API publik subpackage tersebut.Dari main.py kamu memanggil keluar dengan absolute import seperti from my_app.utility import validate_user. Di dalam utility/, validator.py menjangkau helper.py dengan from .helper import log_message — sebuah relative import. Itulah pembagian kerja kanonik.
Mulai dari sini levelnya naik — silakan kembali ke materi sebelumnya kalau buntu
Latihan berikutnya adalah capstone — membangun dua package secara berdampingan. Kamu akan paling lancar kalau pola re-export __init__.py sudah terasa natural dan pembedaan absolute / relative import sudah solid bagimu. Kalau buntu, lompat balik ke latihan validator.py sebelumnya atau diagram "Layout Mendekati Proyek Nyata" di atas untuk reset sebelum mengerjakan ini.
Tantangan Multi-Folder
Saatnya mencoba membangun dua package berdampingan dengan semua yang sudah kamu pelajari. Kamu akan mengelola package katalog produk catalog/ dan package billing/ di folder terpisah, lalu mengoreografikannya dari main.py untuk menyelesaikan satu pesanan. Memecah tanggung jawab antar folder berarti perubahan data produk hanya menyentuh catalog/, dan perubahan format invoice hanya menyentuh billing/ — radius dampaknya tetap di dalam satu folder.
- main.py — entry point (memakai kedua package untuk menangani pesanan)
- __init__.py — me-ekspor ulang
from .products import get_price - products.py — mengimplementasikan
get_price(name)
- __init__.py — me-ekspor ulang
from .invoice import format_invoice - invoice.py — mengimplementasikan
format_invoice(name, qty, unit_price)
catalog/ memuat data produk (products.py); billing/ memuat formatting invoice (invoice.py). Setiap __init__.py me-ekspor ulang API publik, jadi main.py bisa memanggil keluar dengan dua absolute import: from catalog import get_price dan from billing import format_invoice.__init__.py untuk me-ekspor ulang .products / .invoice.Mulai dari sini bonus material — jarang ditemui di kode nyata
Bagian di bawah membahas __all__ dan sifatnya hanya informasi. Kode dunia nyata hampir tidak pernah memakai from package import *, jadi bagian ini tidak memengaruhi alur utama. Selama kamu sudah menguasai bentuk import dengan nama eksplisit, silakan baca sekilas saja bagian ini.
Mengontrol from package import * dengan __all__
Saat seseorang menulis from my_package import * untuk menarik semuanya dengan bintang, kamu bisa mengontrol apa yang diekspos lewat __all__ di __init__.py. Dengan __all__ = ["add"], import * hanya mengambil add — tidak yang lain.
__all__ = ["add"] di __init__.py, hanya add yang masuk lewat from package import *. multiply tertinggal (kamu masih bisa mengambilnya secara eksplisit).# my_package/__init__.py
from .calculation import add, multiply
__all__ = ["add"] # hanya add yang diekspos via *
# Sisi pemanggil
# from my_package import *
# add(1, 2) # OK
# multiply(1, 2) # NameError (tidak ditarik oleh *)
Dalam Praktik, Hindari Import *
from my_package import * itu sulit dibaca — kamu tidak bisa tahu sekilas apa yang masuk. Kode dunia nyata hampir selalu memakai nama eksplisit seperti from my_package import add, multiply. Anggap __all__ sebagai *jaring pengaman untuk kasus langka saat seseorang memang memakai ``**.
Artikel ini membahas memakai __init__.py untuk membundel beberapa modul ke dalam satu package, perbedaan antara from package import nama dan import package, memilih antara absolute dan relative import, serta layout folder yang mendekati proyek nyata.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Dari my_app/utility/validator.py, mana relative import yang benar untuk memuat helper.py di folder utility yang sama?
Soal 3Apa yang terjadi jika kamu menulis relative import (from . import xxx) di dalam entry point (file yang kamu jalankan langsung dengan python main.py)?