Riproduzione semplice:
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
Questa domanda ha un duplicato efficace , ma al duplicato non è stata data risposta e ho scavato un po 'di più nella fonte CPython come esercizio di apprendimento. Attenzione: sono andato tra le erbacce. Spero davvero di poter ottenere aiuto da un capitano che conosce quelle acque . Ho cercato di essere il più esplicito possibile nel tracciare le chiamate che stavo guardando, per i miei benefici futuri e per i futuri lettori.
Ho visto molto inchiostro rovesciato sul comportamento __getattribute__
applicato ai descrittori, ad esempio la precedenza di ricerca. Il Python snippet in "Richiamo descrittori" appena al di sotto For classes, the machinery is in type.__getattribute__()...
d'accordo o meno nella mia mente con quello che credo è il corrispondente fonte CPython in type_getattro
, che ho rintracciato, cercando in "tp_slots" quindi dove tp_getattro è popolato . E il fatto che B.v
inizialmente le stampe __get__, obj=None, objtype=<class '__main__.B'>
abbiano senso per me.
Quello che non capisco è, perché il compito B.v = 3
sovrascrive ciecamente il descrittore, anziché innescarlo v.__set__
? Ho provato a rintracciare la chiamata CPython, partendo ancora una volta da "tp_slots" , quindi guardando dove è popolata tp_setattro , quindi guardando type_setattro . type_setattro
sembra essere un wrapper sottile attorno a _PyObject_GenericSetAttrWithDict . E c'è il nocciolo della mia confusione: _PyObject_GenericSetAttrWithDict
sembra avere una logica che dà la precedenza al __set__
metodo di un descrittore !! Con questo in mente, non riesco a capire perché B.v = 3
sovrascrive ciecamente v
invece di innescare v.__set__
.
Dichiarazione di non responsabilità 1: non ho ricostruito Python dal sorgente con printfs, quindi non sono del tutto sicuro di type_setattro
come venga chiamato durante B.v = 3
.
Dichiarazione di VocalDescriptor
non responsabilità 2: non ha lo scopo di esemplificare la definizione di descrittore "tipica" o "raccomandata". È una no-op verbosa dirmi quando vengono chiamati i metodi.
__get__
funzionato affatto, piuttosto che perché __set__
non ha funzionato.
__get__
metodo. B.v = 3
ha effettivamente sovrascritto l'attributo con un int
.
__get__
viene chiamato e le implementazioni predefinite object.__getattribute__
e type.__getattribute__
invocano __get__
quando si utilizza un'istanza o la classe. L'assegnazione tramite __set__
è solo istanza.
__get__
metodi dei descrittori dovrebbero innescarsi quando invocati dalla classe stessa. Ecco come vengono implementati @classmethods e @staticmethods, secondo la guida pratica . @Jab Mi chiedo perché B.v = 3
sia in grado di sovrascrivere il descrittore di classe. Sulla base dell'implementazione di CPython, mi aspettavo B.v = 3
di innescare anche __set__
.