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
Cette question a un doublon efficace , mais le double n'a pas été répondu, et j'ai creusé un peu plus dans la source CPython comme exercice d'apprentissage. Attention: je suis entré dans les mauvaises herbes. J'espère vraiment pouvoir obtenir l'aide d' un capitaine qui connaît ces eaux . J'ai essayé d'être aussi explicite que possible en traçant les appels que je regardais, pour mon propre avantage futur et celui des futurs lecteurs.
J'ai vu beaucoup d'encre renversée sur le comportement des __getattribute__
appliqués aux descripteurs, par exemple la priorité de recherche. L'extrait Python dans "Invoking Descriptors" juste en dessous For classes, the machinery is in type.__getattribute__()...
correspond à peu près dans mon esprit avec ce que je pense être la source CPython correspondante dans type_getattro
laquelle j'ai trouvé en regardant "tp_slots" puis où tp_getattro est peuplé . Et le fait qu'imprime B.v
initialement ait du __get__, obj=None, objtype=<class '__main__.B'>
sens pour moi.
Ce que je ne comprends pas, c'est pourquoi l'affectation B.v = 3
écrase aveuglément le descripteur, plutôt que de se déclencher v.__set__
? J'ai essayé de tracer l'appel CPython, en recommençant à partir de "tp_slots" , puis en regardant où tp_setattro est rempli , puis en regardant type_setattro . type_setattro
semble être une enveloppe mince autour de _PyObject_GenericSetAttrWithDict . Et il y a le nœud de ma confusion: _PyObject_GenericSetAttrWithDict
semble avoir une logique qui donne la priorité à la __set__
méthode d' un descripteur !! Dans cet esprit, je ne peux pas comprendre pourquoi B.v = 3
écrase aveuglément v
plutôt que de déclencher v.__set__
.
Avertissement 1: Je n'ai pas reconstruit Python à partir de la source avec printfs, donc je ne suis pas complètement sûr de type_setattro
ce qui est appelé pendant B.v = 3
.
Avertissement 2: VocalDescriptor
n'est pas destiné à illustrer la définition de descripteur "typique" ou "recommandé". C'est un non-verbeux pour me dire quand les méthodes sont appelées.
__get__
tout a fonctionné, plutôt que pourquoi __set__
pas.
__get__
méthode. B.v = 3
a effectivement remplacé l'attribut par un int
.
__get__
est appelé, ainsi que les implémentations par défaut object.__getattribute__
et type.__getattribute__
invoquées __get__
lors de l'utilisation d'une instance ou de la classe. L'affectation via __set__
est uniquement une instance.
__get__
méthodes des descripteurs sont censées se déclencher lorsqu'elles sont appelées à partir de la classe elle-même. C'est ainsi que @classmethods et @staticmethods sont implémentés, selon le guide pratique . @Jab Je me demande pourquoi B.v = 3
est capable d'écraser le descripteur de classe. Sur la base de l'implémentation de CPython, je m'attendais B.v = 3
à déclencher également __set__
.