Belajar dengan membaca secara berurutan

Variabel Private dan Encapsulation — Akses Aman lewat getter / setter

Pelajari variabel private dan encapsulation di Python. Konvensi _x vs name mangling __x, get_xxx / set_xxx untuk akses aman, dan gaya Pythonic @property / @xxx.setter — semua hands-on.

Sebelumnya kita membahas dua pilar pertama OOP — pewarisan dan polimorfisme. Artikel ini menutup pilar ketiga: encapsulation.

Variabel Private — Python Tidak Punya Privasi Sungguhan

Java dan C++ punya keyword private yang memblokir akses dari luar saat kamu mendeklarasikannya. Python tidak punya privasi yang ditegakkan bahasa. Sebagai gantinya, jumlah underscore di depan menjadi sinyal "ini untuk pemakaian internal" atau "jangan disentuh langsung" — sebuah konvensi antar programmer, bukan aturan keras.

0 / 1 / 2 underscore menyampaikan niat
name(tidak ada)atribut publicbebas dipakaidari luar_name(1)konvensi:privatejangan disentuh__name(2)
Jumlah underscore tidak mengubah penegakan; itu cuma label yang menandakan niat.

Single underscore _x — privasi konvensi saja

Menambahkan satu _ di depan nama memberitahu komunitas Python "atribut ini untuk pemakaian internal class — jangan diakses langsung dari luar". Parameter __init__ tetap memakai nama biasa; hanya field self. yang diberi awalan _.

class UserAccount:
    def __init__(self, owner_name, balance):
        self._owner_name = owner_name      # internal -> diawali _
        self._balance    = balance

    def get_info(self):                     # accessor menghadap luar
        return {"owner": self._owner_name, "balance": self._balance}


user = UserAccount("Budi", 50000)
print(user._balance)        # 50000  <- jalan tetapi tidak disarankan
print(user.get_info())      # {'owner': 'Budi', 'balance': 50000}  <- disarankan

Pakai class BlogPost untuk merasakan arti _ (skenario berbeda dari sampel di atas — bentuk sama, field beda).

① Definisikan class BlogPost: dan tetapkan self._title dan self._views di __init__(self, title, views).

② Definisikan summary(self) yang return {"title": self._title, "views": self._views}.

③ Buat post = BlogPost("Mulai dengan Python", 100). Pertama, akses langsung dengan post._views dan print (jalan tetapi pola yang dilarang).

④ Kemudian pakai pola yang disarankanprint(post.summary()).

(Begitu kode kamu berjalan dengan benar, penjelasan akan muncul.)

Python Editor

Jalankan kode untuk melihat output

Double underscore __x — name mangling

Menambahkan dua _ di depan membuat Python menulis ulang nama atributnya sendiri. Kalau kamu menulis self.__pin = 1234 di dalam class Account, nama yang sebenarnya disimpan menjadi _Account__pin. Ini disebut name manglingobj.__pin dari luar tidak menemukan apa-apa, jadi akses secara efektif jauh lebih sulit.

__pin ditulis ulang jadi _Account__pin secara internal
self.__pin= 1234Python menulisulang nama_Account__pin= 1234obj.__pin-> Errorobj._Account__pin-> 1234simpan
Atribut double-underscore disimpan di bawah "underscore + nama class + nama asli". Membaca obj.__pin langsung gagal dengan AttributeError karena key itu tidak ada di sana.
class Account:
    def __init__(self, owner, pin):
        self._owner = owner       # privasi konvensi saja
        self.__pin  = pin         # name-mangled (jadi _Account__pin)

acc = Account("Budi", 1234)

print(acc._owner)              # Budi             <- jalan normal
# print(acc.__pin)             # AttributeError <- tidak terlihat langsung
print(acc._Account__pin)       # 1234            <- nama mangled menjangkaunya

Pakai class LoginForm untuk memverifikasi bahwa __password benar-benar ditulis ulang (skenario berbeda dari sampel Account, perilaku sama).

① Definisikan class LoginForm: dan di __init__(self, username, password) tetapkan self._username = username dan self.__password = password.

② Buat form = LoginForm("budi", "p@ssw0rd"). Pakai print(form._username) untuk konfirmasi bahwa sisi single-underscore terbaca normal.

③ Pakai print(form._LoginForm__password) untuk mengambil password lewat nama mangled-nya.

④ Jalankan print([n for n in dir(form) if not n.startswith('__')]) untuk membuang nama atribut yang sebenarnya tersimpan di instance dan lihat _LoginForm__password ada di daftar.

Python Editor

Jalankan kode untuk melihat output

"__" juga bukan tembok mutlak

Double underscore mencegah akses obj.__pin langsung, yang satu tingkat lebih kuat. Tetapi siapa pun yang tahu nama mangled obj._Account__pin masih bisa menjangkaunya. Ini bukan privasi sungguhan. Di proyek nyata, single underscore _x jauh lebih umum kecuali ada alasan spesifik untuk memakai mangling.

Encapsulation — batasi akses ke method khusus

Encapsulation adalah ide desain "bundel atribut data dan method yang mengoperasikannya ke dalam satu class, dan paksa kode luar untuk masuk lewat sekumpulan kecil entry point yang dipublikasikan". Jadi bagaimana kita membangun entry point itu?

Salurkan baca/tulis lewat satu method
konsistensirusaktulis langsung_price = -100❌ luarproduct._price= -100konsistensiterjagaset_price()memvalidasi✅ luarproduct.set_price(100)apa adanyahanya saat OK
Tulis langsung membuat nilai apa pun bisa masuk ke _price tanpa diperiksa. Lewat method berarti setter memvalidasi tipe dan range di satu tempat.

