Menguasai Decorator @property Python: Getter dan Setter Bersih & Kompatibel Mundur
Menguasai Decorator @property Python
Temukan bagaimana decorator bawaan @property Python memungkinkan Anda menulis getter dan setter yang ringkas dan mudah dipelihara sambil menjaga API Anda tetap kompatibel mundur.
Decorator @property Python adalah alat yang kuat untuk enkapsulasi akses atribut. Ia memungkinkan Anda menentukan logika kustom yang berjalan setiap kali atribut dibaca atau ditulis, tanpa memaksa caller untuk mengubah kode mereka. Dalam bagian berikut kita akan melalui contoh dunia nyata—penanganan konversi suhu—tunjukkan cara memberlakukan batasan, dan menjelaskan mengapa @property adalah solusi idiomatik.
Sebuah Kelas Tanpa Getter dan Setter
Misalkan kita membutuhkan kelas sederhana yang menyimpan suhu dalam Celsius dan dapat mengonversi ke Fahrenheit:
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
Menggunakannya sederhana:
# Create a new object
human = Celsius()
# Set the temperature
human.temperature = 37
# Read the temperature
print(human.temperature)
# Convert to Fahrenheit
print(human.to_fahrenheit())
Output
37 98.60000000000001
Hasil floating-point diharapkan karena representasi biner desimal. Saat Anda memeriksa human.__dict__, Anda akan melihat bahwa Python menyimpan nilai dalam entri dictionary biasa:
>>> human.__dict__
{'temperature': 37}
Menambahkan Batasan dengan Getter dan Setter
Objek dunia nyata sering memerlukan validasi. Misalnya, suhu tidak dapat di bawah nol mutlak (-273.15 °C). Salah satu cara yang sederhana adalah menyembunyikan atribut dan mengekspos metode getter/setter eksplisit:
class Celsius:
def __init__(self, temperature=0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# getter
def get_temperature(self):
return self._temperature
# setter
def set_temperature(self, value):
if value < -273.15:
raise ValueError("Temperature below -273.15 is not possible.")
self._temperature = value
Sekarang kelas memberlakukan batas bawah, tetapi kode apa pun yang mengakses obj.temperature harus diperbarui untuk memanggil obj.get_temperature() dan obj.set_temperature(val). Itu adalah refactor besar untuk basis kode besar.
Selain itu, variabel "private" Python hanyalah konvensi penamaan—_temperature masih dapat diakses langsung, melanggar enkapsulasi. Inilah tempat @property muncul.
Menggunakan Kelas property
Python menawarkan kelas property yang mengaitkan fungsi getter dan setter ke nama atribut:
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# getter
def get_temperature(self):
print("Getting value...")
return self._temperature
# setter
def set_temperature(self, value):
print("Setting value...")
if value < -273.15:
raise ValueError("Temperature below -273.15 is not possible")
self._temperature = value
# expose as a property
temperature = property(get_temperature, set_temperature)
Dengan pengaturan ini, kode berikut berperilaku sama seperti kelas sederhana asli, tetapi dengan validasi di balik layar:
human = Celsius(37)
print(human.temperature) # triggers getter
print(human.to_fahrenheit()) # uses getter internally
human.temperature = -300 # triggers setter and raises ValueError
Output
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): ... ValueError: Temperature below -273 is not possible
Perhatikan bagaimana kode yang mengakses temperature tetap tidak berubah; hanya implementasi dasar yang mendapatkan validasi.
Tip: Data aktual tinggal di _temperature; temperature hanyalah antarmuka yang nyaman dan aman.
The @property Decorator
Sementara kelas property bekerja, sintaks decorator Python menawarkan cara yang lebih bersih dan lebih mudah dibaca untuk mencapai hasil yang sama. Decorator secara otomatis membuat objek property dan mengaitkan getter dan setter ke nama atribut.
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
@property
def temperature(self):
print("Getting value...")
return self._temperature
@temperature.setter
def temperature(self, value):
print("Setting value...")
if value < -273.15:
raise ValueError("Temperature below -273 is not possible")
self._temperature = value
Usage remains the same:
human = Celsius(37)
print(human.temperature)
print(human.to_fahrenheit())
# Attempting to set an invalid temperature
try:
Celsius(-300)
except ValueError as e:
print(e)
Output
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): ... ValueError: Temperature below -273 is not possible
Menggunakan @property menjaga namespace tetap rapi, menghilangkan kebutuhan akan nama metode getter/setter terpisah, dan adalah pola yang direkomendasikan untuk sebagian besar pengembang Python.