Repro simple:
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
Esta pregunta tiene un duplicado efectivo , pero el duplicado no fue respondido, y busqué un poco más en la fuente de CPython como ejercicio de aprendizaje. Advertencia: entré en la maleza. Realmente espero poder obtener ayuda de un capitán que conozca esas aguas . Intenté ser lo más explícito posible al rastrear las llamadas que estaba viendo, para mi propio beneficio futuro y el beneficio de futuros lectores.
He visto mucha tinta derramada sobre el comportamiento de los __getattribute__
descriptores aplicados, por ejemplo, la precedencia de búsqueda. El fragmento de código Python en "Invocación descriptores" justo debajo For classes, the machinery is in type.__getattribute__()...
está de acuerdo más o menos en mi mente con lo que creo es la correspondiente fuente CPython en type_getattro
que localicé examinado "tp_slots" a continuación, donde tp_getattro está poblado . Y el hecho de que B.v
inicialmente imprima __get__, obj=None, objtype=<class '__main__.B'>
tiene sentido para mí.
Lo que no entiendo es, ¿por qué la tarea B.v = 3
sobrescribe ciegamente el descriptor, en lugar de desencadenar v.__set__
? Traté de rastrear la llamada CPython, comenzando una vez más desde "tp_slots" , luego mirando dónde está poblado tp_setattro y luego mirando type_setattro . type_setattro
parece ser una envoltura delgada alrededor de _PyObject_GenericSetAttrWithDict . Y ahí está el quid de mi confusión: ¡ _PyObject_GenericSetAttrWithDict
parece tener una lógica que da prioridad al __set__
método de un descriptor ! Con esto en mente, no puedo entender por qué B.v = 3
sobrescribe ciegamente en v
lugar de disparar v.__set__
.
Descargo de responsabilidad 1: no reconstruí Python desde el origen con printfs, por lo que no estoy completamente seguro de type_setattro
cómo se llama durante B.v = 3
.
Descargo de responsabilidad 2: VocalDescriptor
no pretende ejemplificar la definición de descriptor "típico" o "recomendado". Es un no-op detallado para decirme cuándo se llaman los métodos.
__get__
funcionó, en lugar de por qué __set__
no.
__get__
método. B.v = 3
ha sobrescrito efectivamente el atributo con un int
.
__get__
se llama y las implementaciones predeterminadas object.__getattribute__
e type.__getattribute__
invocan __get__
cuando se usa una instancia o la clase. La asignación a través de __set__
es solo de instancia.
__get__
métodos de los descriptores deben activarse cuando se invocan desde la clase misma. Así es como se implementan @classmethods y @staticmethods, de acuerdo con la guía práctica . @Jab Me pregunto por qué B.v = 3
es posible sobrescribir el descriptor de la clase. Según la implementación de CPython, esperaba B.v = 3
que también se activara __set__
.