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_getattrolaquelle j'ai trouvé en regardant "tp_slots" puis où tp_getattro est peuplé . Et le fait qu'imprime B.vinitialement 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_GenericSetAttrWithDictsemble 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 vplutô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_setattroce qui est appelé pendant B.v = 3.
Avertissement 2: VocalDescriptorn'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 = 3a 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 = 3est 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__.