Skip to main content

Objects/descrobject.c

cpython 3.14 @ ab2d84fe1023/Objects/descrobject.c

Descriptor types and the dict proxy. This file implements the five core descriptor types that back the Python data model for C-defined types: PyMethodDescrObject (wraps a PyMethodDef, produces bound PyCFunction on instance access), PyMemberDescrObject (wraps a PyMemberDef, reads and writes C struct fields via PyMember_GetOne/SetOne), PyGetSetDescrObject (wraps a PyGetSetDef, calls C getter/setter functions), PyWrapperDescrObject (wraps a slot function pointer for slot_tp_* wrappers exposed as __add__ etc.), and PyClassMethodDescrObject (like method but binds the class instead of the instance). PyDictProxy_New creates an immutable mappingproxy view over a dict, used for type.__dict__. The tp_descr_get slot on each type implements the descriptor protocol: an instance access returns a bound object while a class access returns the descriptor itself. _PyDescr_Init registers all five descriptor types at interpreter startup.

Map

LinesSymbolRolegopy
1-120descr_new, PyDescr_NewMethod, method_repr, method_get_doc, method_get_text_signatureAllocate a PyMethodDescrObject from a PyMethodDef; repr and doc-string accessor.objects/descr.go:NewGetSetDescr
121-280method_descr_get, classmethod_gettp_descr_get for method and classmethod descriptors: instance access creates a PyCFunctionObject bound to the instance; class access binds the class.objects/descr.go:getsetDescrGet
281-450PyDescr_NewMember, member_get, member_set, member_reprPyMemberDescrObject wrapping a PyMemberDef; field access via PyMember_GetOne/SetOne using offset arithmetic into the C struct.objects/descr.go
451-650PyDescr_NewGetSet, getset_get, getset_set, getset_reprPyGetSetDescrObject wrapping a PyGetSetDef; calls gs_get(obj, gs_closure) or gs_set(obj, value, gs_closure).objects/descr.go:getsetDescrGet
651-900PyDescr_NewWrapper, wrapperdescr_get, wrapper_repr, PyWrapperDescrObjectSlot wrappers: PyWrapperDescrObject holds a slotdef* and exposes the C slot as a Python slot_wrapper descriptor.objects/descr.go
901-1050PyDescr_NewClassMethod, classmethod_descr_get, PyClassMethodDescrObjectClass method descriptor; tp_descr_get always binds the type, regardless of whether accessed from an instance or the class.objects/descr.go
1051-1250PyDictProxy_New, mappingproxy_getitem, mappingproxy_len, mappingproxy_iter, mappingproxy_repr, mappingproxy_richcompareImmutable mappingproxy view wrapping any mapping; used for type.__dict__.objects/mapping_proxy.go:NewMappingProxy
1251-1450PyMemberDef accessor helpers, PyDescr_IsData, descr_checkRuntime checks: descr_check verifies that the descriptor's d_type is in the MRO of the accessing type before binding.objects/descr.go:LookupDescriptor
1451-1650PyMethodDef slot type tables, PyMemberDescrType, PyGetSetDescrType, PyMethodDescrType, PyWrapperDescrType, PyClassMethodDescrTypeType object definitions for all five descriptor kinds.objects/descr.go:GetSetDescrType
1651-1800_PyDescr_Init, PyMappingProxyTypeInitializes descriptor types and mappingproxy at interpreter startup.objects/descr.go, objects/mapping_proxy.go

Reading

Method descriptor and descr_get (lines 121 to 280)

cpython 3.14 @ ab2d84fe1023/Objects/descrobject.c#L121-280

method_descr_get is the tp_descr_get slot for PyMethodDescrObject. The descriptor protocol requires that accessing a descriptor from an instance binds it to that instance, while class access returns the descriptor itself. For method descriptors, instance binding creates a PyCFunctionObject with self set to the instance:

static PyObject *
method_descr_get(PyObject *descr, PyObject *obj, PyObject *type)
{
PyMethodDescrObject *d = (PyMethodDescrObject *)descr;

if (obj == NULL || obj == Py_None) {
/* Accessed from the class: return the unbound descriptor. */
return Py_NewRef(descr);
}
if (!descr_check((PyDescrObject *)d, obj))
return NULL;
return PyCFunction_NewInternal(d->d_method, obj, NULL,
d->d_common.d_qualname);
}

