I was trying to explore how would the descriptor protocol work if I were to access the object of the 'wrapper-descriptor' class itself.
So, I explored the c code in typeobject.c and found two __get__
functions - slot_tp_descr_get
and wrap_descr_get
. They are related by the following command -
TPSLOT(__get__, tp_descr_get, slot_tp_descr_get, wrap_descr_get,
"__get__($self, instance, owner=None, /)\n--\n\nReturn an attribute of instance, which is of type owner.")
The wrap_descr_get
is a wrapper function that wraps the slot_tp_descr_get
.
Going through the code of slot_tp_descr_get
I found it calls the __get__
of the self
argument. So, will it run infinitely if an object of its own type is passed? There is one return statement in the function preluded by an if statement but I don't think it will be run. Because, the 'if clause' checks if the class of the self
implements a __get__
or not, since we pass an object of this class, it will find the __get__
.
Related question
If I call the __get__
of other classes like the function class as written below.
def fun():
pass
type(fun).__get__
or
fun.__get__
Is the underlying __get__
of function class directly called by wrap_descr_get
or as per the descriptor protocol which will look for the class of __get__
(wrapper-descriptor here) and call the class's __get__
i.e. slot_tp_descr_get
?
wrap_descr_get
doesn't wrap slot_tp_descr_get
. slot_tp_descr_get
is only one possible implementation of the tp_descr_get
slot, and it's never the one wrap_descr_get
sees.
A slot_*
function and a wrapper descriptor will never be used for the same slot of the same type.
slot_*
functions are used to provide a C slot implementation corresponding to a Python method, when a type implements the method in Python. There's one for every slot that corresponds to a Python method.
slot_tp_descr_get
is the function used to provide a C slot implementation for tp_descr_get
when a class implements __get__
in Python. It's the function that goes in the tp_descr_get
slot of a class like this:
class Example:
def __get__(self, instance, owner=None):
...
When called, slot_tp_descr_get
will look up the __get__
method and call that.
Wrapper descriptors are used to perform the opposite direction of wrapping. They provide an implementation for Python methods corresponding to C slots, when a type implements the C slots directly.
Wrapper descriptors are instances of types.WrapperDescriptorType
. That type is implemented in Objects/descrobject.c
, along with several other descriptor types.
Wrapper-descriptors need a way to translate incoming Python-level calls into the argument format taken by whatever slot they wrap. For wrapper-descriptors that wrap a tp_descr_get
function, the way they translate calls is wrap_descr_get
.
The wrapper-descriptor type also happens to have its own tp_descr_get
function, wrapperdescr_get
, used to produce bound method objects for whatever method a wrapper-descriptor represents. This is separate from the slot the wrapper-descriptor wraps, even if that slot is a tp_descr_get
slot.
So for types that implement __get__
in Python, the C slot function will be set to slot_tp_descr_get
, and slot_tp_descr_get
will delegate to __get__
. For types that implement tp_descr_get
in C, a wrapper descriptor will be generated to implement __get__
, and the wrapper descriptor will use wrap_descr_get
to call tp_descr_get
.
In no case will slot_tp_descr_get
and wrap_descr_get
end up recursively delegating to each other.