Belajar dengan membaca secara berurutan

__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.

Anatomi sebuah Package
my_package/ (sebuah package)
  • __init__.py — file yang berjalan pertama saat package diimpor
  • calculation.py — sebuah modul (add / multiply)
  • string_utils.py — sebuah modul (format_name, dll.)
Folder 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.

Re-export Lewat __init__.py untuk Import yang Lebih Pendek
my_package/main.py__init__.pyfrom .calculationimport add, multiplycalculation.pydef add()def multiply()from my_packageimport addadd(1, 2)bisa dipanggilviamengangkat
Saat __init__.py mengangkat add dari calculation.py, pemanggil bisa menulis from my_package import add tanpa memikirkan file mana (calculation) yang memuatnya.

Folder my_package/ terlampir di kiri (buka calculation.py dan __init__.py dari 📂 Files untuk memeriksanya).

① Pakai from my_package import add, multiply untuk memuat kedua fungsi.

② Cetak hasil add(10, 20) dan multiply(3, 4).

Python Editor

Jalankan kode untuk melihat output

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 vs import — Apa yang Masuk ke Scope
from my_pkg.calcimport adddi scope:addpanggil:add(1, 2)importmy_pkg.calcdi scope:my_pkgpanggil:my_pkg.calc.add(1, 2)
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))
BentukNama di scopeCara memanggil
from my_package.calculation import addaddadd(1, 2)
import my_package.calculationmy_packagemy_package.calculation.add(1, 2)
import my_package.calculation as calccalccalc.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.

from vs import — Apa yang Masuk ke Scope main
from my_pkg.calc import add
  • add — nama itu sendiri masuk ke scope main
  • Panggilan: add(1, 2) langsung berfungsi
  • Pendek ditulis, tapi tidak jelas dari package mana add berasal
import my_pkg.calc
  • 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.add membuat 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).

Coba kedua bentuk dengan package mathlib/ terlampir. calculator.py mendefinisikan triple(n) (verifikasi di 📂 Files).

① Pakai bentuk from ... import ... untuk membawa triple, lalu print(triple(7)).

② Lalu pakai bentuk import ... as ... untuk juga membawa mathlib.calculator dengan alias calc, dan print(calc.triple(7)).

Python Editor

Jalankan kode untuk melihat output

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.

Apa Itu Project Root
project/ ← root (tempat main.py berada)
  • main.py ← entry point yang kamu jalankan duluan
  • config.py ← pengaturan seluruh aplikasi
my_app/
  • __init__.py ← berjalan saat import my_app terjadi
utility/
  • __init__.py
  • validator.py
  • helper.py
Project root adalah folder tempat 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.

TipeBentukApa artinya
Absolute importfrom my_app.utility import helperJalur penuh dari project root
Relative importfrom . import helperRelatif terhadap file tempatmu berada
Relative import (induk)from .. import configMenargetkan file satu folder di atas
Dua Cara Membaca helper.py dari validator.py
Absolutefrom my_app.utilityimport helperJalur penuhdari rootRelativefrom . importhelperBerdasarkanfolder saat ini
Saat kamu membaca modul tetangga di package yang sama, baik absolute maupun relative bekerja. . 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
Layout Folder untuk Kode di Atas
my_app/
  • config.py ← target dari from ..config import ...
utility/
  • validator.py ← menulis from .helper import log_message
  • helper.py ← menyediakan log_message(...)
validator.py dan helper.py berada bersebelahan di folder utility/ yang sama. Itulah kenapa 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).

Coba absolute dan relative import dengan package shop/ terlampir. Buka 📂 Files dan kamu akan lihat shop/__init__.py kosong — baik Cart maupun to_yen belum diekspos di root package.

① Pertama, jalankan main.py apa adanya — kamu akan dapat ImportError karena tidak ada yang diekspor dari shop.

② Berikutnya, buka shop/__init__.py dari 📂 dan tulis from .cart import Cart dan from .formatter import to_yen sebagai relative import (. = package ini), lalu simpan.

③ Tambahkan penggunaan Cart di main.py — buat Cart, tambahkan apple seharga $100 dan orange seharga $200, lalu format total dengan to_yen() dan cetak. Sisi main pakai absolute import, internal package pakai relative import — itulah pembagiannya.

