Saya mencoba untuk memahami apa itu deskriptor Python dan apa manfaatnya. Saya mengerti bagaimana mereka bekerja, tetapi inilah keraguan saya. Pertimbangkan kode berikut:
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?
Apa
instance
danowner
di sini? (dalam__get__
). Apa tujuan dari parameter ini?Bagaimana saya menelepon / menggunakan contoh ini?
sumber
self
daninstance
?self
adalah turunan descriptor,instance
adalah turunan dari kelas (jika instantiated) descriptor ada di (instance.__class__ is owner
).Temperature.celsius
memberi nilai0.0
sesuai dengan kodecelsius = Celsius()
. Deskriptor Celsius dipanggil, jadi instansinya memiliki nilai init yang0.0
ditetapkan ke atribut kelas Temperature, celsius.Ini memberi Anda kontrol ekstra atas cara kerja atribut. Jika Anda terbiasa dengan getter dan setter di Jawa, misalnya, maka itulah cara Python untuk melakukan itu. Satu keuntungan adalah tampilannya bagi pengguna seperti atribut (tidak ada perubahan dalam sintaksis). Jadi Anda bisa mulai dengan atribut biasa dan kemudian, ketika Anda perlu melakukan sesuatu yang mewah, beralihlah ke deskriptor.
Atribut hanyalah nilai yang bisa berubah. Deskriptor memungkinkan Anda mengeksekusi kode arbitrer saat membaca atau mengatur (atau menghapus) suatu nilai. Jadi Anda bisa membayangkan menggunakannya untuk memetakan atribut ke bidang dalam database, misalnya - semacam ORM.
Penggunaan lain mungkin menolak untuk menerima nilai baru dengan melemparkan pengecualian secara
__set__
efektif membuat "atribut" hanya baca.Ini cukup halus (dan alasan saya menulis jawaban baru di sini - saya menemukan pertanyaan ini sambil bertanya-tanya hal yang sama dan tidak menemukan jawaban yang ada yang hebat).
Deskriptor didefinisikan pada kelas, tetapi biasanya dipanggil dari instance. Ketika itu dipanggil dari instance keduanya
instance
danowner
ditetapkan (dan Anda dapat bekerjaowner
dariinstance
sehingga sepertinya tidak ada gunanya). Tetapi ketika dipanggil dari kelas, hanyaowner
diatur - itulah sebabnya itu ada di sana.Ini hanya diperlukan
__get__
karena itu satu-satunya yang dapat dipanggil di kelas. Jika Anda menetapkan nilai kelas Anda mengatur deskriptor itu sendiri. Demikian pula untuk penghapusan. Itu sebabnyaowner
tidak diperlukan di sana.Nah, inilah trik keren menggunakan kelas serupa:
(Saya menggunakan Python 3; untuk python 2 Anda perlu memastikan divisi-divisi itu
/ 5.0
dan/ 9.0
). Itu memberi:Sekarang ada cara lain yang bisa dibilang lebih baik untuk mencapai efek yang sama dalam python (misalnya jika celsius adalah properti, yang merupakan mekanisme dasar yang sama tetapi menempatkan semua sumber di dalam kelas Temperatur), tetapi itu menunjukkan apa yang bisa dilakukan ...
sumber
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
, danstaticmethod
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
danself
, 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 pemilikobj_instance
,, dengandescriptor
objek:obj_instance.descriptor
memintadescriptor.__get__(self, obj_instance, owner_class)
pengembalian suatuvalue
Ini adalah bagaimana semua metode dan
get
pada properti bekerja.obj_instance.descriptor = value
memintadescriptor.__set__(self, obj_instance, value)
pengembalianNone
Ini adalah cara
setter
kerja properti.del obj_instance.descriptor
memintadescriptor.__delete__(self, obj_instance)
pengembalianNone
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 kelasobj_instance
)Untuk mendefinisikan ini dengan kode, sebuah objek adalah deskriptor jika himpunan atributnya bersinggungan dengan salah satu atribut yang diperlukan:
Sebuah data Descriptor memiliki
__set__
dan / atau__delete__
.Sebuah Non-Data-Descriptor memiliki tidak
__set__
atau__delete__
.Contoh Objek Deskriptor Builtin:
classmethod
staticmethod
property
Penjelas Non-Data
Kita bisa melihat itu
classmethod
danstaticmethod
Non-Data-Deskriptor:Keduanya hanya memiliki
__get__
metode:Perhatikan bahwa semua fungsi juga Non-Data-Deskriptor:
Deskriptor data,
property
Namun,
property
adalah deskriptor data:Urutan Pencarian Bertitik
Ini adalah perbedaan penting , karena mereka memengaruhi urutan pencarian untuk pencarian putus-putus.
obj_instance
's__dict__
, laluKonsekuensi 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:
Deskriptor Anda memastikan Anda selalu memiliki float untuk atribut kelas ini
Temperature
, dan yang tidak dapat Anda gunakandel
untuk menghapus atribut: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):
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: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.Ini sebuah demonstrasi:
Anda tidak dapat menghapus atribut:
Dan Anda tidak dapat menetapkan variabel yang tidak dapat dikonversi ke float:
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):Yang memiliki perilaku yang diharapkan sama persis dengan potongan kode asli:
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.
sumber
Sebelum masuk ke detail deskriptor, penting untuk mengetahui bagaimana pencarian atribut dalam Python bekerja. Ini mengasumsikan bahwa kelas tidak memiliki metaclass dan bahwa itu menggunakan implementasi default
__getattribute__
(keduanya dapat digunakan untuk "menyesuaikan" perilaku).Ilustrasi terbaik pencarian atribut (dalam Python 3.x atau untuk kelas gaya baru di Python 2.x) dalam hal ini adalah dari Memahami metaclasses Python (ionel's codelog) . Gambar digunakan
:
sebagai pengganti "pencarian atribut yang tidak dapat disesuaikan".Ini merupakan pencarian dari atribut
foobar
padainstance
dariClass
:Dua kondisi penting di sini:
instance
memiliki entri untuk nama atribut dan memiliki__get__
dan__set__
.instance
memiliki tidak ada entri untuk nama atribut tetapi kelas memiliki satu dan memiliki__get__
.Di situlah deskriptor masuk ke dalamnya:
__get__
dan__set__
.__get__
.Dalam kedua kasus nilai yang dikembalikan
__get__
disebut dengan instance sebagai argumen pertama dan kelas sebagai argumen kedua.Pencarian bahkan lebih rumit untuk pencarian atribut kelas (lihat misalnya Pencarian atribut kelas (di blog yang disebutkan di atas) ).
Mari kita beralih ke pertanyaan spesifik Anda:
Dalam kebanyakan kasus, Anda tidak perlu menulis kelas deskriptor! Namun Anda mungkin adalah pengguna akhir yang sangat teratur. Misalnya fungsi. Fungsi adalah deskriptor, dengan begitu fungsi dapat digunakan sebagai metode yang
self
secara implisit dilewatkan sebagai argumen pertama.Jika Anda melihat
test_method
contoh Anda akan mendapatkan kembali "metode terikat":Demikian pula Anda juga dapat mengikat fungsi dengan menjalankan
__get__
metode secara manual (tidak benar-benar direkomendasikan, hanya untuk tujuan ilustrasi):Anda bahkan dapat menyebutnya "metode terikat sendiri":
Perhatikan bahwa saya tidak memberikan argumen apa pun dan fungsinya mengembalikan contoh yang telah saya ikat!
Fungsinya adalah deskriptor non-data !
Beberapa contoh built-in dari deskriptor data adalah
property
. Mengabaikangetter
,setter
dandeleter
yangproperty
descriptor adalah (dari Descriptor HowTo Panduan "Properties" ):Karena itu data deskriptor itu dipanggil setiap kali Anda mencari "nama" dari
property
dan itu hanya delegasi untuk fungsi dihiasi dengan@property
,@name.setter
, dan@name.deleter
(jika ada).Ada beberapa penjelas lainnya di perpustakaan standar, misalnya
staticmethod
,classmethod
.Titik deskriptor mudah (walaupun Anda jarang membutuhkannya): Kode umum abstrak untuk akses atribut.
property
adalah abstraksi untuk akses variabel instan,function
menyediakan abstraksi untuk metode,staticmethod
menyediakan abstraksi untuk metode yang tidak memerlukan akses instance danclassmethod
menyediakan abstraksi untuk metode yang memerlukan akses kelas daripada akses instance (ini sedikit disederhanakan).Contoh lain adalah properti kelas .
Satu contoh yang menyenangkan (menggunakan
__set_name__
dari Python 3.6) juga bisa menjadi properti yang hanya memungkinkan jenis tertentu:Kemudian Anda bisa menggunakan deskriptor di kelas:
Dan bermain sedikit dengannya:
Atau "properti malas":
Ini adalah kasus di mana memindahkan logika ke deskriptor umum mungkin masuk akal, namun orang juga bisa menyelesaikannya (tapi mungkin dengan mengulang beberapa kode) dengan cara lain.
Itu tergantung pada bagaimana Anda mencari atribut. Jika Anda mencari atribut pada instance maka:
Jika Anda mencari atribut di kelas (dengan asumsi deskriptor didefinisikan pada kelas):
None
Jadi pada dasarnya argumen ketiga diperlukan jika Anda ingin menyesuaikan perilaku ketika Anda melakukan pencarian tingkat kelas (karena
instance
adaNone
).Contoh Anda pada dasarnya adalah properti yang hanya memungkinkan nilai yang dapat dikonversi
float
dan yang dibagikan di antara semua instance kelas (dan di kelas - meskipun orang hanya dapat menggunakan akses "baca" di kelas jika tidak, Anda akan mengganti instance deskriptor) ):Itu sebabnya deskriptor umumnya menggunakan argumen kedua (
instance
) untuk menyimpan nilai untuk menghindari berbagi. Namun dalam beberapa kasus berbagi nilai antar instance mungkin diinginkan (walaupun saya tidak bisa memikirkan skenario saat ini). Namun praktis tidak masuk akal untuk properti celsius pada kelas suhu ... kecuali mungkin sebagai murni kegiatan akademis.sumber
Terinspirasi oleh Fluent Python oleh Buciano Ramalho
Gambar Anda memiliki kelas seperti ini
Kita harus memvalidasi bobot dan harga untuk menghindari memberi mereka angka negatif, kita dapat menulis lebih sedikit kode jika kita menggunakan deskriptor sebagai proxy karena ini
kemudian definisikan kelas LineItem seperti ini:
dan kita dapat memperluas kelas Kuantitas untuk melakukan validasi yang lebih umum
sumber
weight = Quantity()
, tetapi nilai-nilai harus ditetapkan dalam contoh namespace hanya menggunakanself
(misalnyaself.weight = 4
), kalau tidak atribut akan kembali ke nilai baru dan deskriptor akan dibuang. Bagus!weight = Quantity()
sebagai variabel kelas dan itu__get__
dan__set__
bekerja pada variabel instan. Bagaimana?Saya mencoba (dengan perubahan kecil seperti yang disarankan) kode dari jawaban Andrew Cooke. (Saya menjalankan python 2.7).
Kode:
Hasil:
Dengan Python sebelum 3, pastikan Anda subkelas dari objek yang akan membuat deskriptor berfungsi dengan benar karena get magic tidak berfungsi untuk kelas gaya lama.
sumber
Anda akan melihat https://docs.python.org/3/howto/descriptor.html#properties
sumber