Skip to main content

Include/cpython/descrobject.h

cpython 3.14 @ ab2d84fe1023/Include/cpython/descrobject.h

This header declares the internal struct layouts and factory functions for CPython's four built-in descriptor types: method descriptors, member descriptors, getset descriptors, and slot wrapper descriptors. These objects are created at type-creation time (by type_new and PyType_Ready) and placed into the type's tp_dict. When attribute lookup finds one of these in the MRO, the descriptor protocol invokes tp_descr_get (and optionally tp_descr_set) to mediate access.

Map​

SymbolKindPurpose
PyDescrObjectstructCommon prefix shared by all descriptor types
PyMemberDescrObjectstructDescriptor for PyMemberDef (struct-offset members)
PyMethodDescrObjectstructDescriptor for PyMethodDef (C functions)
PyWrapperDescrObjectstructDescriptor wrapping a slotdef (special method slot)
PyGetSetDescrObjectstructDescriptor for PyGetSetDef (getter/setter pairs)
d_typefieldThe type that owns this descriptor
d_namefield__name__ of the attribute
d_qualnamefield__qualname__ (computed lazily)
PyDescr_NewMethodfunctionCreate a PyMethodDescrObject
PyDescr_NewMemberfunctionCreate a PyMemberDescrObject
PyDescr_NewGetSetfunctionCreate a PyGetSetDescrObject
PyDescr_NewWrapperfunctionCreate a PyWrapperDescrObject
PyDescr_IsDatamacroTrue if the descriptor defines tp_descr_set

Reading​

PyDescrObject: the shared prefix​

All four descriptor types begin with the same three fields. The common prefix allows descriptor protocol code in Objects/object.c to read d_type and d_name without knowing the concrete type.

typedef struct {
PyObject_HEAD
PyTypeObject *d_type; /* type that owns this descriptor */
PyObject *d_name; /* __name__ */
PyObject *d_qualname; /* __qualname__, NULL until first access */
} PyDescrObject;

Concrete structs extend this prefix. For example:

typedef struct {
PyDescrObject d_common;
PyMethodDef *d_method;
vectorcallfunc vectorcall;
} PyMethodDescrObject;

typedef struct {
PyDescrObject d_common;
PyGetSetDef *d_getset;
} PyGetSetDescrObject;

The d_type back-pointer is the key difference between descriptors and ordinary callables. It is used by PyDescr_NewMethod and friends to bind __objclass__, and by tp_descr_get to enforce that the descriptor is only invoked on compatible instances.

Data descriptors vs. non-data descriptors​

The descriptor protocol distinguishes two classes of descriptor: data descriptors (those that define both tp_descr_get and tp_descr_set) and non-data descriptors (those that define only tp_descr_get). This distinction controls lookup priority relative to the instance __dict__.

/* Returns 1 if descr is a data descriptor (has tp_descr_set). */
#define PyDescr_IsData(descr) \
(Py_TYPE(descr)->tp_descr_set != NULL)

PyMemberDescrObject and PyGetSetDescrObject are data descriptors when the underlying PyGetSetDef provides a setter. PyMethodDescrObject and PyWrapperDescrObject are always non-data descriptors because C methods cannot intercept assignment.

Objects/object.c:_PyObject_GenericGetAttrWithDict consults PyDescr_IsData to decide whether to check the instance dict before calling tp_descr_get.

Factory functions and type ownership​

Each factory takes the owning type and the definition struct:

PyAPI_FUNC(PyObject *) PyDescr_NewMethod(
PyTypeObject *type, PyMethodDef *meth);
PyAPI_FUNC(PyObject *) PyDescr_NewMember(
PyTypeObject *type, PyMemberDef *meth);
PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(
PyTypeObject *type, PyGetSetDef *meth);
PyAPI_FUNC(PyObject *) PyDescr_NewWrapper(
PyTypeObject *type, struct wrapperbase *base, void *wrapped);

All four are called from PyType_Ready as it iterates tp_methods, tp_members, tp_getset, and the internal slotdefs table. The resulting descriptor objects are inserted directly into type->tp_dict. Subtype creation inherits them through normal MRO lookup rather than copying.

gopy mirror​

gopy represents all four descriptor types in objects/descr.go:

  • MethodDescr mirrors PyMethodDescrObject. It holds a *MethodDef and implements tp_descr_get by returning a bound method.
  • MemberDescr mirrors PyMemberDescrObject. It uses field offset information to read and write struct fields on Go-backed objects.
  • GetSetDescr mirrors PyGetSetDescrObject. It stores Go getter and setter functions of type func(*Object) (*Object, error) and func(*Object, *Object) error.
  • WrapperDescr mirrors PyWrapperDescrObject. It wraps slot functions from Go type definitions, enabling special method lookup via the standard MRO.

The DescrObject base struct in objects/descr.go carries dType and dName, matching the PyDescrObject prefix.

CPython 3.14 changes​

CPython 3.14 adds vectorcall support to PyMethodDescrObject (the vectorcall field in the struct layout above). This allows CALL bytecode to invoke method descriptors without creating a bound method object as an intermediate step, providing a measurable speedup for obj.method(args) call sites. The d_qualname lazy-computation field was added in 3.3 alongside __qualname__ support for classes; before that, method descriptors reported only __name__.