Mari kita asumsikan kita ingin membuat keluarga kelas yang implementasi atau spesialisasi yang berbeda dari konsep menyeluruh. Mari kita asumsikan ada implementasi default yang masuk akal untuk beberapa properti turunan. Kami ingin memasukkan ini ke kelas dasar
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Jadi subclass akan secara otomatis dapat menghitung elemen-elemennya dalam contoh yang agak konyol ini
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Tetapi bagaimana jika subkelas tidak ingin menggunakan default ini? Ini tidak bekerja:
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
Saya menyadari ada cara untuk mengganti properti dengan properti, tetapi saya ingin menghindari itu. Karena tujuan dari kelas dasar adalah untuk membuat hidup semudah mungkin bagi penggunanya, bukan untuk menambahkan mengasapi dengan memaksakan (dari sudut pandang sempit subclass) metode akses yang berbelit-belit dan berlebihan.
Bisakah itu dilakukan? Jika tidak, apa solusi terbaik berikutnya?
sumber
__get__()
,__set__()
dan__delete__()
) dan mereka meningkatkanAttributeError
jika Anda tidak menyediakan fungsi apa pun untuk mereka. Lihat Python yang setara dengan implementasi properti .setter
atau__set__
properti, ini akan berhasil. Namun saya akan mengingatkan Anda bahwa ini juga berarti Anda dapat dengan mudah menimpa properti tidak hanya di level kelas (self.size = ...
) tetapi bahkan di tingkat instance (misalnyaConcrete_Math_Set(1, 2, 3).size = 10
akan sama-sama valid). Makanan untukSquare_Integers_Below(9).size = 1
juga valid karena merupakan atribut sederhana. Dalam kasus penggunaan "ukuran" khusus ini, ini mungkin tampak akward (gunakan properti sebagai gantinya), tetapi dalam kasus umum "menimpa prop yang dihitung dengan mudah dalam subkelas", mungkin dalam beberapa kasus baik. Anda juga dapat mengontrol akses atribut dengan__setattr__()
tetapi itu bisa sangat besar.Ini akan menjadi jawaban panjang lebar yang mungkin hanya berfungsi sebagai pelengkap ... tapi pertanyaan Anda membawa saya untuk turun ke lubang kelinci jadi saya ingin berbagi temuan saya (dan rasa sakit) juga.
Anda mungkin pada akhirnya menemukan jawaban ini tidak membantu masalah Anda yang sebenarnya. Bahkan, kesimpulan saya adalah - saya tidak akan melakukan ini sama sekali. Karena itu, latar belakang kesimpulan ini mungkin sedikit menghibur Anda, karena Anda mencari detail lebih lanjut.
Mengatasi beberapa kesalahpahaman
Jawaban pertama, meskipun benar dalam banyak kasus, tidak selalu demikian. Sebagai contoh, pertimbangkan kelas ini:
inst_prop
, sementara menjadiproperty
, adalah atribut instance yang tidak dapat dibatalkan:Itu semua tergantung di mana Anda
property
didefinisikan di tempat pertama. Jika Anda@property
didefinisikan dalam "lingkup" kelas (atau benar-benar,namespace
), itu menjadi atribut kelas. Dalam contoh saya, kelas itu sendiri tidak mengetahui adanyainst_prop
sampai instantiated. Tentu saja, ini sama sekali tidak berguna sebagai properti di sini.Tapi pertama-tama, mari alamat komentar Anda pada resolusi warisan ...
Jadi bagaimana tepatnya faktor pewarisan dalam masalah ini? Artikel berikut ini sedikit menyimpang ke dalam topik, dan Metode Resolusi Urutan agak terkait, meskipun sebagian besar membahas Luasnya warisan daripada Kedalaman.
Dikombinasikan dengan temuan kami, dengan pengaturan di bawah ini:
Bayangkan apa yang terjadi ketika baris ini dieksekusi:
Hasilnya adalah sebagai berikut:
Perhatikan caranya:
self.world_view
dapat diterapkan, sementaraself.culture
gagalculture
tidak ada diChild.__dict__
(mappingproxy
kelas, jangan dikelirukan dengan instance__dict__
)culture
ada dic.__dict__
, itu tidak dirujuk.Anda mungkin dapat menebak mengapa -
world_view
ditimpa olehParent
kelas sebagai non-properti, sehinggaChild
dapat menimpanya juga. Sementara itu, sejakculture
diwariskan, hanya ada dalammappingproxy
dariGrandparent
:Bahkan jika Anda mencoba untuk menghapus
Parent.culture
:Anda akan melihat itu bahkan tidak ada
Parent
. Karena objek secara langsung merujuk kembali keGrandparent.culture
.Jadi, bagaimana dengan Resolution Order?
Jadi kami tertarik untuk mengamati Resolution Order yang sebenarnya, mari kita coba hapus
Parent.world_view
:Bertanya-tanya apa hasilnya?
Itu dikembalikan ke kakek-nenek
world_view
property
, meskipun kami telah berhasil menugaskanself.world_view
sebelumnya! Tetapi bagaimana jika kita dengan paksa berubahworld_view
di tingkat kelas, seperti jawaban yang lain? Bagaimana jika kita menghapusnya? Bagaimana jika kita menetapkan atribut kelas saat ini menjadi properti?Hasilnya adalah:
Ini menarik karena
c.world_view
dikembalikan ke atribut instansinya, sedangkan atributChild.world_view
yang kami tetapkan. Setelah menghapus atribut instance, itu akan kembali ke atribut class. Dan setelah menugaskan kembaliChild.world_view
ke properti, kami langsung kehilangan akses ke atribut instance.Oleh karena itu, kami dapat menduga urutan resolusi berikut :
property
, ambil nilainya melaluigetter
ataufget
(lebih lanjut tentang ini nanti). Kelas saat ini pertama ke kelas Base terakhir.property
atribut non- kelas. Kelas saat ini pertama ke kelas Base terakhir.Dalam hal ini, mari kita hapus root
property
:Pemberian yang mana:
Ta-dah!
Child
sekarang memiliki sendiriculture
berdasarkan penyisipan paksa kec.__dict__
.Child.culture
tidak ada, tentu saja, karena tidak pernah didefinisikan dalamParent
atauChild
atribut kelas, danGrandparent
sudah dihapus.Apakah ini penyebab utama masalah saya?
Sebenarnya tidak . Kesalahan yang Anda dapatkan, yang masih kami amati saat menetapkan
self.culture
, sangat berbeda . Tetapi urutan warisan mengatur latar belakang untuk jawaban - yang merupakanproperty
dirinya sendiri.Selain
getter
metode yang disebutkan sebelumnya ,property
juga memiliki beberapa trik rapi di lengan bajunya. Yang paling relevan dalam kasus ini adalahsetter
, ataufset
metode, yang dipicu olehself.culture = ...
garis. Karena Andaproperty
tidak mengimplementasikansetter
ataufget
fungsi apa pun , python tidak tahu apa yang harus dilakukan, danAttributeError
sebaliknya melemparkan (yaitucan't set attribute
).Namun jika Anda menerapkan
setter
metode:Saat membuat instance
Child
kelas Anda akan mendapatkan:Alih-alih menerima
AttributeError
, Anda sekarang benar-benar memanggilsome_prop.setter
metode. Yang memberi Anda lebih banyak kontrol atas objek Anda ... dengan temuan kami sebelumnya, kami tahu bahwa kami perlu memiliki atribut kelas yang ditimpa sebelum mencapai properti. Ini bisa diimplementasikan dalam kelas dasar sebagai pemicu. Ini contoh baru:Yang mengakibatkan:
TA-DAH! Sekarang Anda dapat menimpa atribut instance Anda sendiri di atas properti yang diwarisi!
Jadi, masalah terpecahkan?
... Tidak juga. Masalah dengan pendekatan ini adalah, sekarang Anda tidak dapat memiliki
setter
metode yang tepat . Ada kasus di mana Anda ingin menetapkan nilai padaproperty
. Tapi sekarang setiap kali Anda mengaturnyaself.culture = ...
akan selalu menimpa fungsi apa pun yang Anda tetapkan dalamgetter
(yang dalam hal ini, benar-benar hanya bagian@property
terbungkus. Anda dapat menambahkan dalam langkah-langkah yang lebih bernuansa, tetapi dengan satu atau lain cara itu akan selalu melibatkan lebih dari sekadarself.culture = ...
. misalnya:Itu waaaaay lebih rumit dari jawaban yang lain,
size = None
di tingkat kelas.Anda juga dapat mempertimbangkan untuk menulis deskriptor Anda sendiri untuk menangani metode
__get__
dan__set__
, atau tambahan. Tetapi pada akhirnya, ketikaself.culture
direferensikan, yang__get__
akan selalu dipicu pertama, dan ketikaself.culture = ...
direferensikan,__set__
akan selalu dipicu terlebih dahulu. Tidak ada jalan keluar sejauh yang saya coba.Inti masalahnya, IMO
Masalah yang saya lihat di sini adalah - Anda tidak dapat memiliki kue dan memakannya juga.
property
dimaksudkan seperti deskriptor dengan akses mudah dari metode sepertigetattr
atausetattr
. Jika Anda juga ingin metode ini mencapai tujuan yang berbeda, Anda hanya meminta masalah. Saya mungkin akan memikirkan kembali pendekatan:property
ini?property
, apakah ada alasan saya perlu menimpanya?property
tidak berlaku?property
, akankah metode terpisah bermanfaat bagi saya lebih baik daripada hanya menugaskan kembali, karena penugasan kembali secara tidak sengaja dapat membatalkanproperty
s?Untuk poin 5, pendekatan saya akan memiliki
overwrite_prop()
metode di kelas dasar yang menimpa atribut kelas saat ini sehinggaproperty
tidak akan lagi dipicu:Seperti yang Anda lihat, sementara masih sedikit dibuat-buat, itu setidaknya lebih eksplisit daripada yang samar
size = None
. Yang mengatakan, pada akhirnya, saya tidak akan menimpa properti sama sekali, dan akan mempertimbangkan kembali desain saya dari root.Jika Anda telah sampai sejauh ini - terima kasih telah berjalan dalam perjalanan ini bersama saya. Itu adalah latihan kecil yang menyenangkan.
sumber
A
@property
didefinisikan di tingkat kelas. Dokumentasi membahas secara mendalam tentang cara kerjanya, tetapi cukup untuk mengatakan bahwa pengaturan atau membuat properti memutuskan memanggil metode tertentu. Namun,property
objek yang mengelola proses ini didefinisikan dengan definisi kelas sendiri. Yaitu, itu didefinisikan sebagai variabel kelas tetapi berperilaku seperti variabel instan.Salah satu konsekuensi dari ini adalah Anda dapat menugaskan kembali secara bebas di tingkat kelas :
Dan sama seperti nama level kelas lainnya (misalnya metode), Anda dapat menimpanya dalam subkelas dengan hanya mendefinisikannya secara eksplisit:
Ketika kita membuat instance aktual, variabel instance hanya membayangi variabel kelas dengan nama yang sama. The
property
objek biasanya menggunakan beberapa shenanigans untuk memanipulasi proses ini (yaitu menerapkan getter dan setter) tetapi ketika nama kelas tingkat tidak didefinisikan sebagai properti, tidak ada yang istimewa yang terjadi, dan sehingga bertindak seperti yang Anda harapkan dari variabel lain.sumber
__dict__
? Juga, apakah ada cara untuk mengotomatisasi ini? Ya, itu hanya satu baris tapi itu jenis hal yang sangat samar untuk dibaca jika Anda tidak terbiasa dengan detail mengerikan dari proprties dll.# declare size to not be a @property
Anda tidak perlu penugasan
size
sama sekali.size
adalah properti di kelas dasar, sehingga Anda bisa menimpa properti itu di kelas anak:Anda dapat (mikro) mengoptimalkan ini dengan mengkompilasi akar kuadrat:
sumber
size
adalah properti dan bukan atribut instance, Anda tidak perlu melakukan sesuatu yang mewah.Sepertinya Anda ingin mendefinisikan
size
di kelas Anda:Pilihan lain adalah menyimpan
cap
di kelas Anda dan menghitungnya dengansize
didefinisikan sebagai properti (yang menimpa properti kelas dasarsize
).sumber
Saya sarankan menambahkan setter seperti:
Dengan cara ini Anda dapat mengganti
.size
properti default seperti:sumber
Anda juga dapat melakukan hal berikutnya
sumber
_size
atau di_size_call
sana. Anda bisa saja terpanggang di pemanggilan fungsi di dalamsize
kondisi, dan gunakantry... except...
untuk menguji_size
alih - alih mengambil referensi kelas tambahan yang tidak akan digunakan. Bagaimanapun, saya merasa ini bahkan lebih samar daripadasize = None
menimpa sederhana di tempat pertama.Saya pikir adalah mungkin untuk mengatur properti dari kelas dasar ke properti lain dari kelas turunan, di dalam kelas turunan dan kemudian menggunakan properti dari kelas dasar dengan nilai baru.
Dalam kode awal ada semacam konflik antara nama
size
dari kelas dasar dan atributself.size
dari kelas turunan. Ini bisa terlihat jika kita mengganti namaself.size
dari kelas turunan denganself.length
. Ini akan menampilkan:Kemudian, jika kita mengganti nama metode
size
denganlength
dalam semua kejadian di seluruh program, ini akan menghasilkan pengecualian yang sama:Kode tetap, atau bagaimanapun juga, versi yang entah bagaimana berfungsi, adalah untuk menjaga kode tetap sama, dengan pengecualian kelas
Square_Integers_Below
, yang akan mengatur metodesize
dari kelas dasar, ke nilai lain.Dan kemudian, ketika kita menjalankan semua program, hasilnya adalah:
Saya berharap ini membantu dalam satu atau lain cara.
sumber