{count, plural, one {Kursus} other {Kursus}}
Deskriptor Python atau secara umum, deskriptor, memberi Anda teknik yang kuat untuk menulis kode yang dapat digunakan kembali dan dibagikan antar kelas. Sekilas mirip dengan konsep pewarisan, tetapi secara teknis berbeda. Ini adalah cara serbaguna untuk mencegat akses atribut. Deskriptor adalah mekanisme di balik properti, metode statis, metode kelas, metode super, dan sebagainya.
Deskriptor ditambahkan pada Python versi 2.2, dan sejak itu dianggap sebagai hal “magis” yang memberi gaya baru pada kelas tradisional. Ini adalah kelas yang memungkinkan Anda mengelola properti di kelas lain. Secara khusus, mereka mengimplementasikan antarmuka untuk metode __get__(), __set__() dan __delete__() yang membuatnya menarik karena banyak alasan. Misalnya, dekorator kelas dan dekorator properti yang mungkin sudah Anda lihat di Python.
Sederhananya, kelas yang mengimplementasikan metode __get__(), __set()__, atau __delete()__ untuk suatu objek dikenal sebagai "Descriptor". Mengutip langsung dari dokumentasi resmi Python, deskriptor adalah atribut objek dengan perilaku binding, yaitu akses atributnya digantikan oleh metode dalam protokol deskriptor. Metode-metode tersebut adalah __get__(), __set__(), dan __delete__() (Sumber).
Perilaku binding dalam konteks deskriptor berarti pengikatan cara nilai dapat diatur (set), diambil (get), atau dihapus untuk variabel, objek, atau sekumpulan data tertentu. Seluruh interaksi ini terikat ke potongan data tersebut karena hanya berlaku pada data yang Anda tetapkan, sehingga mengikatnya pada bagian data tertentu itu saja.
Deskriptor dapat dikategorikan lebih lanjut menjadi deskriptor data dan non-data. Jika deskriptor yang Anda tulis hanya memiliki metode __get__(), maka itu adalah deskriptor non-data; sebaliknya, implementasi yang melibatkan metode __set__() dan __delete__() disebut deskriptor data. Deskriptor non-data hanya dapat dibaca, sedangkan deskriptor data dapat dibaca dan ditulis.
Penting untuk dicatat bahwa deskriptor ditetapkan pada kelas, bukan pada instans kelas. Memodifikasi kelas akan menimpa atau menghapus deskriptor itu sendiri, alih-alih memicu kodenya (IBM Developer).
Pada akhirnya, kelas deskriptor tidak terbatas hanya memiliki tiga metode ini, artinya ia juga dapat memuat atribut lain selain metode get, set, dan delete.
Mari pahami metode get, set, dan delete lebih rinci yang terinspirasi dari halaman IBM Developer ini:
selfadalah instans deskriptor yang Anda buat (Real Python).objectadalah instans objek tempat deskriptor Anda terpasang (Real Python).typeadalah tipe objek tempat deskriptor terpasang (Real Python).valueadalah nilai yang ditetapkan ke atribut deskriptor. get(self, object, type) set(self, object, value) delete(self, object)-
__get__()mengakses atribut atau saat Anda ingin mengekstrak informasi. Mengembalikan nilai atribut atau memunculkan pengecualian AttributeError jika atribut yang diminta tidak ada. -
__set__()dipanggil pada operasi penetapan atribut yang menetapkan nilai suatu atribut. Tidak mengembalikan apa pun, tetapi dapat memunculkan pengecualian AttributeError. -
__delete__()mengontrol operasi penghapusan, yaitu saat Anda ingin menghapus atribut dari suatu objek. Tidak mengembalikan apa pun.
Sekarang mari pahami tujuan Deskriptor dengan bantuan beberapa contoh!
Tujuan Deskriptor
Misalkan kita mendefinisikan kelas car yang memiliki tiga atribut, yaitu make, model, dan fuel_cap. Anda akan menggunakan metode __init__() untuk menginisialisasi atribut kelas. Lalu, Anda akan menggunakan fungsi magis __str__(), yang hanya akan mengembalikan keluaran dari tiga atribut yang Anda berikan ke kelas saat membuat objek.
Perhatikan bahwa metode __str__() mengembalikan representasi string dari objek. Ini dipanggil ketika fungsi print() atau str() dipanggil pada objek kelas.
class Car:
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)
car1 = Car("BMW","X7",40)
print(car1)
BMW model X7 with a fuel capacity of 40 ltr.
Seperti yang terlihat dari keluaran di atas, semuanya terlihat baik!
Sekarang mari ubah kapasitas bahan bakar mobil menjadi negatif 40.
car2 = Car("BMW","X7",-40)
print(car2)
BMW model X7 with a fuel capacity of -40 ltr.
Tunggu dulu, ada yang tidak beres, bukan? Kapasitas bahan bakar mobil tidak mungkin negatif. Namun, Python menerimanya sebagai masukan tanpa error. Ini karena Python adalah bahasa pemrograman dinamis yang tidak mendukung pemeriksaan tipe secara eksplisit.
Untuk menghindari masalah ini, mari tambahkan kondisi if di metode __init__() dan periksa apakah kapasitas bahan bakar yang dimasukkan valid atau tidak. Jika tidak valid, naikkan pengecualian ValueError.
class Car:
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if self.fuel_cap < 0:
raise ValueError("Fuel Capacity can never be less than zero")
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)
car1 = Car("BMW","X7",40)
print(car1)
BMW model X7 with a fuel capacity of 40 ltr.
car2 = Car("BMW","X7",-40)
print(car2)
----------------------------------------
ValueErrorTraceback (most recent call last)
<ipython-input-22-1c3d23be72f7> in <module>
----> 1 car2 = Car("BMW","X7",-40)
2 print(car2)
<ipython-input-20-1e154783588d> in __init__(self, make, model, fuel_cap)
5 self.fuel_cap = fuel_cap
6 if self.fuel_cap < 0:
----> 7 raise ValueError("Fuel Capacity can never be less than zero")
8
9 def __str__(self):
ValueError: Fuel Capacity can never be less than zero
Dari keluaran di atas, Anda dapat melihat bahwa untuk saat ini semuanya berjalan baik karena program menaikkan ValueError jika kapasitas bahan bakar di bawah nol.
Namun, bisa ada masalah lain, yaitu bagaimana jika fuel capacity yang dimasukkan berupa nilai float atau string. Bukan hanya kapasitas bahan bakar, tetapi juga make dan model mobil bisa berupa nilai integer. Dalam semua kasus ini, program tidak akan menaikkan pengecualian.
class Car:
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if self.fuel_cap < 0:
raise ValueError("Fuel Capacity can never be less than zero")
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)
car2 = Car(-40,"X7",40)
print(car2)
-40 model X7 with a fuel capacity of 40 ltr.
Untuk menangani kasus di atas, Anda mungkin terpikir menambahkan kondisi if lain atau menggunakan metode isinstance untuk pemeriksaan tipe.
Kali ini mari gunakan metode bawaan isinstance untuk menangani error.
class Car:
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if isinstance(self.make, str):
print(self.make)
else:
raise ValueError("Make of the car can never be an integer")
if self.fuel_cap < 0:
raise ValueError("Fuel Capacity can never be less than zero")
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)
car2 = Car("BMW","X7",40)
print(car2)
BMW
BMW model X7 with a fuel capacity of 40 ltr.
car2 = Car(-40,"X7",40)
print(car2)
----------------------------------------
ValueErrorTraceback (most recent call last)
<ipython-input-34-75b08cba454f> in <module>
----> 1 car2 = Car(-40,"X7",40)
2 print(car2)
<ipython-input-31-175690bf3b98> in __init__(self, make, model, fuel_cap)
7 print(self.make)
8 else:
----> 9 raise ValueError("Make of the car can never be an integer")
10
11 if self.fuel_cap < 0:
ValueError: Make of the car can never be an integer
Bagus! Jadi Anda juga berhasil menangani error ini.
Namun, bagaimana jika Anda ingin mengubah atribut kapasitas bahan bakar menjadi negatif 40 secara eksplisit di kemudian hari. Dalam kasus ini, tidak akan berhasil, karena pemeriksaan tipe hanya dilakukan sekali di metode __init__(). Seperti Anda tahu, metode __init__() adalah konstruktor dan dipanggil hanya sekali saat Anda membuat objek dari kelas. Maka, pemeriksaan tipe kustom akan gagal di kemudian hari.
Mari pahami dengan contoh.
class Car:
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if isinstance(self.make, str):
print(self.make)
else:
raise ValueError("Make of the car can never be an integer")
if self.fuel_cap < 0:
raise ValueError("Fuel Capacity can never be less than zero")
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)
car2 = Car("BMW","X7",40)
print(car2)
BMW
BMW model X7 with a fuel capacity of 40 ltr.
car2.make = -40
print(car2)
-40 model X7 with a fuel capacity of 40 ltr.
Nah, itu dia! Anda berhasil keluar dari pemeriksaan tipe.
Sekarang pikirkan begini, bagaimana jika Anda memiliki banyak atribut lain dari mobil seperti jarak tempuh, harga, aksesori, dan sebagainya yang juga membutuhkan pemeriksaan tipe, dan Anda juga menginginkan fungsionalitas di mana beberapa atribut hanya memiliki akses baca. Bukankah itu akan sangat merepotkan?
Untuk mengatasi semua masalah di atas, Python memiliki Deskriptor yang menjadi penyelamat!
Seperti yang Anda pelajari di atas, kelas apa pun yang mengimplementasikan metode magis __get__(), __set()__, atau __delete()__ untuk objek protokol deskriptor disebut Deskriptor. Mereka juga memberi Anda kontrol tambahan atas cara sebuah atribut bekerja, misalnya apakah atribut tersebut seharusnya bisa dibaca atau ditulis.
Sekarang mari perluas contoh di atas dengan menambahkan metode Python Descriptor.
class Descriptor:
def __init__(self):
self.__fuel_cap = 0
def __get__(self, instance, owner):
return self.__fuel_cap
def __set__(self, instance, value):
if isinstance(value, int):
print(value)
else:
raise TypeError("Fuel Capacity can only be an integer")
if value < 0:
raise ValueError("Fuel Capacity can never be less than zero")
self.__fuel_cap = value
def __delete__(self, instance):
del self.__fuel_cap
class Car:
fuel_cap = Descriptor()
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)
car2 = Car("BMW","X7",40)
print(car2)
40
BMW model X7 with a fuel capacity of 40 ltr.
Jangan khawatir jika kelas Descriptor terlihat membingungkan. Mari kita uraikan menjadi bagian-bagian kecil dan pahami apa yang dilakukan masing-masing metode.
- Metode
__init__()dari kelas Descriptor memiliki variabel lokal__fuel_capbernilai nol.dunderatau dua garis bawah di awal artinya variabel tersebut privat. Dunder di awal hanya untuk membedakan atribut kapasitas bahan bakar milik kelas Descriptor dengan kelas Car.
- Seperti yang kini Anda tahu, metode
__get__()digunakan untuk mengambil atribut, dan ia mengembalikan variabel kapasitas bahan bakar. Ia menerima tiga argumen: objek deskriptor, instans kelas yang memuat instans objek deskriptor, yaitucar2, dan terakhir pemilik (owner), yaitu kelas tempat instans tersebut berada, yaitu kelasCar. Dalam metode ini, Anda cukup mengembalikan atribut nilai, yaitufuel_capyang nilainya diatur dalam metode__set__().
- Metode
__set__()dipanggil ketika nilai diatur pada atribut, dan tidak seperti metode__get__(), ia tidak mengembalikan apa pun. Ia memiliki dua argumen selain objek deskriptor itu sendiri, yaitu instans yang sama seperti pada metode__get__()dan argumen value, yaitu nilai yang Anda tetapkan pada atribut. Dalam metode ini, Anda memeriksa apakah nilai yang ingin ditetapkan ke atributfuel_capadalah integer atau bukan. Jika bukan, Anda menaikkan pengecualianTypeError. Lalu, dalam metode yang sama, Anda juga memeriksa apakah nilainya kurang dari nol; jika ya, Anda menaikkan pengecualian lain, kali iniValueError. Setelah memeriksa error, Anda memperbarui atributfuel_capsama dengan nilai tersebut.
- Terakhir, metode
__delete__(), yang dipanggil saat atribut dihapus dari suatu objek, dan seperti metode__set__(), ia tidak mengembalikan apa pun.
Kelas Car tetap sama seperti sebelumnya. Namun, satu-satunya perubahan yang Anda lakukan adalah menambahkan instans fuel_cap dari kelas Descriptor(). Perhatikan bahwa seperti disebutkan sebelumnya, instans kelas deskriptor harus ditambahkan ke kelas sebagai atribut kelas, bukan sebagai atribut instans.
Begitu Anda menetapkan variabel lokal fuel_cap di metode __init__() ke instans fuel_cap, itu akan memanggil metode __set__() dari kelas Descriptor.
Sekarang mari ubah kapasitas bahan bakar menjadi nilai negatif dan lihat apakah program menaikkan pengecualian ValueError atau tidak.
car2 = Car("BMW","X7",-40)
print(car2)
-40
----------------------------------------
ValueErrorTraceback (most recent call last)
<ipython-input-115-1c3d23be72f7> in <module>
----> 1 car2 = Car("BMW","X7",-40)
2 print(car2)
<ipython-input-107-3e1f3e97d3c7> in __init__(self, make, model, fuel_cap)
4 self.make = make
5 self.model = model
----> 6 self.fuel_cap = fuel_cap
7
8 def __str__(self):
<ipython-input-106-0b252695aeed> in __set__(self, instance, value)
11
12 if value < 0:
---> 13 raise ValueError("Fuel Capacity can never be less than zero")
14
15 self.__fuel_cap = value
ValueError: Fuel Capacity can never be less than zero
Jika Anda ingat di sini, pemeriksaan tipe gagal ketika Anda kemudian mengubah nilai atribut menjadi angka negatif karena pemeriksaan tipe hanya berlaku sekali di metode __init__(). Mari perbarui nilai fuel_cap menjadi nilai string dan lihat apakah ini menghasilkan error.
car2.fuel_cap = -1
-1
----------------------------------------
ValueErrorTraceback (most recent call last)
<ipython-input-120-dea9dbe96ebe> in <module>
----> 1 car2.fuel_cap = -1
<ipython-input-106-0b252695aeed> in __set__(self, instance, value)
11
12 if value < 0:
---> 13 raise ValueError("Fuel Capacity can never be less than zero")
14
15 self.__fuel_cap = value
ValueError: Fuel Capacity can never be less than zero
car2.fuel_cap = "BMW"
----------------------------------------
TypeErrorTraceback (most recent call last)
<ipython-input-121-0b316a9872c6> in <module>
----> 1 car2.fuel_cap = "BMW"
<ipython-input-106-0b252695aeed> in __set__(self, instance, value)
8 print(value)
9 else:
---> 10 raise TypeError("Fuel Capacity can only be an integer")
11
12 if value < 0:
TypeError: Fuel Capacity can only be an integer
Sempurna! Seperti yang Anda lihat, ini berfungsi ketika Anda memperbarui atribut kapasitas bahan bakar di kemudian hari.
Ada sedikit masalah pada Deskriptor, yaitu saat Anda membuat instans baru atau instans kedua dari kelas, nilai instans sebelumnya tertimpa. Alasannya karena Deskriptor ditautkan ke kelas, bukan ke instans.
Mari pahami dengan contoh di bawah ini.
car3 = Car("BMW","X7",48) #created a new instance 'car3' with different values
48
Ketika Anda mencetak instans car2, Anda akan melihat bahwa nilainya telah ditimpa oleh car3.
print(car2)
BMW model X7 with a fuel capacity of 48 ltr.
Kesimpulan
Selamat telah menyelesaikan tutorial ini.
Tutorial ini ditujukan bagi mereka yang sudah nyaman dengan Python dan bercita-cita menguasai tingkat lanjut. Sebagai latihan yang baik, Anda mungkin ingin mencari cara untuk mengatasi masalah penimpaan instans yang dibahas dalam tutorial hari ini.
Jangan ragu untuk mengajukan pertanyaan terkait tutorial ini di kolom komentar di bawah.
Jika Anda ingin mempelajari lebih lanjut tentang Python, ikuti jalur keterampilan lengkap Python Programming dari DataCamp.
Lihat juga Tutorial Fungsi Python kami.
