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 tidak return nilai (selain None implisit)
  • 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.

buku_a judul · penulis · stok instance di memori def info(self): self = buku_a baca self.judul, … return string buku_a.info()
Method selalu “tahu” object-nya lewat self.

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 lewat self.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. 1
    Tulis data wajib di __init__ Judul, penulis, tahun, stok — semua lewat self.nama = ...
  2. 2
    Pisahkan baca vs ubah info() hanya baca; pinjam() mengubah stok.
  3. 3
    Selalu tulis self di method def nama(self, ...): — jangan dihilangkan.
  4. 4
    Validasi di batas yang jelas Tolak stok negatif / tahun aneh sedini mungkin.
  5. 5
    Uji lewat beberapa instance Pastikan ubah buku_a tidak merusak buku_b.
Ringkas: init → attribute → method baca/ubah → uji multi-instance.

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

  1. Tambah attribute kategori (string) ke Buku. Cetak lewat info().
  2. Buat method apakah_tersedia(self) yang mengembalikan True jika stok > 0.
  3. 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).