Skip to main content

Objects/descrobject.c

Source:

cpython 3.14 @ ab2d84fe1023/Objects/descrobject.c

Map

LinesSymbolPurpose
1–80PyMemberDescrObject layoutStruct embedding PyDescrObject plus a PyMemberDef * pointer
81–160member_get / member_setRead/write struct fields at fixed byte offsets
161–280methoddescr_callDispatch for PyMethodDef-backed descriptors
281–420wrapperdescr_callDispatch for C slot wrappers (slot_tp_descr_get)
421–560classmethoddescr_callPass type as first argument instead of instance
561–720getset_get / getset_setCall PyGetSetDef.get / .set with closure pointer
721–950property_descr_getThree-way branch on instance vs. class vs. None owner
951–1100property_descr_setRoute to fset or fdel depending on value presence
1101–1300property_copy / property_initConstructor and property() keyword handling
1301–1400PyDescr_NewMember etc.Public factory functions for all descriptor kinds

Reading

PyMemberDescrObject and slot_tp_descr_get

PyMemberDescrObject is the runtime object for a field declared with PyMemberDef. It stores a pointer back to the def so member_get can locate the field by byte offset relative to the instance pointer.

slot_tp_descr_get is the tp_descr_get slot installed on every descriptor type. It checks whether the owner is None (class-level access) and returns the descriptor itself in that case, otherwise it calls the type-specific get implementation.

// CPython: Objects/descrobject.c:165 slot_tp_descr_get
static PyObject *
slot_tp_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
PyTypeObject *tp = Py_TYPE(self);
descrgetfunc f = tp->tp_descr_get;
if (f == NULL) {
return Py_NewRef(self);
}
return f(self, obj, type);
}

member_get reads raw memory using PyMember_GetOne, which switches on the type field in the PyMemberDef to decide how many bytes to read and how to box them as a Python object.

// CPython: Objects/descrobject.c:92 member_get
static PyObject *
member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type)
{
if (descr_check((PyDescrObject *)descr, obj, &res) < 0)
return NULL;
return PyMember_GetOne((char *)obj, descr->d_member);
}

wrapperdescr for C slot wrappers

Wrapper descriptors bridge the Python descriptor protocol to raw C slots such as tp_hash or nb_add. Each PyWrapperDescrObject records a slotdef * that carries the wrapper function and a stack-effect offset.

When called, wrapperdescr_call creates a PyMethodWrapper (a bound wrapper) if called on an instance, or invokes the underlying slot directly when dispatched as an unbound call from the type.

// CPython: Objects/descrobject.c:322 wrapperdescr_call
static PyObject *
wrapperdescr_call(PyWrapperDescrObject *descr, PyObject *args, PyObject *kwds)
{
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
if (nargs < 1) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' needs an argument",
descr_name((PyDescrObject *)descr), "?");
return NULL;
}
PyObject *self = PyTuple_GET_ITEM(args, 0);
/* ... type check, then call wrapper ... */
return (*descr->d_wrapped)(self, args, kwds);
}

property descriptor (property_descr_get / property_descr_set)

The pure-C property type stores three optional callables (fget, fset, fdel) plus a __doc__ string. property_descr_get has a three-way branch.

  • obj is None means the access came from the class, so the property object itself is returned.
  • Otherwise fget is called with the instance.
  • If fget is NULL, AttributeError is raised.
// CPython: Objects/descrobject.c:773 property_descr_get
static PyObject *
property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
propertyobject *gs = (propertyobject *)self;
if (obj == NULL || obj == Py_None) {
return Py_NewRef(self);
}
if (gs->prop_get == NULL) {
PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
return NULL;
}
return PyObject_CallOneArg(gs->prop_get, obj);
}

property_descr_set distinguishes deletion (value is NULL) from assignment, routing to fdel or fset accordingly. Both raise AttributeError if the matching callable is absent.

gopy notes

Status: not yet ported.

Planned package path: objects/ (files descr.go, property.go).

The existing objects/descr.go stub in the repo defines the descriptor interface but does not yet implement MemberDescr, WrapperDescr, or GetSetDescr. property_descr_get/set logic is partially sketched in objects/property.go. Full port requires PyMember_GetOne byte-offset reads, which depend on the Go unsafe.Pointer offset helpers being wired up first.