pythoncpython

Understanding descriptor protocol for 'wrapper-descriptor' itself


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?


Solution

  • 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.