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.vinizialmente le stampe __get__, obj=None, objtype=<class '__main__.B'>abbiano senso per me.
Quello che non capisco è, perché il compito B.v = 3sovrascrive 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_GenericSetAttrWithDictsembra avere una logica che dà la precedenza al __set__metodo di un descrittore !! Con questo in mente, non riesco a capire perché B.v = 3sovrascrive ciecamente vinvece 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_setattrocome venga chiamato durante B.v = 3.
Dichiarazione di VocalDescriptornon 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 = 3ha 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 = 3sia in grado di sovrascrivere il descrittore di classe. Sulla base dell'implementazione di CPython, mi aspettavo B.v = 3di innescare anche __set__.