descr_check verifies that type(obj) is a subtype of d->d_common.d_type, the type the descriptor was defined on. This prevents calling a descriptor from an unrelated type that happens to store it in its dict. classmethod_get is identical except it passes type (or Py_TYPE(obj)) as the first argument rather than obj.

Member and getset descriptors (lines 281 to 650)

cpython 3.14 @ ab2d84fe1023/Objects/descrobject.c#L281-650

PyMemberDescrObject and PyGetSetDescrObject are the two data descriptor kinds (they define both tp_descr_get and tp_descr_set, so they shadow instance __dict__ entries). Member descriptors use PyMemberDef.offset to index directly into the C struct:

static PyObject *
member_get(PyObject *descr, PyObject *obj, PyObject *type)
{
PyMemberDescrObject *d = (PyMemberDescrObject *)descr;
if (descr_check((PyDescrObject *)d, obj) < 0)
return NULL;
if (d->d_member->flags & READ_RESTRICTED) {
if (PySys_Audit("object.__getattr__", "Os",
obj, d->d_member->name) < 0)
return NULL;
}
return PyMember_GetOne((char *)obj, d->d_member);
}

PyMember_GetOne (in Python/structmember.c) reads the field at (char *)obj + offset and converts it to a Python object according to the T_* type code (T_INT, T_DOUBLE, T_OBJECT_EX, etc.).

Getset descriptors call C function pointers instead:

static PyObject *
getset_get(PyObject *descr, PyObject *obj, PyObject *type)
{
PyGetSetDescrObject *d = (PyGetSetDescrObject *)descr;
if (descr_check((PyDescrObject *)d, obj) < 0)
return NULL;
if (d->d_getset->get == NULL) {
PyErr_Format(PyExc_AttributeError, "unreadable attribute");
return NULL;
}
return d->d_getset->get(obj, d->d_getset->closure);
}

The closure pointer lets the same getter function serve multiple attributes by carrying a type tag or offset as a void *.

PyDictProxy_New (lines 1051 to 1250)

cpython 3.14 @ ab2d84fe1023/Objects/descrobject.c#L1051-1250

PyDictProxy_New wraps any mapping in a mappingproxy object that exposes read operations but raises TypeError on any write. This is used for type.__dict__ to prevent accidental direct mutation of the type's namespace (all type mutations must go through type.__setattr__ so that slot updates and watcher notifications fire):

PyObject *
PyDictProxy_New(PyObject *mapping)
{
mappingproxyobject *pp;

if (!PyMapping_Check(mapping)) {
PyErr_SetString(PyExc_TypeError,
"mappingproxy only wraps mappings");
return NULL;
}
pp = PyObject_GC_New(mappingproxyobject, &PyDictProxy_Type);
if (pp == NULL)
return NULL;
Py_INCREF(mapping);
pp->dict = mapping;
_PyObject_GC_TRACK(pp);
return (PyObject *)pp;
}

mappingproxy_setitem and mappingproxy_setattr both return TypeError: 'mappingproxy' object does not support item assignment. The underlying mapping is not copied; writes to the original dict are visible through the proxy, so type.__dict__['key'] reflects the live type state.

gopy mirror

objects/descr.go for GetSetDescr, LookupDescriptor, SetTypeDescr, and related helpers. objects/property.go for the Python-level property descriptor. objects/mapping_proxy.go for MappingProxy / NewMappingProxy (PyDictProxy_New). The descr_check type validation maps to the MRO walk in LookupDescriptor.

CPython 3.14 changes

PyClassMethodDescrObject stable since 3.0. PyGetSetDef.closure has always been present. Slot wrapper infrastructure (PyWrapperDescrObject) unchanged since the type system rewrite in 2.2. PyDictProxy_New accepts any mapping (not just dicts) since 3.3. The READ_RESTRICTED audit event on member descriptors was added in 3.10.