Reprodução simples:
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
Essa pergunta possui uma duplicata efetiva , mas a duplicata não foi respondida e eu procurei um pouco mais na fonte do CPython como um exercício de aprendizado. Atenção: entrei no mato. Estou realmente esperando conseguir ajuda de um capitão que conhece essas águas . Tentei ser o mais explícito possível ao rastrear as ligações que estava olhando, para meu próprio benefício futuro e o benefício de futuros leitores.
Eu já vi muita tinta derramada sobre o comportamento de __getattribute__
aplicado aos descritores, por exemplo, precedência de pesquisa. O trecho de código Python em "Invoking Descriptors", logo abaixo, For classes, the machinery is in type.__getattribute__()...
concorda em minha mente com o que acredito ser a fonte CPython correspondente type_getattro
, na qual localizei olhando "tp_slots" e depois onde tp_getattro é preenchido . E o fato de B.v
inicialmente imprimir __get__, obj=None, objtype=<class '__main__.B'>
faz sentido para mim.
O que eu não entendo é: por que a tarefa B.v = 3
substitui cegamente o descritor, em vez de disparar v.__set__
? Tentei rastrear a chamada CPython, iniciando mais uma vez a partir de "tp_slots" , depois olhando para onde tp_setattro está preenchido , depois olhando para type_setattro . type_setattro
parece ser um invólucro fino em torno de _PyObject_GenericSetAttrWithDict . E há o cerne da minha confusão: _PyObject_GenericSetAttrWithDict
parece ter lógica que dá precedência ao __set__
método de um descritor !! Com isso em mente, não consigo descobrir por que B.v = 3
substitui cegamente, em v
vez de acionar v.__set__
.
Isenção de responsabilidade 1: Eu não reconstruí o Python da fonte com o printfs, por isso não tenho muita certeza do type_setattro
que está sendo chamado durante B.v = 3
.
Isenção de responsabilidade 2: VocalDescriptor
não pretende exemplificar a definição do descritor "típico" ou "recomendado". É um no-op detalhado para me dizer quando os métodos estão sendo chamados.
__get__
funcionou, e não por __set__
que não funcionou.
__get__
método. B.v = 3
efetivamente substituiu o atributo por um int
.
__get__
é chamado e as implementações padrão de object.__getattribute__
e type.__getattribute__
invocam __get__
ao usar uma instância ou a classe. A atribuição via __set__
é apenas para instância.
__get__
métodos dos descritores devem disparar quando invocados da própria classe. É assim que @classmethods e @staticmethods são implementados, de acordo com o guia de instruções . @ Jab Eu estou querendo saber por que B.v = 3
é capaz de substituir o descritor de classe. Com base na implementação do CPython, eu esperava B.v = 3
também disparar __set__
.