Repro sederhana:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
class B(object):
v = VocalDescriptor()
B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor
Pertanyaan ini memiliki duplikat yang efektif , tetapi duplikat itu tidak dijawab, dan saya menggali lebih dalam sumber CPython sebagai latihan pembelajaran. Peringatan: saya pergi ke gulma. Saya benar-benar berharap bisa mendapatkan bantuan dari seorang kapten yang mengetahui perairan itu . Saya mencoba untuk menjadi sejelas mungkin dalam melacak panggilan yang saya lihat, untuk keuntungan saya di masa depan dan manfaat pembaca di masa depan.
Saya telah melihat banyak tinta tumpah karena perilaku yang __getattribute__
diterapkan pada deskriptor, misalnya pencarian prioritas. Python potongan di "menyerukan Deskriptor" tepat di bawah For classes, the machinery is in type.__getattribute__()...
kira-kira setuju dalam pikiran saya dengan apa yang saya percaya adalah yang sesuai sumber CPython di type_getattro
, yang saya melacak dengan melihat "tp_slots" maka di mana tp_getattro dihuni . Dan fakta bahwa B.v
awalnya mencetak __get__, obj=None, objtype=<class '__main__.B'>
masuk akal bagi saya.
Yang tidak saya mengerti adalah, mengapa tugas itu B.v = 3
secara buta menimpa deskriptor, bukannya memicu v.__set__
? Saya mencoba melacak panggilan CPython, mulai sekali lagi dari "tp_slots" , kemudian melihat di mana tp_setattro diisi , kemudian melihat type_setattro . type_setattro
tampaknya menjadi pembungkus tipis di sekitar _PyObject_GenericSetAttrWithDict . Dan ada inti dari kebingungan saya: _PyObject_GenericSetAttrWithDict
tampaknya memiliki logika yang memberikan diutamakan untuk keterangan ini __set__
metode !! Dengan mengingat hal ini, saya tidak tahu mengapa B.v = 3
menindih secara membabi buta v
daripada memicu v.__set__
.
Penafian 1: Saya tidak membangun kembali Python dari sumber dengan printfs, jadi saya tidak sepenuhnya yakin type_setattro
apa yang dipanggil selama B.v = 3
.
Penafian 2: VocalDescriptor
tidak dimaksudkan untuk memberikan contoh definisi deskriptor "tipikal" atau "direkomendasikan". Ini adalah no-op verbose untuk memberi tahu saya ketika metode dipanggil.
sumber
__get__
bekerja sama sekali, daripada mengapa__set__
tidak.__get__
metode ini.B.v = 3
telah secara efektif menimpa atribut denganint
.__get__
dipanggil, dan implementasi defaultobject.__getattribute__
dantype.__getattribute__
memanggil__get__
ketika menggunakan instance atau kelas. Penugasan via__set__
hanya untuk instance.__get__
metode deskriptor seharusnya dipicu ketika dipanggil dari kelas itu sendiri. Ini adalah bagaimana @classmethods dan @staticmethods diimplementasikan, sesuai dengan panduan cara . @ Jab Saya bertanya-tanya mengapaB.v = 3
bisa menimpa deskriptor kelas. Berdasarkan implementasi CPython, saya berharapB.v = 3
juga memicu__set__
.Jawaban:
Anda benar bahwa
B.v = 3
hanya menimpa deskriptor dengan integer (sebagaimana mestinya).Untuk
B.v = 3
memohon deskriptor, deskriptor seharusnya didefinisikan pada metaclass, yaitu padatype(B)
.Untuk mengaktifkan deskriptor pada
B
, Anda akan menggunakan contoh:B().v = 3
akan melakukannya.Alasan untuk
B.v
memanggil pengambil adalah untuk memungkinkan mengembalikan contoh deskriptor itu sendiri. Biasanya Anda akan melakukan itu, untuk memungkinkan akses pada deskriptor melalui objek kelas:Sekarang
B.v
akan mengembalikan beberapa contoh seperti<mymodule.VocalDescriptor object at 0xdeadbeef>
yang dapat Anda berinteraksi. Secara harfiah objek deskriptor, didefinisikan sebagai atribut kelas, dan kondisinyaB.v.__dict__
dibagi antara semua contohB
.Tentu saja tergantung pada kode pengguna untuk menentukan dengan tepat apa yang ingin mereka
B.v
lakukan, mengembalikanself
hanya pola umum.sumber
__get__
dirancang untuk disebut sebagai atribut instance atau atribut kelas, tetapi__set__
dirancang untuk dipanggil hanya sebagai atribut instance. Dan dokumen yang relevan: docs.python.org/3/reference/datamodel.html#object.__get___PyObject_GenericSetAttrWithDict
, ia menarik Py_TYPE dari B sebagaitp
, yang merupakan B's metaclass (type
dalam kasus saya), maka itu adalah metaclasstp
yang diperlakukan oleh logika hubung singkat deskriptor . Jadi deskriptor didefinisikan langsung padaB
tidak terlihat bahwa logika hubungan arus pendek (karena itu dalam kode asli saya__set__
tidak disebut), tapi deskriptor didefinisikan pada metaclass yang adalah dilihat oleh logika hubungan arus pendek.__set__
metode deskriptor itu disebut.Kecuali segala override,
B.v
sama dengantype.__getattribute__(B, "v")
, sementarab = B(); b.v
setara denganobject.__getattribute__(b, "v")
. Kedua definisi tersebut memanggil__get__
metode hasil jika didefinisikan.Perhatikan, pikirkan, bahwa panggilan untuk
__get__
berbeda dalam setiap kasus.B.v
lewatNone
sebagai argumen pertama, sementaraB().v
melewati instance itu sendiri. Dalam kedua kasusB
dilewatkan sebagai argumen kedua.B.v = 3
, di sisi lain, sama dengantype.__setattr__(B, "v", 3)
, yang tidak meminta__set__
.sumber