CPython's PyCapsule Implementation in C: A void* Wrapper for Extension Modules
PyCapsule carries a C void* through the interpreter: the struct, name matching, destructors, context, optional GC traversal and PyCapsule_Import traced through CPython 3.15.
TL;DR: A
PyCapsuleis a tiny Python object whose only job is to carry a Cvoid *through the interpreter. It adds a name (so you can’t grab the wrong pointer), a destructor (so the C side can clean up), and optional GC hooks. The whole thing lives inObjects/capsule.c.
Extension modules use capsules to export a C API to other extension modules. The exporting module stores a pointer — often to a struct of function pointers — in a capsule and attaches it to a module attribute. The importing module calls PyCapsule_Import to retrieve it.
The implementation lives entirely in Objects/capsule.c.
The struct: PyCapsule
Internally a capsule is a plain struct: an object header followed by six fields.
// Objects/capsule.c
typedef struct {
PyObject_HEAD
void *pointer;
const char *name;
void *context;
PyCapsule_Destructor destructor;
traverseproc traverse_func;
inquiry clear_func;
} PyCapsule;What each field is for:
pointeris the wrappedvoid *(neverNULLfor a legal capsule).nameis an optional, conventionally dotted attribute path such as"socket.CAPI"(orNULL).contextis an arbitraryvoid *for the owner’s use.destructoris called exactly once when the capsule is deallocated.traverse_func/clear_funcare only set by_PyCapsule_SetTraverseand enable GC participation (see below).
The public type is declared in Include/pycapsule.h:
// Include/pycapsule.h
PyAPI_DATA(PyTypeObject) PyCapsule_Type;
typedef void (*PyCapsule_Destructor)(PyObject *);
#define PyCapsule_CheckExact(op) Py_IS_TYPE((op), &PyCapsule_Type)The type: PyCapsule_Type
PyCapsule_Type is a GC-capable type with tp_basicsize = sizeof(PyCapsule):
// Objects/capsule.c
PyTypeObject PyCapsule_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
.tp_name = "PyCapsule",
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_basicsize = sizeof(PyCapsule),
.tp_dealloc = capsule_dealloc,
.tp_repr = capsule_repr,
.tp_doc = PyCapsule_Type__doc__,
.tp_traverse = capsule_traverse,
.tp_clear = capsule_clear,
};The key points:
Py_TPFLAGS_HAVE_GCis set because a capsule may own references to Python objects through itscontext/pointer(via the optional traverse callbacks).- However, a freshly created capsule is not GC-tracked. Tracking is opt-in through
_PyCapsule_SetTraverse. That keeps the common case — no owned Python objects — cheap and skips unnecessary GC work. tp_cleariscapsule_clear, which only runs when traverse was registered.
Construction: PyCapsule_New
// Objects/capsule.c
PyObject *
PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor)
{
PyCapsule *capsule;
if (!pointer) {
PyErr_SetString(PyExc_ValueError, "PyCapsule_New called with null pointer");
return NULL;
}
capsule = PyObject_GC_New(PyCapsule, &PyCapsule_Type);
if (capsule == NULL) {
return NULL;
}
capsule->pointer = pointer;
capsule->name = name;
capsule->context = NULL;
capsule->destructor = destructor;
capsule->traverse_func = NULL;
capsule->clear_func = NULL;
// Only track the object by the GC when _PyCapsule_SetTraverse() is called
return (PyObject *)capsule;
}Things to note:
- A
NULLpointer is rejected up front withValueError. - The name is stored by pointer, not copied. The caller owns that string memory for the capsule’s lifetime — which is why module-level capsule names are typically string literals.
- The destructor may be
NULL, in which case nothing runs at dealloc. contextstarts asNULL.
Legality and name matching
Most accessors begin with the is_legal_capsule guard:
// Objects/capsule.c
static int
_is_legal_capsule(PyObject *op, const char *invalid_capsule)
{
if (!op || !PyCapsule_CheckExact(op)) {
goto error;
}
PyCapsule *capsule = (PyCapsule *)op;
if (capsule->pointer == NULL) {
goto error;
}
return 1;
error:
PyErr_SetString(PyExc_ValueError, invalid_capsule);
return 0;
}A capsule becomes “illegal” once its pointer is cleared (set to NULL), which lets a destructor mark it as consumed.
Name comparison is done by name_matches, which treats two NULL names as equal and otherwise uses strcmp:
// Objects/capsule.c
static int
name_matches(const char *name1, const char *name2) {
if (!name1 || !name2) {
return name1 == name2;
}
return !strcmp(name1, name2);
}PyCapsule_IsValid combines the legality and name checks:
// Objects/capsule.c
int
PyCapsule_IsValid(PyObject *op, const char *name)
{
PyCapsule *capsule = (PyCapsule *)op;
return (capsule != NULL &&
PyCapsule_CheckExact(capsule) &&
capsule->pointer != NULL &&
name_matches(capsule->name, name));
}The name is a safety tag
The name is a const char *, and it’s compared (not copied) by every getter that takes a name argument.
PyCapsule_GetPointer requires the supplied name to match the stored one:
// Objects/capsule.c
void *
PyCapsule_GetPointer(PyObject *op, const char *name)
{
if (!is_legal_capsule(op, "PyCapsule_GetPointer")) {
return NULL;
}
PyCapsule *capsule = (PyCapsule *)op;
if (!name_matches(name, capsule->name)) {
PyErr_SetString(PyExc_ValueError, "PyCapsule_GetPointer called with incorrect name");
return NULL;
}
return capsule->pointer;
}PyCapsule_GetName / PyCapsule_SetName read and overwrite the pointer (again, no copy):
// Objects/capsule.c
const char *
PyCapsule_GetName(PyObject *op)
{
if (!is_legal_capsule(op, "PyCapsule_GetName")) {
return NULL;
}
PyCapsule *capsule = (PyCapsule *)op;
return capsule->name;
}
int
PyCapsule_SetName(PyObject *op, const char *name)
{
if (!is_legal_capsule(op, "PyCapsule_SetName")) {
return -1;
}
PyCapsule *capsule = (PyCapsule *)op;
capsule->name = name;
return 0;
}Here’s the thing: the name’s purpose is purely defensive. It guards against accidentally retrieving a pointer from the wrong capsule, and it’s the key used by PyCapsule_Import.
The destructor gets one shot
The destructor is a PyCapsule_Destructor (void (*)(PyObject *)). It’s invoked from capsule_dealloc before the object memory is freed:
// Objects/capsule.c
static void
capsule_dealloc(PyObject *op)
{
PyCapsule *capsule = _PyCapsule_CAST(op);
PyObject_GC_UnTrack(op);
if (capsule->destructor) {
capsule->destructor(op);
}
PyObject_GC_Del(op);
}Important details:
PyObject_GC_UnTrackruns first, so the object is out of the GC list before the destructor (which may manipulate other objects) runs.- The destructor receives the capsule object itself, so it can call
PyCapsule_GetPointer/GetContextto recover its payload. - The destructor is responsible for releasing whatever the pointer owns (e.g.
PyMem_Free,Py_DECREFon referenced types).capsule_deallocthen frees the capsule struct viaPyObject_GC_Del. PyCapsule_GetDestructor/PyCapsule_SetDestructorexpose the field:
// Objects/capsule.c
PyCapsule_Destructor
PyCapsule_GetDestructor(PyObject *op)
{
if (!is_legal_capsule(op, "PyCapsule_GetDestructor")) {
return NULL;
}
PyCapsule *capsule = (PyCapsule *)op;
return capsule->destructor;
}
int
PyCapsule_SetDestructor(PyObject *op, PyCapsule_Destructor destructor)
{
if (!is_legal_capsule(op, "PyCapsule_SetDestructor")) {
return -1;
}
PyCapsule *capsule = (PyCapsule *)op;
capsule->destructor = destructor;
return 0;
}A common pattern is to clear the pointer inside the destructor, marking the capsule consumed:
static void my_destructor(PyObject *capsule) {
void *p = PyCapsule_GetPointer(capsule, "my.module.API");
/* release p ... */
PyCapsule_SetPointer(capsule, NULL); /* mark consumed */
}Context: the owner’s scratchpad
context is a free-form void * slot, accessed via:
// Objects/capsule.c
void *
PyCapsule_GetContext(PyObject *op)
{
if (!is_legal_capsule(op, "PyCapsule_GetContext")) {
return NULL;
}
PyCapsule *capsule = (PyCapsule *)op;
return capsule->context;
}
int
PyCapsule_SetContext(PyObject *op, void *context)
{
if (!is_legal_capsule(op, "PyCapsule_SetContext")) {
return -1;
}
PyCapsule *capsule = (PyCapsule *)op;
capsule->context = context;
return 0;
}Unlike pointer, context may be NULL and isn’t subject to the legality check’s non-null requirement.
It’s typically used to stash auxiliary data the destructor or traverse callback needs (for example, a reference to an owning object), since the destructor only receives the capsule.
Optional GC traversal
By default a capsule owns no Python references as far as the GC is concerned.
When the wrapped pointer does refer to Python objects (say, a C API struct holding PyObject * fields), the owner can register traverse/clear callbacks:
// Objects/capsule.c
int
_PyCapsule_SetTraverse(PyObject *op, traverseproc traverse_func, inquiry clear_func)
{
if (!is_legal_capsule(op, "_PyCapsule_SetTraverse")) {
return -1;
}
PyCapsule *capsule = (PyCapsule *)op;
if (traverse_func == NULL || clear_func == NULL) {
PyErr_SetString(PyExc_ValueError,
"_PyCapsule_SetTraverse() called with NULL callback");
return -1;
}
if (!_PyObject_GC_IS_TRACKED(op)) {
_PyObject_GC_TRACK(op);
}
capsule->traverse_func = traverse_func;
capsule->clear_func = clear_func;
return 0;
}This is an internal API (declared in Include/internal/pycore_capsule.h), used by modules such as socket and _curses:
// Modules/socketmodule.c
PyObject *capsule = PyCapsule_New(capi, PySocket_CAPSULE_NAME, sock_capi_destroy);
if (_PyCapsule_SetTraverse(capsule,
sock_capi_traverse, sock_capi_clear) < 0) { ... }The callbacks receive the capsule and use PyCapsule_GetPointer to reach the wrapped struct, then Py_VISIT/Py_CLEAR its Python members:
// Modules/socketmodule.c
static int
sock_capi_traverse(PyObject *capsule, visitproc visit, void *arg)
{
PySocketModule_APIObject *capi = PyCapsule_GetPointer(capsule, PySocket_CAPSULE_NAME);
assert(capi != NULL);
Py_VISIT(capi->Sock_Type);
return 0;
}
static int
sock_capi_clear(PyObject *capsule)
{
PySocketModule_APIObject *capi = PyCapsule_GetPointer(capsule, PySocket_CAPSULE_NAME);
assert(capi != NULL);
Py_CLEAR(capi->Sock_Type);
return 0;
}The generic traverse/clear in capsule.c just dispatch to the registered callbacks (and capsule_clear asserts that a clear callback exists):
// Objects/capsule.c
static int
capsule_traverse(PyObject *self, visitproc visit, void *arg)
{
PyCapsule *capsule = _PyCapsule_CAST(self);
if (capsule->traverse_func != NULL) {
return capsule->traverse_func(self, visit, arg);
}
return 0;
}
static int
capsule_clear(PyObject *self)
{
PyCapsule *capsule = _PyCapsule_CAST(self);
assert(capsule->clear_func != NULL);
return capsule->clear_func(self);
}PyCapsule_Import: the headline use case
This is how one extension module imports a capsule from another module’s attribute:
// Objects/capsule.c
void *
PyCapsule_Import(const char *name, int no_block)
{
PyObject *object = NULL;
void *return_value = NULL;
char *trace;
size_t name_length = (strlen(name) + 1) * sizeof(char);
char *name_dup = (char *)PyMem_Malloc(name_length);
...
memcpy(name_dup, name, name_length);
trace = name_dup;
while (trace) {
char *dot = strchr(trace, '.');
if (dot) {
*dot++ = '\0';
}
if (object == NULL) {
object = PyImport_ImportModule(trace);
...
} else {
PyObject *object2 = PyObject_GetAttrString(object, trace);
Py_SETREF(object, object2);
}
...
trace = dot;
}
if (PyCapsule_IsValid(object, name)) {
PyCapsule *capsule = (PyCapsule *)object;
return_value = capsule->pointer;
} else {
PyErr_Format(PyExc_AttributeError,
"PyCapsule_Import \"%s\" is not valid", name);
}
EXIT:
Py_XDECREF(object);
if (name_dup) {
PyMem_Free(name_dup);
}
return return_value;
}It walks the dotted name ("module.sub.attr"), importing the first segment and then getattr-ing the rest, and finally validates that the resolved object is a capsule whose name matches the full name.
The no_block parameter is accepted for API compatibility, but the implementation imports normally.
On success it returns the borrowed pointer — the capsule (and thus the pointer) stays alive as long as the module holds it.
Repr
// Objects/capsule.c
static PyObject *
capsule_repr(PyObject *o)
{
PyCapsule *capsule = _PyCapsule_CAST(o);
const char *name;
const char *quote;
if (capsule->name) {
quote = "\"";
name = capsule->name;
} else {
quote = "";
name = "NULL";
}
return PyUnicode_FromFormat("<capsule object %s%s%s at %p>",
quote, name, quote, capsule);
}So repr(capsule) looks like <capsule object "socket.CAPI" at 0x...> or <capsule object NULL at 0x...>.
The memory layout
PyCapsule (sizeof = 7 words on 64-bit)
+----------------------------+
| ob_refcnt |
+----------------------------+
| ob_flags |
+----------------------------+
| ob_type -> &PyCapsule_Type|
+----------------------------+
| pointer (void *) |
+----------------------------+
| name (const char *) |
+----------------------------+
| context (void *) |
+----------------------------+
| destructor |
+----------------------------+
| traverse_func |
+----------------------------+
| clear_func |
+----------------------------+Key takeaways
- A
PyCapsuleis a GC-capable object wrapping avoid *, plus a name, context, destructor, and optional traverse/clear callbacks. PyCapsule_NewrejectsNULLpointers; the name is stored by pointer (not copied) andcontextstartsNULL.- Legality requires a non-null
pointer; setting it toNULL(often in the destructor) marks the capsule consumed. - Names are compared with
strcmp(withNULL == NULL), and getters likePyCapsule_GetPointerrequire a matching name. - The destructor runs during dealloc, before
PyObject_GC_Del, and receives the capsule so it can recover its payload. contextis a free-form slot not subject to the non-null legality check.- GC tracking is opt-in via the internal
_PyCapsule_SetTraverse; otherwise capsules are untracked. PyCapsule_Importwalks a dotted path, validates the final capsule by name, and returns its pointer.
The one thing to remember: a capsule is a typed handle, not a container. The name is a cheap tag that catches “wrong pointer”, the destructor is your one guaranteed cleanup hook, and everything else is convention.
Further reading
Objects/capsule.c–PyCapsulestruct,PyCapsule_Type,PyCapsule_New, accessors,_PyCapsule_SetTraverse,PyCapsule_Import,capsule_dealloc,capsule_repr,capsule_traverse,capsule_clear.Include/pycapsule.h– public API andPyCapsule_CheckExact.Include/internal/pycore_capsule.h–_PyCapsule_SetTraverse.Modules/socketmodule.c,Modules/_cursesmodule.c– real users of traverse.Lib/test/test_capi/test_capsule.py– capsule test suite.Doc/c-api/capsule.rst– public documentation.