Pendahuluan — menjaga data di dalam object
Di Attribute, Method, dan __init__ (#42) kamu sudah punya self.stok dan method pinjam(). Masalahnya: siapa pun masih bisa menulis buku.stok = -99 dari luar — aturan bisnis dilompati.
Encapsulation adalah praktik menyembunyikan detail implementasi dan membuka akses lewat “pintu” yang kamu kendalikan. Di Python, itu bukan kunci gembok keras seperti bahasa lain — lebih ke konvensi + API (_, __, @property).
Prasyarat: Selesai #42 (attribute, method,
__init__). Fondasi object: Class & Object (#41) · mental model: Mengenal OOP (#40).
Masalah: attribute publik terlalu terbuka
Tanpa penjaga, state bisa rusak dari mana saja:
class Buku:
def __init__(self, judul, tahun, stok=1):
self.judul = judul
self.tahun = tahun
self.stok = stok
def pinjam(self):
if self.stok <= 0:
raise ValueError("stok habis")
self.stok -= 1
buku = Buku("Belajar Python", 2024, stok=2)
buku.stok = -5 # lolos — pinjam() tidak dipanggil
buku.tahun = 1200 # tahun mustahil, tetap diterima
print(buku.stok, buku.tahun)
Method pinjam() sudah benar, tapi “pintu belakang” masih terbuka. Encapsulation menutup pintu itu — atau setidaknya membuatnya sengaja dan terlihat.
Tiga tingkat akses di Python
Python tidak punya private keyword seperti Java. Yang ada: kesepakatan nama:
| Nama | Arti konvensi | Contoh |
|---|---|---|
stok |
Publik — boleh diakses dari luar | buku.stok |
_stok |
“Internal” — jangan disentuh dari luar kecuali sadar | self._stok |
__stok |
Name-mangling — Python ubah jadi _Buku__stok |
hindari bentrok di inheritance |
class Buku:
def __init__(self, judul, stok=1):
self.judul = judul # publik OK untuk label
self._stok = stok # internal — pakai _ awalan
self.__kode = "BK-01" # name-mangling
buku = Buku("Belajar Python", stok=2)
print(buku.judul) # OK
print(buku._stok) # bisa, tapi “jangan” — sinyal internal
# print(buku.__kode) # AttributeError
print(buku._Buku__kode) # bisa, tapi jelek — hindari
Gunakan _ untuk state yang ingin kamu jaga. Cadangkan __ jika benar-benar khawatir bentrok nama di subclass (lihat Inheritance (#44)). Jangan mengira __ = keamanan mutlak.
@property — baca lewat API bersih
@property membuat method terlihat seperti attribute saat dibaca. Dari luar: buku.stok. Di dalam: kamu yang tentukan dari mana nilai itu datang:
class Buku:
def __init__(self, judul, tahun, stok=1):
self.judul = judul
self._tahun = tahun
self._stok = stok
@property
def stok(self):
"""Baca stok — dari luar seperti attribute."""
return self._stok
@property
def tahun(self):
return self._tahun
buku = Buku("Belajar Python", 2024, stok=2)
print(buku.stok) # 2 — memanggil property, bukan field mentah
print(buku.tahun) # 2024
# buku.stok = 9 # AttributeError: can't set attribute (belum ada setter)
Ini jembatan bagus dari #42: pembaca masih menulis buku.stok, tapi kamu sudah mengontrol implementasinya. Di contoh di atas kita isi _tahun/_stok langsung karena belum ada setter — setelah ada setter (bagian berikutnya), prefer self.tahun = tahun di __init__ supaya validasi ikut jalan.
@setter — validasi saat menulis
Tambah setter agar penulisan juga lewat pintu yang sama. Di sini kita jaga tahun dan stok:
class Buku:
def __init__(self, judul, tahun, stok=1):
self.judul = judul
self.tahun = tahun # lewat setter di bawah
self.stok = stok
@property
def tahun(self):
return self._tahun
@tahun.setter
def tahun(self, nilai):
if nilai < 1900 or nilai > 2100:
raise ValueError("tahun tidak masuk akal")
self._tahun = nilai
@property
def stok(self):
return self._stok
@stok.setter
def stok(self, nilai):
if nilai < 0:
raise ValueError("stok tidak boleh negatif")
self._stok = nilai
def pinjam(self):
if self.stok <= 0:
raise ValueError("stok habis")
self.stok -= 1
return self.stok
buku = Buku("Belajar Python", 2024, stok=2)
print(buku.stok) # 2
buku.stok = 1 # OK lewat setter
print(buku.pinjam()) # 0
# buku.stok = -1 # ValueError: stok tidak boleh negatif
# Buku("Kuno", 1800) # ValueError: tahun tidak masuk akal
Perhatikan: di __init__ kita menulis self.tahun = tahun dan self.stok = stok — itu memanggil setter, jadi validasi jalan sejak object lahir. Jangan assign langsung ke self._tahun di __init__ kalau kamu ingin aturan yang sama berlaku di mana-mana (kecuali kasus khusus yang kamu sadari).
Bonus kecil: di pinjam(), baris self.stok -= 1 juga lewat property — Python membaca lewat getter, lalu menulis lewat setter. Aturan “stok tidak negatif” tetap dijaga.
Tip: Method seperti
pinjam()tetap berguna untuk aksi bisnis (“pinjam satu salinan”). Property menjaga invariant data (stok ≥ 0, tahun wajar). Keduanya saling melengkapi — bukan saling mengganti.
Jebakan paling sering di property: di dalam setter kamu menulis self.stok = ... lagi — itu memanggil setter yang sama, lalu RecursionError:
# SALAH — setter memanggil dirinya sendiri
class BukuSalah:
def __init__(self, stok=1):
self.stok = stok
@property
def stok(self):
return self._stok
@stok.setter
def stok(self, nilai):
self.stok = nilai # memanggil setter lagi → RecursionError
# BukuSalah(2) # RecursionError: maximum recursion depth exceeded
# BENAR — simpan ke attribute internal
class BukuBenar:
def __init__(self, stok=1):
self.stok = stok
@property
def stok(self):
return self._stok
@stok.setter
def stok(self, nilai):
if nilai < 0:
raise ValueError("stok tidak boleh negatif")
self._stok = nilai
b = BukuBenar(2)
print(b.stok) # 2
Kapan encapsulation terasa penting?
- Proyek tumbuh — banyak file menyentuh object yang sama; satu pintu mengurangi bug “siapa yang set stok aneh?”
- API publik class — kamu bisa ganti penyimpanan internal (
_stok→ hitung dari list pinjaman) tanpa mengubah pemanggilanbuku.stok - Validasi terpusat — aturan tahun/stok tidak diduplikasi di 10 tempat
Untuk script 30 baris sekali pakai, attribute publik dari #42 sering cukup. Encapsulation mulai “wajib secara praktik” saat class dipakai orang lain (atau dirimu 3 bulan kemudian).
Pola Dasar — merancang encapsulation
-
1
Tandai state sensitif dengan _
_stok,_tahun— sinyal “internal”. -
2
Buka baca lewat @property API luar tetap
buku.stok, implementasi bisa berubah. -
3
Validasi di @setter Tolak nilai mustahil sedini mungkin.
-
4
Init lewat property
self.stok = stokdi__init__supaya aturan sama. -
5
Method untuk aksi bisnis
pinjam()/kembalikan()— bukan hanya setter mentah.
Kode lengkap — salin & jalankan
Simpan sebagai buku_terlindungi.py, lalu python buku_terlindungi.py:
class Buku:
def __init__(self, judul, penulis, tahun, stok=1):
self.judul = judul
self.penulis = penulis
self.tahun = tahun
self.stok = stok
@property
def tahun(self):
return self._tahun
@tahun.setter
def tahun(self, nilai):
if nilai < 1900 or nilai > 2100:
raise ValueError("tahun tidak masuk akal")
self._tahun = nilai
@property
def stok(self):
return self._stok
@stok.setter
def stok(self, nilai):
if nilai < 0:
raise ValueError("stok tidak boleh negatif")
self._stok = nilai
def info(self):
return f"{self.judul} · {self.penulis} ({self.tahun}) · stok {self.stok}"
def pinjam(self):
if self.stok <= 0:
raise ValueError("stok habis")
self.stok -= 1
return self.stok
def kembalikan(self):
self.stok += 1
return self.stok
a = Buku("Belajar Python", "Sari", 2024, stok=2)
print(a.info())
print("pinjam:", a.pinjam())
print("stok:", a.stok)
a.stok = 5
print("set stok:", a.stok)
# a.stok = -1 # ValueError
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
AttributeError: property 'stok' of 'Buku' object has no setter |
Hanya ada @property, belum @stok.setter |
Tambah setter, atau jangan assign dari luar |
RecursionError di property |
Di dalam getter/setter menulis self.stok lagi (bukan self._stok) |
Simpan di self._stok; property hanya pintu |
Validasi tidak jalan di __init__ |
Assign langsung self._stok = stok tanpa lewat setter |
Pakai self.stok = stok di init |
AttributeError: ... has no attribute '_Buku__x' bingung |
Mengira __x “rahasia absolut”, lalu akses salah nama |
Lebih sering cukup _x; pahami mangling sebelum pakai __ |
| Setter terlalu ketat / longgar | Aturan bisnis tidak jelas | Tulis invariant dulu (stok ≥ 0), baru kode |
Latihan singkat
- Tambah property
tersedia(read-only) yang mengembalikanTruejikastok > 0. - Buat setter
judulyang menolak string kosong. - Coba sengaja tulis
self.stok = self.stokdi dalam setter (tanpa_) — amatiRecursionError, lalu perbaiki.
FAQ singkat
Apakah _ benar-benar mencegah akses?
Tidak. Itu sinyal sosial untuk programmer. Python percaya dewasa: kamu bisa menyentuh _stok, tapi sebaiknya tidak.
Haruskah semua attribute jadi property?
Tidak. Mulai dari data yang punya aturan (stok, tahun, email). Label sederhana seperti judul sering aman publik dulu.
Apa bedanya property dan method info()?
Property terasa seperti data (buku.stok). Method terasa seperti aksi atau ringkasan (buku.info()). Pilih yang paling jujur dibaca orang lain.
Kesimpulan & langkah berikutnya
Encapsulation di Python = konvensi nama + pintu resmi (@property / setter) supaya invariant tetap hidup. Kamu tidak mengunci dunia — kamu merapikan kontrak class.
Artikel ini adalah #43 (ini) — langkah keempat Seri 3 setelah Attribute & __init__ (#42).
Lanjut ke Inheritance & super() (#44): class Ebook(Buku), super().__init__, dan override method tanpa menduplikasi kode.
Seri 3 progress: 5/10 artikel live. Kamu di langkah #43 (ini). Prasyarat: #42 · #41 · #40.