Domain perpustakaan tidak punya aturan bawaan yang harus dijaga ketat — judul yang salah cuma salah ketik. Domain keuangan lain ceritanya: saldo rekening bank tidak boleh negatif, penarikan tidak boleh melebihi saldo, dan setoran harus bernilai positif. Encapsulation adalah praktik membundulkan data bersama method-method yang menjaga aturannya, lalu membatasi akses langsung dari luar. Untuk itu kita sementara berganti domain ke Rekening — kasus uang memang contoh terbaik encapsulation. Bahasa lain mengunci data dengan kata kunci private; Python memilih jalan berbeda, dan chapter ini menjelaskan jalannya.

Masalah Attribute Publik #

Secara default, semua attribute di Python bersifat publik — siapa pun bisa membaca dan menulis dari luar class:

python
class Rekening:
    def __init__(self, pemilik, saldo):
        self.pemilik = pemilik
        self.saldo = saldo

rekening = Rekening("Budi", 500000)

rekening.saldo = -1000000

print(rekening.saldo)

Output yang Dihasilkan:

text
-1000000

Tidak ada peringatan, tidak ada validasi — aturan bisnis “saldo tidak boleh negatif” remuk dalam satu baris. Masalahnya bukan hilangnya tembok pengaman, melainkan tidak adanya titik tunggal pengendalian: kode mana pun di program bisa mengubah saldo lewat jalan pintas.

Konvensi _protected dan Method Penjaga #

Python memakai konvensi: attribute yang diawali satu underscore (_saldo) dibaca sebagai “internal — jangan disentuh dari luar”. Konvensi ini tidak ditegakkan interpreter, jadi kekuatannya ada di kesepakatan sosial. Class berikut memindahkan semua perubahan saldo ke method yang bisa memeriksa aturan dulu:

python
class Rekening:
    def __init__(self, pemilik, saldo_awal):
        self.pemilik = pemilik
        self._saldo = saldo_awal

    def setor(self, jumlah):
        if jumlah <= 0:
            print("Jumlah setor harus lebih dari 0")
            return
        self._saldo += jumlah

    def tarik(self, jumlah):
        if jumlah > self._saldo:
            print("Saldo tidak cukup")
            return
        self._saldo -= jumlah

rekening = Rekening("Budi", 500000)

rekening.setor(250000)
rekening.tarik(100000)
rekening.tarik(999999999)

print("Saldo akhir:", rekening._saldo)

Output yang Dihasilkan:

text
Saldo tidak cukup
Saldo akhir: 650000

Mari kita telaah hasilnya:

  1. Baris 4 (self._saldo = saldo_awal): satu underscore di depan memberi sinyal “internal” kepada pembaca kode.
  2. Method setor() dan tarik(): keduanya memvalidasi permintaan sebelum menyentuh _saldo. Penarikan sebesar 999999999 ditolak dengan pesan, dan saldo tetap 650000 (500000 + 250000 − 100000).
  3. Baris terakhir (rekening._saldo): akses langsung seperti ini tetap bisa — interpreter tidak melarang. Konvensi hanya sinyal, bukan tembok.

Filosofi ini sering diringkas sebagai consenting adults: Python percaya pembaca kode adalah orang dewasa yang bisa menghormati konvensi. Library yang mengandalkan _attribute internal memberi tahu kita “boleh saja memaksa, tapi jangan salahkan penulis library jika versi berikutnya berubah”.

Name Mangling dengan __saldo #

Dua underscore di depan (__saldo) memicu mekanisme berbeda: name mangling. Interpreter menulis ulang nama attribute tersebut menjadi _NamaClass__saldo pada saat class didefinisikan:

python
class Rekening:
    def __init__(self, pemilik, saldo_awal):
        self.pemilik = pemilik
        self.__saldo = saldo_awal

    def lihat_saldo(self):
        return self.__saldo

rekening = Rekening("Siti", 1000000)

print(rekening.lihat_saldo())
print(rekening.__dict__)

Output yang Dihasilkan:

text
1000000
{'pemilik': 'Siti', '_Rekening__saldo': 1000000}

Kamus __dict__ memperlihatkan kebenarannya: attribute yang tersimpan bernama _Rekening__saldo, bukan __saldo. Akibatnya, akses dengan nama aslinya dari luar class gagal:

python
class Rekening:
    def __init__(self, pemilik, saldo_awal):
        self.pemilik = pemilik
        self.__saldo = saldo_awal

    def lihat_saldo(self):
        return self.__saldo

rekening = Rekening("Siti", 1000000)

print(rekening.__saldo)

Output yang Dihasilkan:

text
Traceback (most recent call last):
  File "07d-error-saldo.py", line 11, in <module>
    print(rekening.__saldo)
          ^^^^^^^^^^^^^^^^
AttributeError: 'Rekening' object has no attribute '__saldo'

Interpreter mencari nama __saldo di kamus instance, tidak menemukannya (yang tersimpan adalah bentuk mangled), lalu memunculkan AttributeError. Nama file pada baris File "..." bisa berbeda di komputer lain — bentuk pesannya tetap sama.

Bentuk _Rekening__saldo tetap bisa diakses kalau kita benar-benar menuliskannya — jadi ini juga bukan pengamanan sungguhan. Tujuan utama name mangling adalah mencegah akses tak sengaja dan mencegah tabrakan nama attribute antara class induk dan anak saat kita sampai ke inheritance.

Pratinjau property #

Begitu data bergerak ke balik method penjaga, membaca rekening.saldo() terasa kaku — padahal membaca data. Decorator @property menjembatani keduanya: pemanggil menulis seperti membaca attribute, interpreter menjalankan sebuah method:

python
class Rekening:
    def __init__(self, pemilik, saldo_awal):
        self.pemilik = pemilik
        self._saldo = saldo_awal

    @property
    def saldo(self):
        return self._saldo

rekening = Rekening("Budi", 500000)

print(rekening.saldo)

Output yang Dihasilkan:

text
500000

Perhatikan baris terakhir: rekening.saldo tanpa tanda kurung, namun method tetap berjalan. Kombinasi _saldo + @property memberi akses baca yang rapi sambil menjaga titik tunggal pengendalian — suatu hari validasi bisa ditambahkan tanpa mengubah kode pemanggil. Pembahasan penuh property (termasuk setter dan validasi tulis) menyusul pada chapter Properties di Section Advanced Data Modeling.