Pendahuluan — self, state, dan perilaku
Di Class dan Object Pertama (#41) kamu sudah menulis class Buku, membuat instance, dan melihat identitas object. Hari ini kita bedah tiga hal yang membuat class “hidup”:
- Attribute — data yang melekat pada object (state)
- Method — fungsi di dalam class yang bekerja lewat
self - Constructor
__init__— tempat menyiapkan state saat object lahir
Prasyarat: Selesai #41 (atau setidaknya bisa membuat
class+ instance). Mental model OOP: Mengenal OOP (#40).
Attribute = state milik object
Attribute adalah nama yang kamu simpan di self. Tiap instance punya salinan nilainya sendiri:
class Buku:
def __init__(self, judul, penulis, tahun, stok=1):
self.judul = judul # attribute
self.penulis = penulis # attribute
self.tahun = tahun # attribute
self.stok = stok # state awal — berapa salinan tersedia
buku_a = Buku("Belajar Python", "Sari", 2024, stok=2)
buku_b = Buku("ESP32 Praktis", "Budi", 2023)
print(buku_a.judul) # Belajar Python
print(buku_a.stok) # 2
buku_a.tahun = 2025 # ubah state object ini saja
print(buku_b.tahun) # 2023 — tidak ikut berubah
Convention: nama attribute pakai snake_case. Jangan takut menambah attribute di __init__ kalau memang bagian dari “siapa object ini”.
__init__ = constructor: menyiapkan object
__init__ dipanggil otomatis saat kamu menulis Buku(...). Parameter pertama wajib self — itu object yang sedang dibuat. Argumen setelahnya kamu yang tentukan:
class Buku:
def __init__(self, judul, penulis, tahun, stok=1):
if tahun < 1900:
raise ValueError("tahun tidak masuk akal")
self.judul = judul
self.penulis = penulis
self.tahun = tahun
self.stok = stok
buku = Buku("Belajar Python", "Sari", 2024)
print(buku.judul, buku.stok)
# Buku("Kuno", "Anonim", 1800) # ValueError: tahun tidak masuk akal
Catatan praktis:
__init__biasanya tidakreturnnilai (selainNoneimplisit)- Validasi ringan di sini boleh — detail “penyembunyian” lebih dalam di Encapsulation (#43)
- Default argument (
stok=1) membuat pemanggilan lebih nyaman
self — kenapa selalu ada?
Di #41 kamu sudah melihat method info(). Saat menulis buku_a.info(), Python menerjemahkannya kira-kira menjadi Buku.info(buku_a). Parameter self menerima object pemanggil. Tanpa self, method tidak tahu attribute siapa yang dibaca.
Method baca vs method ubah state
Method bisa hanya membaca attribute, atau mengubah state object:
class Buku:
def __init__(self, judul, penulis, tahun, stok=1):
self.judul = judul
self.penulis = penulis
self.tahun = tahun
self.stok = stok
def info(self):
"""Method baca — tidak mengubah state."""
return f"{self.judul} oleh {self.penulis} ({self.tahun}) · stok {self.stok}"
def pinjam(self):
"""Method ubah state — stok berkurang."""
if self.stok <= 0:
raise ValueError("stok habis")
self.stok -= 1
return self.stok
def kembalikan(self):
self.stok += 1
return self.stok
buku = Buku("Belajar Python", "Sari", 2024, stok=2)
print(buku.info())
print("sisa:", buku.pinjam()) # 1
print("sisa:", buku.pinjam()) # 0
# buku.pinjam() # ValueError: stok habis
print("sisa:", buku.kembalikan()) # 1
Pola ini membuat aturan bisnis (stok tidak boleh negatif) tinggal di satu tempat — dekat datanya — bukan tersebar di banyak fungsi lepas.
Jebakan klasik: menulis
riwayat = []di tubuh class (bukan di__init__) membuat semua instance berbagi list yang sama. Selalu inisialisasi data per-object lewatself.riwayat = []di dalam__init__.
# SALAH — list di tubuh class = saling berbagi
class BukuSalah:
riwayat = []
def catat(self, nama):
self.riwayat.append(nama)
a = BukuSalah(); b = BukuSalah()
a.catat("Rina")
print(b.riwayat) # ['Rina'] — b ikut berubah!
# BENAR — list per instance
class BukuBenar:
def __init__(self):
self.riwayat = []
def catat(self, nama):
self.riwayat.append(nama)
a = BukuBenar(); b = BukuBenar()
a.catat("Rina")
print(b.riwayat) # [] — aman
Parameter method: self + argumen tambahan
Method boleh menerima argumen selain self. Contoh class lengkap dengan method pinjam_untuk:
class Buku:
def __init__(self, judul, penulis, tahun, stok=1):
self.judul = judul
self.penulis = penulis
self.tahun = tahun
self.stok = stok
def pinjam_untuk(self, nama_anggota):
if self.stok <= 0:
raise ValueError("stok habis")
self.stok -= 1
return f"{nama_anggota} meminjam {self.judul}"
buku = Buku("Belajar Python", "Sari", 2024, stok=2)
print(buku.pinjam_untuk("Rina"))
# Rina meminjam Belajar Python
print("sisa stok:", buku.stok) # 1
Yang sering keliru: menulis buku.pinjam_untuk("Rina") tapi di definisi lupa self — Python mengira "Rina" adalah self, lalu muncul TypeError argumen tertukar/kurang.
Pola Dasar — merancang attribute & method
-
1
Tulis data wajib di __init__ Judul, penulis, tahun, stok — semua lewat
self.nama = ... -
2
Pisahkan baca vs ubah
info()hanya baca;pinjam()mengubah stok. -
3
Selalu tulis self di method
def nama(self, ...):— jangan dihilangkan. -
4
Validasi di batas yang jelas Tolak stok negatif / tahun aneh sedini mungkin.
-
5
Uji lewat beberapa instance Pastikan ubah
buku_atidak merusakbuku_b.
Kode lengkap — salin & jalankan
Simpan sebagai perpustakaan_mini.py, lalu python perpustakaan_mini.py:
class Buku:
def __init__(self, judul, penulis, tahun, stok=1):
if tahun < 1900:
raise ValueError("tahun tidak masuk akal")
self.judul = judul
self.penulis = penulis
self.tahun = tahun
self.stok = stok
def info(self):
return f"{self.judul} · 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)
b = Buku("ESP32 Praktis", "Budi", 2023)
print(a.info())
print("pinjam a:", a.pinjam())
print("a stok:", a.stok, "| b stok:", b.stok)
print(a.kembalikan())
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
TypeError: Buku.__init__() missing ... required positional argument |
Memanggil Buku() tanpa argumen wajib |
Isi parameter: Buku("Judul", "Penulis", 2024) |
TypeError: ... missing 1 required positional argument: 'self' |
Memanggil lewat class tanpa instance, atau lupa self di definisi |
Pakai obj.method(); pastikan def method(self): |
TypeError: ... takes 1 positional argument but 2 were given |
Lupa menulis self di def, lalu memanggil dengan argumen |
Tambah self sebagai parameter pertama |
AttributeError: ... has no attribute 'stok' |
Attribute belum di-set di __init__, atau typo |
Cek self.stok = stok |
| Semua instance “ikut berubah” | List/dict diletakkan di tubuh class (attribute class bersama), bukan di __init__ |
Pakai self.riwayat = [] di dalam __init__, jangan riwayat = [] di level class |
Latihan singkat
- Tambah attribute
kategori(string) keBuku. Cetak lewatinfo(). - Buat method
apakah_tersedia(self)yang mengembalikanTruejikastok > 0. - Buat dua instance; pinjam salah satu berkali-kali sampai
ValueError. Pastikan instance lain tidak terdampak.
FAQ singkat
Apakah __init__ wajib?
Secara sintaks tidak. Secara praktik hampir selalu ya — tanpa itu attribute harus di-set manual setelah object dibuat.
Bolehkah method tanpa mengubah state?
Boleh dan dianjurkan. Method baca (seperti info) membuat kode lebih jelas.
Kapan pakai attribute class (bukan self)?
Untuk konstanta bersama yang jarang berubah. Untuk state per object, selalu self. Detail penyembunyian attribute: lihat Encapsulation (#43).
Kesimpulan & langkah berikutnya
__init__ menyiapkan state; attribute menyimpan data; method membaca atau mengubah state lewat self. Kalau tiga ini sudah nyaman, class Python-mu mulai terasa seperti “benda” yang punya tanggung jawab — bukan sekadar kumpulan dict.
Artikel ini adalah #42 (ini) — langkah ketiga Seri 3 setelah Class & Object (#41).
Lanjut ke Encapsulation & @property (#43): kita rapikan akses data dengan konvensi _ / __ dan property/setter agar stok & tahun tidak diubah sembarangan dari luar.
Seri 3 progress: 5/10 artikel live. Kamu di langkah #42 (ini). Prasyarat: Class & Object (#41) · fondasi: Mengenal OOP (#40) · lanjut: Encapsulation (#43).