Python Editor

Jalankan kode untuk melihat output

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:

Layout Mendekati Proyek Nyata
project/ (project root)
  • main.py — entry point (file yang kamu jalankan dengan python)
  • config.py — pengaturan seluruh aplikasi
my_app/
  • __init__.py — API publik package my_app
database/
  • __init__.py
  • connection.py / models.py
utility/
  • __init__.py
  • validator.py / helper.py
Entry point 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.

Batas Antara Absolute dan Relative
my_app/utility/main.pyvalidator.pyhelper.pyabsoluterelative
main.py masuk ke package utility dengan absolute (jalur dari root); di dalam utility, validator → helper pakai relative (jalur dari posisi sekarang). Mengetahui batasnya menjaga rename folder nanti tidak menyebar.

Implementasikan validator.py di dalam package my_app/utility/ sendiri dan panggil dari main.py (buka validator.py dari 📂 Files, edit, lalu simpan dengan Cmd+S atau 💾). helper.py sudah selesai.

① Implementasikan validate_user(email) di validator.py:

- Mulai dengan from .helper import log_messagerelative import helper.py di folder yang sama

- Jika email mengandung baik @ maupun ., panggil log_message("OK: " + email) dan return True

- Selain itu panggil log_message("masalah terdeteksi: " + email) dan return False

② Di main.py, impor dengan absolute import (from my_app.utility import validate_user) dan print(validate_user("budi@example.com")).

Python Editor

Jalankan kode untuk melihat output

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.

Layout Proyek (catalog dan billing sebagai Dua Package)
project/ ← project root
  • main.py — entry point (memakai kedua package untuk menangani pesanan)
catalog/
  • __init__.py — me-ekspor ulang from .products import get_price
  • products.py — mengimplementasikan get_price(name)
billing/
  • __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.
main.py Mengoreografikan Dua Package
catalogget_price()main.pymengoreografi keduanyabillingformat_invoice()from catalog import get_pricefrom billing import ...
main.py memanggil keluar dengan absolute import untuk mengambil unit_price dari catalog dan memformat invoice via billing. Tiap package internalnya pakai relative import di __init__.py untuk me-ekspor ulang .products / .invoice.

Selesaikan package catalog/ dan billing/ terlampir termasuk file __init__.py-nya, lalu koreografikan dari main.py dengan absolute import (buka tiap file dari 📂 Files dan simpan dengan Cmd+S atau 💾).

① Di catalog/products.py, implementasikan get_price(name)"apple" → 100, "orange" → 150, selain itu 0 (dict + dict.get(name, 0) praktis).

② Di catalog/__init__.py, tulis from .products import get_price agar luar bisa memanggil from catalog import get_price (relative import).

③ Di billing/invoice.py, implementasikan format_invoice(name, qty, unit_price) — hitung qty * unit_price dan kembalikan string seperti "apple x 3 = $300".

④ Di billing/__init__.py, tulis from .invoice import format_invoice.

⑤ Di main.py, lakukan from catalog import get_price dan from billing import format_invoice (absolute import). Pesan 3 buah "apple", cari unit price, format invoice, dan print().

Python Editor

Jalankan kode untuk melihat output

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__ Mempersempit Apa yang Diekspos
__init__.pyfrom .calc importadd, multiply__all__ = ["add"]from pkgimport *add→ tertarik masukmultiply→ tidak tertarik(NameError)
Dengan __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 ``**.

Verifikasi perilaku __all__ dengan package bundle/ terlampir. Buka bundle/__init__.py dari 📂 Files — ia menarik baik add maupun multiply tapi mempersempit eksposnya dengan __all__ = ["add"].

① Di main.py, lakukan from bundle import * dan cetak add(2, 3).

② Lalu bungkus panggilan multiply(2, 3) dengan try/except dan verifikasi bahwa ia memunculkan NameError (latihan ini memakai runtime Pyodide yang kompatibel-CPython — pemuatan pertama butuh 5–15 detik).

Python Editor

Jalankan kode untuk melihat output

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.

QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1File apa yang harus kamu tempatkan di sebuah folder agar dikenali sebagai package?

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)?