Skip to main content

Objects/descrobject.c (part 7)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/descrobject.c

This annotation covers the built-in descriptor types. See objects_descrobject6_detail for property, slot_wrapper, method_wrapper, and the descriptor protocol dispatch.

Map

LinesSymbolRole
1-80classmethod_descriptorWrap a C function as a classmethod
81-180staticmethod_descriptorWrap a C function as a staticmethod
181-300member_descriptorRead/write a C struct member by offset
301-440getset_descriptorGetter/setter pair for C struct fields
441-600wrapper_descriptorWrap a slotwrapper as a descriptor (e.g. int.__add__)

Reading

classmethod_descriptor

// CPython: Objects/descrobject.c:1420 classmethod_get
static PyObject *
classmethod_get(PyObject *self, PyObject *obj, PyObject *cls)
{
PyMethodDescrObject *descr = (PyMethodDescrObject *)self;
if (cls == NULL) {
if (obj != NULL)
cls = (PyObject *)Py_TYPE(obj);
else {
Py_INCREF(descr);
return (PyObject *)descr;
}
}
return PyCFunction_NewEx(descr->d_method, cls, NULL);
}

classmethod_descriptor.__get__(obj, cls) binds the C function to the class, not the instance. dict.fromkeys is a classmethod_descriptor: dict.fromkeys is unbound, my_dict.fromkeys is bound to dict (not my_dict).

member_descriptor

// CPython: Objects/descrobject.c:1680 member_get
static PyObject *
member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type)
{
if (obj == NULL) {
Py_INCREF(descr);
return (PyObject *)descr;
}
PyMemberDef *member = descr->d_member;
/* Use PyMember_GetOne to extract the field at member->offset
from the object's C struct */
return PyMember_GetOne((char *)obj, member);
}

PyMember_GetOne reads a C struct member at obj + offset and converts it to a Python object based on member->type (T_INT, T_DOUBLE, T_OBJECT_EX, etc.). Used by list.ob_size (internal), complex.real, complex.imag, and many C extension types.

getset_descriptor

// CPython: Objects/descrobject.c:1820 getset_get
static PyObject *
getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type)
{
if (obj == NULL) {
Py_INCREF(descr);
return (PyObject *)descr;
}
if (descr->d_getset->get == NULL) {
PyErr_Format(PyExc_AttributeError, "unreadable attribute");
return NULL;
}
return descr->d_getset->get(obj, descr->d_getset->closure);
}

PyGetSetDef has get, set, and closure fields. The closure pointer is passed to get/set, allowing the same C function to serve multiple attributes with different parameters. dict.keys, list.__doc__, and int.real are all getset_descriptor instances.

wrapper_descriptor

// CPython: Objects/descrobject.c:2020 wrapperdescr_get
static PyObject *
wrapperdescr_get(PyWrapperDescrObject *descr, PyObject *obj, PyObject *type)
{
if (obj == NULL || (PyObject *)Py_TYPE(obj) != type) {
Py_INCREF(descr);
return (PyObject *)descr;
}
return PyWrapper_New((PyObject *)descr, obj);
}

int.__add__ is a wrapper_descriptor. (3).__add__ returns a method-wrapper (an instance of PyWrapperObject) binding the __add__ slot to 3. Calling it ((3).__add__(4)) invokes PyNumber_Add(3, 4) via the slot.

gopy notes

classmethod_descriptor is objects.ClassMethodDescriptor in objects/descr.go. member_descriptor reads fields via unsafe.Pointer arithmetic. getset_descriptor stores Go func(obj objects.Object) objects.Object pairs. wrapper_descriptor is objects.SlotWrapper binding a method table entry.