Saya mencoba memahami apa itu deskriptor Python dan apa manfaatnya.
Deskriptor adalah atribut kelas (seperti properti atau metode) dengan salah satu metode khusus berikut:
__get__
(metode deskriptor non-data, misalnya pada metode / fungsi)
__set__
(metode deskriptor data, misalnya pada instance properti)
__delete__
(metode deskriptor data)
Objek deskriptor ini dapat digunakan sebagai atribut pada definisi kelas objek lainnya. (Yaitu, mereka hidup di __dict__
objek kelas.)
Objek deskriptor dapat digunakan untuk secara terprogram mengelola hasil pencarian putus-putus (mis. foo.descriptor
) Dalam ekspresi normal, tugas, dan bahkan penghapusan.
Fungsi / metode, metode terikat, property
, classmethod
, dan staticmethod
semua menggunakan metode-metode khusus untuk kontrol bagaimana mereka diakses melalui pencarian putus-putus.
Sebuah deskripsi Data , seperti property
, dapat memungkinkan untuk evaluasi malas atribut berdasarkan keadaan sederhana objek, sehingga contoh untuk menggunakan lebih sedikit memori daripada jika Anda Precomputed setiap atribut mungkin.
Deskriptor data lain, a member_descriptor
, dibuat oleh __slots__
, memungkinkan penghematan memori dengan memungkinkan kelas untuk menyimpan data dalam struktur data tuple-suka bisa berubah bukan lebih fleksibel tetapi memakan ruang __dict__
.
Deskriptor non-data, biasanya instance, class, dan metode statis, mendapatkan argumen pertama implisit mereka (biasanya dinamai cls
dan self
, masing-masing) dari metode deskriptor non-data __get__
,.
Sebagian besar pengguna Python hanya perlu mempelajari penggunaannya yang sederhana, dan tidak perlu mempelajari atau memahami implementasi deskriptor lebih lanjut.
Dalam Kedalaman: Apa Itu Penjelas?
Deskriptor adalah objek dengan salah satu metode berikut ( __get__
,, __set__
atau __delete__
), yang dimaksudkan untuk digunakan melalui pencarian bertitik seolah-olah itu adalah atribut khas dari sebuah instance. Untuk objek pemilik obj_instance
,, dengan descriptor
objek:
obj_instance.descriptor
meminta
descriptor.__get__(self, obj_instance, owner_class)
pengembalian suatu value
Ini adalah bagaimana semua metode dan get
pada properti bekerja.
obj_instance.descriptor = value
meminta
descriptor.__set__(self, obj_instance, value)
pengembalian None
Ini adalah cara setter
kerja properti.
del obj_instance.descriptor
meminta
descriptor.__delete__(self, obj_instance)
pengembalian None
Ini adalah cara deleter
kerja properti.
obj_instance
adalah instance yang kelasnya berisi instance objek deskriptor. self
adalah contoh dari deskriptor (mungkin hanya satu untuk kelas obj_instance
)
Untuk mendefinisikan ini dengan kode, sebuah objek adalah deskriptor jika himpunan atributnya bersinggungan dengan salah satu atribut yang diperlukan:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
Sebuah data Descriptor memiliki __set__
dan / atau __delete__
.
Sebuah Non-Data-Descriptor memiliki tidak __set__
atau __delete__
.
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
Contoh Objek Deskriptor Builtin:
classmethod
staticmethod
property
- fungsi secara umum
Penjelas Non-Data
Kita bisa melihat itu classmethod
dan staticmethod
Non-Data-Deskriptor:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
Keduanya hanya memiliki __get__
metode:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
Perhatikan bahwa semua fungsi juga Non-Data-Deskriptor:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Deskriptor data, property
Namun, property
adalah deskriptor data:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
Urutan Pencarian Bertitik
Ini adalah perbedaan penting , karena mereka memengaruhi urutan pencarian untuk pencarian putus-putus.
obj_instance.attribute
- Pertama di atas terlihat untuk melihat apakah atributnya adalah Data-Descriptor pada kelas instance,
- Jika tidak, akan terlihat apakah atributnya ada di
obj_instance
's __dict__
, lalu
- akhirnya jatuh kembali ke Non-Data-Descriptor.
Konsekuensi dari urutan pencarian ini adalah Non-Data-Descriptors seperti fungsi / metode dapat ditimpa oleh instance .
Rekap dan Langkah Selanjutnya
Kami telah belajar bahwa deskriptor adalah obyek dengan salah __get__
, __set__
atau __delete__
. Objek deskriptor ini dapat digunakan sebagai atribut pada definisi kelas objek lainnya. Sekarang kita akan melihat bagaimana mereka digunakan, menggunakan kode Anda sebagai contoh.
Analisis Kode dari Pertanyaan
Inilah kode Anda, diikuti dengan pertanyaan dan jawaban Anda untuk masing-masing:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- Mengapa saya membutuhkan kelas deskriptor?
Deskriptor Anda memastikan Anda selalu memiliki float untuk atribut kelas ini Temperature
, dan yang tidak dapat Anda gunakan del
untuk menghapus atribut:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Jika tidak, deskriptor Anda mengabaikan kelas pemilik dan instance pemilik, sebagai gantinya, menyimpan status dalam deskriptor. Anda bisa dengan mudah membagikan status di semua instance dengan atribut kelas sederhana (selama Anda selalu menetapkannya sebagai float ke kelas dan tidak pernah menghapusnya, atau merasa nyaman dengan pengguna kode Anda yang melakukannya):
class Temperature(object):
celsius = 0.0
Ini memberi Anda perilaku yang persis sama dengan contoh Anda (lihat respons untuk pertanyaan 3 di bawah), tetapi menggunakan Python builtin ( property
), dan akan dianggap lebih idiomatis:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- Apa instance dan pemilik di sini? (di dapatkan ). Apa tujuan dari parameter ini?
instance
adalah contoh dari pemilik yang memanggil deskriptor. Pemilik adalah kelas di mana objek deskriptor digunakan untuk mengelola akses ke titik data. Lihat deskripsi metode khusus yang mendefinisikan deskriptor di sebelah paragraf pertama dari jawaban ini untuk nama variabel yang lebih deskriptif.
- Bagaimana saya menelepon / menggunakan contoh ini?
Ini sebuah demonstrasi:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
Anda tidak dapat menghapus atribut:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Dan Anda tidak dapat menetapkan variabel yang tidak dapat dikonversi ke float:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
Kalau tidak, yang Anda miliki di sini adalah keadaan global untuk semua instance, yang dikelola dengan menetapkan instance apa pun.
Cara yang diharapkan bahwa programmer Python paling berpengalaman akan mencapai hasil ini adalah dengan menggunakan property
dekorator, yang menggunakan deskriptor yang sama di bawah tenda, tetapi membawa perilaku ke dalam implementasi kelas pemilik (sekali lagi, sebagaimana didefinisikan di atas):
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
Yang memiliki perilaku yang diharapkan sama persis dengan potongan kode asli:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
Kesimpulan
Kami telah membahas atribut yang mendefinisikan deskriptor, perbedaan antara deskriptor data dan non-data, objek bawaan yang menggunakannya, dan pertanyaan spesifik tentang penggunaan.
Jadi sekali lagi, bagaimana Anda akan menggunakan contoh pertanyaan? Saya harap kamu tidak akan melakukannya. Saya harap Anda akan mulai dengan saran pertama saya (atribut kelas sederhana) dan beralih ke saran kedua (dekorator properti) jika Anda merasa perlu.
self
daninstance
?