Gaya paling dasar adalah menulis method get_xxx / set_xxx dengan tangan. Di dalam setter, lakukan pemeriksaan isinstance untuk tipe dan pemeriksaan range, dan raise ValueError(...) kalau ada yang salah. Dengan itu di tempat, tidak ada nilai sampah yang pernah mencapai _price.

class Product:
    def __init__(self, name, price, stock):
        self._name  = name
        self._price = price
        self._stock = stock

    def get_price(self):
        return self._price

    def set_price(self, price):
        if isinstance(price, int) and price >= 0:
            self._price = price
        else:
            raise ValueError("price must be a non-negative integer")


product = Product("Kaos", 1500, 30)
print(product.get_price())     # 1500
product.set_price(2000)
print(product.get_price())     # 2000
# product.set_price(-100)      # ValueError

Tambahkan getter dan setter khusus age ke class form pendaftaran UserProfile. Age tidak boleh negatif atau 200+, jadi setter harus menjaga range.

① Definisikan class UserProfile: dan di __init__(self, name, age) tetapkan _name dan _age.

② Definisikan get_age(self) dan return self._age.

③ Definisikan set_age(self, age). Hanya saat age adalah int dan 0 <= age <= 150 jalankan self._age = age; selain itu raise ValueError("age must be an integer in [0, 150]").

④ Buat user = UserProfile("Budi", 30), print user.get_age(), lalu user.set_age(31) dan print hasilnya lagi.

⑤ Konfirmasi jalur penolakan: bungkus user.set_age(-5) dengan try / except ValueError as e: dan print("NG:", e).

Python Editor

Jalankan kode untuk melihat output

@property dan @xxx.setter

Gaya get_price() / set_price(...) jelas, tetapi tempat panggilnya jadi terlihat seperti panggilan method — bukan yang paling bersih. Idiom Python yang lebih halus memakai dua dekorator: @property dan @xxx.setter.

Dengan keduanya, tempat panggil tetap sebagai product.price / product.price = 2000akses atribut biasa — tetapi di bawahnya, method getter dan setter dipanggil. Itu struktur dua-lapis di mana sintaks tetap sederhana tetapi logika tetap berjalan.

product.price terlihat sama; method berjalan di bawahnya
product.price@propertydef pricereturnself._priceproduct.price= 2000@price.setterdef pricevalidasi, laluupdate self._pricebacatulis
Sintaks akses-atribut tetap apa adanya; @property mengarahkan ulang baca dan @price.setter mengarahkan ulang tulis ke panggilan method. Validasi tinggal di dalam setter.
class Product:
    def __init__(self, name, price):
        self._name  = name
        self._price = price

    @property
    def price(self):                 # getter
        return self._price

    @price.setter
    def price(self, value):          # setter — nama harus sama dengan getter
        if not isinstance(value, int) or value < 0:
            raise ValueError("price must be a non-negative integer")
        self._price = value

    @property
    def label(self):                 # computed property — nilai turunan
        return f"{self._name} ({self._price})"


product = Product("Kaos", 1500)
print(product.price)         # 1500              <- @property dipanggil
product.price = 2000         # <- @price.setter dipanggil
print(product.price)         # 2000
print(product.label)         # Kaos (2000)       <- computed property

Jaga nama setter identik dengan getter

price di @price.setter harus cocok dengan nama method dari @property def price sebelumnya. Python menafsirkan dekorator itu sebagai "tempelkan versi tulis ke object price sama yang sudah punya versi baca" — kalau nama melenceng, keduanya diperlakukan sebagai hal terpisah.

Tulis ulang UserProfile yang sama dari Latihan 3 untuk memakai @property / @age.setter, dan tambahkan computed property age_group (under 18 / adult / senior).

① Definisikan class UserProfile: dengan __init__(self, name, age) yang menetapkan _name / _age.

② Pakai @property def age(self): untuk return self._age.

③ Pakai @age.setter def age(self, value):; hanya saat isinstance(value, int) and 0 <= value <= 150 tetapkan self._age = value, selain itu raise ValueError("age must be an integer in [0, 150]").

④ Pakai @property def age_group(self): untuk return "under 18" kalau self._age < 18, "adult" kalau self._age < 65, selain itu "senior" (tanpa setter — read-only).

⑤ Buat user = UserProfile("Budi", 30), print user.age dan user.age_group, lalu user.age = 70 dan print age_group sekali lagi.

Python Editor

Jalankan kode untuk melihat output

Tiga Pilar OOP

Apa yang didukung encapsulation
Encapsulation
  • Proteksi data — pisahkan state internal dari API publik via _x
  • Konsistensi — pusatkan validasi di setter, satu tempat
  • Kebebasan implementasi — ubah internal tanpa mengubah API publik
  • Gaya Pythonic — konvensi plus @property, bukan penegakan bahasa
Pewarisan
  • Pakai ulang mesin induk
Polimorfisme
  • Nama method sama, perilaku beda per tipe
Encapsulation
  • Salurkan akses luar lewat sekumpulan kecil pintu
Pewarisan, polimorfisme, dan encapsulation adalah tiga pilar OOP. Pewarisan memakai ulang, polimorfisme menyatukan tempat panggil, dan encapsulation membuat seluruhnya sulit dirusak — menetapkan tiap peran secara sadar menjaga desain class tetap bersih.
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Mana pernyataan paling akurat tentang variabel private Python?

Soal 2Apa manfaat terbesar memakai @property dan @xxx.setter?

Soal 3Di dalam class Account: kamu menulis self.__pin = 1234. Dari luar, acc.__pin memunculkan AttributeError. Nama apa yang sebenarnya tersimpan di instance?