Selain method biasa yang memakai self, class Python punya tiga alat lagi: @property untuk atribut yang dihitung atau dijaga, @staticmethod, dan @classmethod. Bayangin papan harga di Warung Kopi Pak Darto yang dijaga kasir. Siapa pun boleh menulis harga baru di papan, tapi tiap kali ada yang menulis, kasirnya mengecek dulu: harga minus? Ditolak. Pembeli yang membaca papan gak tahu ada kasir di baliknya. Itulah property: dari luar kelihatan seperti atribut biasa, tapi ada method yang jalan di belakangnya.
🧪 Tebakan cepat: class
Menupunya setterhargayang menolak angka negatif, dan__init__-nya menulisself.harga = harga. Apa yang terjadi padaMenu("es kopi", -3000): object-nya tetap jadi dengan harga -3000, atau program berhenti dengan error? Tebak dulu, jawabannya ada di bagian "Setter yang menjaga nilai".
Decorator dan empat jenis method
Decorator adalah baris @nama tepat di atas def. Dia mengubah cara method itu dipakai, tanpa mengubah isinya:
| Jenis | Cara menulis | Parameter pertama | Cara memanggil |
|---|---|---|---|
| Method biasa (instance method) | def total(self, jumlah): |
self, object-nya |
kopi.total(3) |
| Property | @property di atas def |
self |
kopi.label, tanpa kurung |
| Class method | @classmethod di atas def |
cls, class-nya |
Menu.dari_teks("roti bakar,12000") |
| Static method | @staticmethod di atas def |
gak ada | Menu.diskon(89000) |
@property: atribut yang dihitung
class Menu:
def __init__(self, nama, harga):
self.nama = nama
self.harga = harga
@property
def label(self):
return f"{self.nama} ({self.harga})"
kopi = Menu("kopi susu", 15000)
print(kopi.label) # kopi susu (15000)
kopi.harga = 17000
print(kopi.label) # kopi susu (17000)
@propertymembuat methodlabeldibaca seperti atribut:kopi.label, tanpa tanda kurung. Python memanggil method-nya di belakang layar.- Nilainya dihitung ulang tiap kali dibaca, jadi selalu ikut harga terbaru. Nilai seperti ini disebut atribut terkomputasi (computed attribute).
kopi.label()dengan kurung malah error.kopi.labelsudah berupa teks, dan teks gak bisa dipanggil:TypeError: 'str' object is not callable.- Property tanpa setter cuma bisa dibaca.
kopi.label = "murah"ditolak:AttributeError: property 'label' of 'Menu' object has no setter.
Setter yang menjaga nilai
class Menu:
def __init__(self, nama, harga):
self.nama = nama
self.harga = harga
@property
def harga(self):
return self._harga
@harga.setter
def harga(self, nilai):
if nilai < 0:
raise ValueError("harga tidak boleh negatif")
self._harga = nilai
kopi = Menu("kopi susu", 15000)
kopi.harga = 17000
print(kopi.harga) # 17000
kopi.harga = -5000
17000
Traceback (most recent call last):
File "/home/darto/warung.py", line 19, in <module>
kopi.harga = -5000
^^^^^^^^^^
File "/home/darto/warung.py", line 13, in harga
raise ValueError("harga tidak boleh negatif")
ValueError: harga tidak boleh negatif
@property def hargaadalah getter: jalan saatkopi.hargadibaca.@harga.setterdi atasdef harga(self, nilai)adalah setter: jalan saatkopi.harga = ...ditulis.nilaiberisi angka di kanan tanda=. Nama method-nya harus sama,harga.raise ValueError("...")menghentikan program dengan error yang kamu pilih. Cara menangkapnya ada di pelajaran try/except.- Angka aslinya disimpan di
self._harga. Garis bawah di depan nama adalah konvensi yang artinya "jangan disentuh dari luar class". Python gak melarangnya, tapi semua developer paham maksudnya. self.harga = hargadi__init__juga lewat setter. JadiMenu("es kopi", -3000)sudah ditolak sejak dibuat, denganValueError: harga tidak boleh negatif. Object-nya gak pernah jadi. Itu jawaban tebakannya.
@staticmethod dan @classmethod
class Menu:
def __init__(self, nama, harga):
self.nama = nama
self.harga = harga
@classmethod
def dari_teks(cls, teks):
nama, harga = teks.split(",")
return cls(nama, int(harga))
@staticmethod
def diskon(total):
if total >= 100000:
return 15
elif total >= 50000:
return 10
elif total >= 25000:
return 5
return 0
roti = Menu.dari_teks("roti bakar,12000")
print(roti.nama, roti.harga) # roti bakar 12000
print(Menu.diskon(89000)) # 10
print(roti.diskon(120000)) # 15
@staticmethodgak menerimaselfmaupuncls. Isinya fungsi biasa yang disimpan di dalam class karena masih berhubungan.diskondari pelajaran if/elif cuma butuh angka total, gak butuh data menu mana pun.@classmethodmenerimacls, yaitu class-nya sendiri, sebagai parameter pertama.cls(nama, int(harga))sama denganMenu(nama, int(harga)). Pola ini disebut alternative constructor: cara lain membuat object, di sini dari teks"roti bakar,12000", tanpa mengubah__init__.- Kenapa
cls, bukanMenulangsung? Kalau dipanggil dari turunan,MenuMinuman.dari_teks("es teh,7000"),clsberisiMenuMinuman, jadi yang dibuat object turunan. - Keduanya bisa dipanggil lewat nama class, tanpa membuat object dulu. Lewat object juga boleh:
roti.diskon(120000)memberi 15.
⚠️ Jebakan yang sering ketemu
- Memanggil property dengan kurung.
kopi.label()gagal:TypeError: 'str' object is not callable. Property dibaca tanpa kurung. - Setter menyimpan ke namanya sendiri.
self.harga = nilaidi dalam setterhargamemanggil setter itu lagi, terus-menerus, sampaiRecursionError: maximum recursion depth exceeded. Simpan diself._harga. - Nama setter beda dengan property-nya.
@harga.setterdi atasdef ubah_harga(...)gak memasang setter keharga, jadiself.harga = hargadi__init__ditolak:AttributeError: property 'harga' of 'Menu' object has no setter. - Lupa
@staticmethod.def diskon(total):tanpa decorator, lalukopi.diskon(89000): Python ikut mengirimkopi, jadiTypeError: Menu.diskon() takes 1 positional argument but 2 were given.
TL;DR: Decorator @... di atas def mengubah cara method dipakai. @property membuat method dibaca seperti atribut, tanpa kurung (kopi.label), dihitung ulang tiap kali dibaca; memanggilnya dengan kurung jadi TypeError: 'str' object is not callable. @harga.setter jalan saat kopi.harga = ... ditulis, bisa menolak nilai dengan raise ValueError(...), dan menyimpan angkanya di self._harga; self.harga = harga di __init__ juga lewat setter. @staticmethod gak menerima self maupun cls, cocok untuk fungsi bantu seperti diskon(total). @classmethod menerima cls (class-nya) dan sering dipakai sebagai alternative constructor seperti Menu.dari_teks(...).