Framework to build your own C Extension for Python

Goal

This article explains, through an example, how to structure a Python C extension module using multi-phase initialization.

In Python, there are two ways to create a C extension module:

  • Single phase intialization
  • Multi-phase intialization


This article focuses on multi-phase initialization, introduced in Python 3.5 by PEP 489.

Single-phase initialization is still supported, but multi-phase initialization is recommended for new modules.

Code Part (Step by Step)

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string.h>

ℹ️️#define PY_SSIZE_T_CLEAN

PY_SSIZE_T_CLEAN macro is no longer required since Python 3.13. But might be kept, just for backward compatibility.

It was required for Python 3.12 and earlier when using argument-parsing formats that return a size, such as “s#”, “y#”, “z#”, “u#”, “es#” and “et#” (such format is used in function PyArg_ParseTupleAndKeywords() from CPython, used to retrieve arguments in C function in your module).

These formats provide both a pointer to the object data and its length. Without this macro, the length was expected as an int in C. With the macro,it had to be a Py_ssize_t.

Py_ssize_t is a signed integer type with the same size as size_t. Unlike size_t, it can represent negative values, including -1, which is commonly used by the Python/C API as an error indicator.

ℹ️️ #include

Python.h provides the functions, macros and structures needed to interact with the Python interpreter and to initialize a C extension module.

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string.h>

/* ------------------------------------------------------------------ */
/* 1. Module State                                                   */
/* ------------------------------------------------------------------ */

typedef struct {
    long counter;
    char* message;
} module_state;

/* ------------------------------------------------------------------ */
/* 2. Fonctions exposées à Python                                      */
/* ------------------------------------------------------------------ */

static PyObject*
add(PyObject* self, PyObject* args)
{
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    return PyLong_FromLong(a + b);
}

static PyObject*
increment_counter(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    state->counter++;

    return PyLong_FromLong(state->counter);
}

static PyObject*
get_counter(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    return PyLong_FromLong(state->counter);
}

static PyObject*
set_message(PyObject* self, PyObject* args)
{
    const char* text;
    module_state* state;
    char* copy;

    if (!PyArg_ParseTuple(args, "s", &text)) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    copy = PyMem_Malloc(strlen(text) + 1);

    if (copy == NULL) {
        PyErr_NoMemory();
        return NULL;
    }

    strcpy(copy, text);

    PyMem_Free(state->message);
    state->message = copy;

    Py_RETURN_NONE;
}

static PyObject*
get_message(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    if (state->message == NULL) {
        Py_RETURN_NONE;
    }

    return PyUnicode_FromString(state->message);
}

static PyObject*
divide(PyObject* self, PyObject* args)
{
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    if (b == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
        return NULL;
    }

    return PyFloat_FromDouble((double)a / (double)b);
}

/* ------------------------------------------------------------------ */
/* 3. Table des méthodes                                               */
/* ------------------------------------------------------------------ */

static PyMethodDef mymodule_methods[] = {
    {"add", add, METH_VARARGS,
     "add(a, b) -> int\n\nReturn the sum of two integers."},

    {"divide", divide, METH_VARARGS,
     "divide(a, b) -> float\n\nReturn a divided by b."},

    {"increment_counter", increment_counter, METH_VARARGS,
     "increment_counter() -> int\n\nIncrement and return the module counter."},

    {"get_counter", get_counter, METH_VARARGS,
     "get_counter() -> int\n\nReturn the current module counter."},

    {"set_message", set_message, METH_VARARGS,
     "set_message(text) -> None\n\nStore a message in the module state."},

    {"get_message", get_message, METH_VARARGS,
     "get_message() -> str or None\n\nReturn the stored message."},

    {NULL, NULL, 0, NULL}
};

/* ------------------------------------------------------------------ */
/* 4. Slot Py_mod_exec : initialisation du module                      */
/* ------------------------------------------------------------------ */

static int
mymodule_exec(PyObject* module)
{
    module_state* state;
    PyObject* my_error;

    /* Récupère l’état alloué par CPython grâce à m_size. */
    state = (module_state*)PyModule_GetState(module);

    if (state == NULL) {
        return -1;
    }

    /* Initialisation explicite de l’état. */
    state->counter = 0;
    state->message = NULL;

    /* Ajout des fonctions. */
    if (PyModule_AddFunctions(module, mymodule_methods) < 0) {
        return -1;
    }

    /* Ajout d’une constante. */
    if (PyModule_AddIntConstant(module, "VERSION", 1) < 0) {
        return -1;
    }

    /* Création et ajout d’une exception personnalisée. */
    my_error = PyErr_NewException("mymodule.MyError", NULL, NULL);

    if (my_error == NULL) {
        return -1;
    }

    if (PyModule_AddObjectRef(module, "MyError", my_error) < 0) {
        Py_DECREF(my_error);
        return -1;
    }

    Py_DECREF(my_error);

    return 0;
}

/* ------------------------------------------------------------------ */
/* 5. Libération de l’état                                             */
/* ------------------------------------------------------------------ */

static void
mymodule_free(void* raw_state)
{
    module_state* state = (module_state*)raw_state;

    if (state != NULL) {
        PyMem_Free(state->message);
        state->message = NULL;
    }
}

/* ------------------------------------------------------------------ */
/* 6. Définition multi-phase du module                                 */
/* ------------------------------------------------------------------ */

static struct PyModuleDef mymodule_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "mymodule",
    .m_doc = "Exemple complet de module Python en C avec initialisation multi-phase.",
    .m_size = sizeof(module_state),
    .m_methods = mymodule_methods,
    .m_slots = (PyModuleDef_Slot[]) {
        {Py_mod_exec, mymodule_exec},
        {0, NULL}
    },
    .m_free = mymodule_free
};

/* ------------------------------------------------------------------ */
/* 7. Point d’entrée obligatoire                                       */
/* ------------------------------------------------------------------ */

PyMODINIT_FUNC
PyInit_mymodule(void)
{
    return PyModuleDef_Init(&mymodule_module);
}</kbd>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string.h>

/* ------------------------------------------------------------------ */
/* 1. État du module                                                   */
/* ------------------------------------------------------------------ */

typedef struct {
    long counter;
    char* message;
} module_state;

/* ------------------------------------------------------------------ */
/* 2. Fonctions exposées à Python                                      */
/* ------------------------------------------------------------------ */

static PyObject*
add(PyObject* self, PyObject* args)
{
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    return PyLong_FromLong(a + b);
}

static PyObject*
increment_counter(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    state->counter++;

    return PyLong_FromLong(state->counter);
}

static PyObject*
get_counter(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    return PyLong_FromLong(state->counter);
}

static PyObject*
set_message(PyObject* self, PyObject* args)
{
    const char* text;
    module_state* state;
    char* copy;

    if (!PyArg_ParseTuple(args, "s", &text)) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    copy = PyMem_Malloc(strlen(text) + 1);

    if (copy == NULL) {
        PyErr_NoMemory();
        return NULL;
    }

    strcpy(copy, text);

    PyMem_Free(state->message);
    state->message = copy;

    Py_RETURN_NONE;
}

static PyObject*
get_message(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    if (state->message == NULL) {
        Py_RETURN_NONE;
    }

    return PyUnicode_FromString(state->message);
}

static PyObject*
divide(PyObject* self, PyObject* args)
{
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    if (b == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
        return NULL;
    }

    return PyFloat_FromDouble((double)a / (double)b);
}

/* ------------------------------------------------------------------ */
/* 3. Table des méthodes                                               */
/* ------------------------------------------------------------------ */

static PyMethodDef mymodule_methods[] = {
    {"add", add, METH_VARARGS,
     "add(a, b) -> int\n\nReturn the sum of two integers."},

    {"divide", divide, METH_VARARGS,
     "divide(a, b) -> float\n\nReturn a divided by b."},

    {"increment_counter", increment_counter, METH_VARARGS,
     "increment_counter() -> int\n\nIncrement and return the module counter."},

    {"get_counter", get_counter, METH_VARARGS,
     "get_counter() -> int\n\nReturn the current module counter."},

    {"set_message", set_message, METH_VARARGS,
     "set_message(text) -> None\n\nStore a message in the module state."},

    {"get_message", get_message, METH_VARARGS,
     "get_message() -> str or None\n\nReturn the stored message."},

    {NULL, NULL, 0, NULL}
};

/* ------------------------------------------------------------------ */
/* 4. Slot Py_mod_exec : initialisation du module                      */
/* ------------------------------------------------------------------ */

static int
mymodule_exec(PyObject* module)
{
    module_state* state;
    PyObject* my_error;

    /* Récupère l’état alloué par CPython grâce à m_size. */
    state = (module_state*)PyModule_GetState(module);

    if (state == NULL) {
        return -1;
    }

    /* Initialisation explicite de l’état. */
    state->counter = 0;
    state->message = NULL;

    /* Ajout des fonctions. */
    if (PyModule_AddFunctions(module, mymodule_methods) < 0) {
        return -1;
    }

    /* Ajout d’une constante. */
    if (PyModule_AddIntConstant(module, "VERSION", 1) < 0) {
        return -1;
    }

    /* Création et ajout d’une exception personnalisée. */
    my_error = PyErr_NewException("mymodule.MyError", NULL, NULL);

    if (my_error == NULL) {
        return -1;
    }

    if (PyModule_AddObjectRef(module, "MyError", my_error) < 0) {
        Py_DECREF(my_error);
        return -1;
    }

    Py_DECREF(my_error);

    return 0;
}

/* ------------------------------------------------------------------ */
/* 5. Libération de l’état                                             */
/* ------------------------------------------------------------------ */

static void
mymodule_free(void* raw_state)
{
    module_state* state = (module_state*)raw_state;

    if (state != NULL) {
        PyMem_Free(state->message);
        state->message = NULL;
    }
}

/* ------------------------------------------------------------------ */
/* 6. Définition multi-phase du module                                 */
/* ------------------------------------------------------------------ */

static struct PyModuleDef mymodule_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "mymodule",
    .m_doc = "Exemple complet de module Python en C avec initialisation multi-phase.",
    .m_size = sizeof(module_state),
    .m_methods = mymodule_methods,
    .m_slots = (PyModuleDef_Slot[]) {
        {Py_mod_exec, mymodule_exec},
        {0, NULL}
    },
    .m_free = mymodule_free
};

/* ------------------------------------------------------------------ */
/* 7. Point d’entrée obligatoire                                       */
/* ------------------------------------------------------------------ */

PyMODINIT_FUNC
PyInit_mymodule(void)
{
    return PyModuleDef_Init(&mymodule_module);
}</kbd>

Start with source code.
There is 2 method : single phase and multiphase.

The single phase method is not anymore recommended (is it obsolete ? Not sure yet) ==> Why not to build a program to migrate source code to single phase to multiphase . Maybe it is possible ? but it is another subject.


Focus ont the source code : here is a generic template . Let start by practice the code to understand what to do.

Et dans le cœur de Python :

  • Include/pyport.h contient le commentaire : “Now PY_SSIZE_T_CLEAN is mandatory. This is just for backward compatibility.”
  • et il définit aussi : typedef Py_ssize_t Py_ssize_clean_t;

Donc la règle pratique est :

  • avant Python 3.13 : le définir avant #include <Python.h> pour utiliser les APIs avec #
  • à partir de Python 3.13 : ce n’est plus strictement nécessaire, mais on le garde pour compatibilité

En bref, PY_SSIZE_T_CLEAN sert à garantir le bon type Py_ssize_t dans les bindings C/extension, et le dépôt le montre surtout dans la documentation et les exemples, pas dans le cœur du header principal.

#ifdef PY_SSIZE_T_CLEAN is a compile-time guard used by CPython’s C API to switch some functions to the Py_ssize_t-based variants.

In practice, the common pattern is:

C

#define PY_SSIZE_T_CLEAN
#include <Python.h>

Then APIs that take a length/count or a buffer size use Py_ssize_t instead of int for certain format units such as:

  • s#
  • y#
  • z#
  • and similar #-based argument parsing formats

Example:

C

char *buf;
Py_ssize_t len;

PyArg_ParseTuple(args, "s#", &buf, &len);

Without PY_SSIZE_T_CLEAN, some older Python versions could treat the length as int instead of Py_ssize_t, which is not ideal for large sizes and is a source of ABI/API mismatch problems.

Important CPython notes:

  • On Python 3.12 and older, this macro is required to use the # variants safely.
  • On Python 3.13 and later, it is no longer necessary in most cases.
  • In newer CPython code, this is considered a compatibility macro: the runtime still supports it, but modern code mostly uses the direct Py_ssize_t-aware APIs without needing it.

You can also see this in CPython docs:

  • Doc/c-api/arg.rst says it must be defined on Python 3.12 and older for all # format variants.
  • Include/pyport.h says this is now effectively kept only for backward compatibility.

So in short:

  • PY_SSIZE_T_CLEAN = “use Py_ssize_t for size/count-like C API arguments”
  • It is mainly relevant to argument parsing and buffer APIs
  • It is not a runtime flag, just a macro that changes the C API contract at compile time

If you want, I can also show exactly which C API functions in CPython are affected and how to use Py_ssize_t correctly in an extension module.


Py_ssize_t n’est pas défini en fonction de PY_SSIZE_T_CLEAN. Il est défini dans Include/pyport.h. La macro PY_SSIZE_T_CLEAN change plutôt quelle fonction de parsing est utilisée, et donc le type attendu pour les longueurs des formats # comme s# ou y#.huggingface

Définition de Py_ssize_t

Dans Include/pyport.h :

ctypedef ssize_t Py_ssize_t;

lorsque la plateforme fournit ssize_t. Sinon, CPython choisit un type entier signé ayant la même taille que size_t :

csizeof(Py_ssize_t) == sizeof(size_t)

Donc :

textPy_ssize_t : toujours défini dans Include/pyport.h
PY_SSIZE_T_CLEAN : contrôle le comportement des formats "#"

Où agit PY_SSIZE_T_CLEAN

La macro est utilisée dans Include/modsupport.h. Elle redirige les fonctions de parsing vers leurs variantes _SizeT :

c#ifdef PY_SSIZE_T_CLEAN
#define PyArg_Parse                 _PyArg_Parse_SizeT
#define PyArg_ParseTuple            _PyArg_ParseTuple_SizeT
#define PyArg_ParseTupleAndKeywords _PyArg_ParseTupleAndKeywords_SizeT
#define Py_VaBuildValue             _Py_VaBuildValue_SizeT
#endif

Donc, avec :

c#define PY_SSIZE_T_CLEAN
#include <Python.h>

ce code :

cPyArg_ParseTuple(args, "y#", &data, &length);

appelle en réalité :

c_PyArg_ParseTuple_SizeT(args, "y#", &data, &length);

Effet sur les formats #

Macro définie ?Fonction appeléeType attendu pour s#, y#, etc.
NonPyArg_ParseTupleint
Oui_PyArg_ParseTuple_SizeTPy_ssize_t

huggingface+1

Exemple :

c#define PY_SSIZE_T_CLEAN
#include <Python.h>

const char *data;
Py_ssize_t length;

PyArg_ParseTuple(args, "y#", &data, &length);

Ici, length doit être un Py_ssize_t.cmi.ac+1

Où est implémenté le comportement

L’implémentation est dans :

textPython/getargs.c

notamment dans les fonctions de conversion comme convertsimple(). C’est là que CPython vérifie, pour les anciennes versions, que la macro a bien été définie avant d’accepter un format # :

c#define REQUIRE_PY_SSIZE_T_CLEAN \
if (!(flags & FLAG_SIZE_T)) { \
    PyErr_SetString(PyExc_SystemError, \
        "PY_SSIZE_T_CLEAN macro must be defined for '#' formats"); \
    return NULL; \
}

Résumé des fichiers

Fichier CPythonRôle
Include/pyport.hDéfinit Py_ssize_t, PY_SSIZE_T_MAX, PY_SSIZE_T_MIN
Include/Python.hInclut pyport.h, modsupport.h, etc.
Include/modsupport.hUtilise PY_SSIZE_T_CLEAN pour rediriger vers les variantes _SizeT
Python/getargs.cImplémente le parsing et le traitement des formats #
Python/modsupport.cImplémente notamment Py_BuildValue()

huggingface+1

Depuis Python 3.13

Depuis Python 3.13, PY_SSIZE_T_CLEAN n’est plus nécessaire pour les formats # : Py_ssize_t est désormais le comportement standard. La macro reste recommandée dans le code source pour compatibilité avec les versions antérieures.github

En une phrase

Py_ssize_t est défini dans Include/pyport.h indépendamment de PY_SSIZE_T_CLEAN ; cette macro, traitée dans Include/modsupport.h et Python/getargs.c, sélectionne les variantes _SizeT de PyArg_Parse*() afin que les formats # attendent un Py_ssize_t plutôt qu’un int.

Code C
#define PY_SSIZE_T_CLEAN
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string.h>

/* ------------------------------------------------------------------ */
/* 1. État du module                                                   */
/* ------------------------------------------------------------------ */

typedef struct {
    long counter;
    char* message;
} module_state;

/* ------------------------------------------------------------------ */
/* 2. Fonctions exposées à Python                                      */
/* ------------------------------------------------------------------ */

static PyObject*
add(PyObject* self, PyObject* args)
{
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    return PyLong_FromLong(a + b);
}

static PyObject*
increment_counter(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    state->counter++;

    return PyLong_FromLong(state->counter);
}

static PyObject*
get_counter(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    return PyLong_FromLong(state->counter);
}

static PyObject*
set_message(PyObject* self, PyObject* args)
{
    const char* text;
    module_state* state;
    char* copy;

    if (!PyArg_ParseTuple(args, "s", &text)) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    copy = PyMem_Malloc(strlen(text) + 1);

    if (copy == NULL) {
        PyErr_NoMemory();
        return NULL;
    }

    strcpy(copy, text);

    PyMem_Free(state->message);
    state->message = copy;

    Py_RETURN_NONE;
}

static PyObject*
get_message(PyObject* self, PyObject* args)
{
    module_state* state;

    if (!PyArg_ParseTuple(args, "")) {
        return NULL;
    }

    state = (module_state*)PyModule_GetState(self);

    if (state == NULL) {
        return NULL;
    }

    if (state->message == NULL) {
        Py_RETURN_NONE;
    }

    return PyUnicode_FromString(state->message);
}

static PyObject*
divide(PyObject* self, PyObject* args)
{
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b)) {
        return NULL;
    }

    if (b == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
        return NULL;
    }

    return PyFloat_FromDouble((double)a / (double)b);
}

/* ------------------------------------------------------------------ */
/* 3. Table des méthodes                                               */
/* ------------------------------------------------------------------ */

static PyMethodDef mymodule_methods[] = {
    {"add", add, METH_VARARGS,
     "add(a, b) -> int\n\nReturn the sum of two integers."},

    {"divide", divide, METH_VARARGS,
     "divide(a, b) -> float\n\nReturn a divided by b."},

    {"increment_counter", increment_counter, METH_VARARGS,
     "increment_counter() -> int\n\nIncrement and return the module counter."},

    {"get_counter", get_counter, METH_VARARGS,
     "get_counter() -> int\n\nReturn the current module counter."},

    {"set_message", set_message, METH_VARARGS,
     "set_message(text) -> None\n\nStore a message in the module state."},

    {"get_message", get_message, METH_VARARGS,
     "get_message() -> str or None\n\nReturn the stored message."},

    {NULL, NULL, 0, NULL}
};

/* ------------------------------------------------------------------ */
/* 4. Slot Py_mod_exec : initialisation du module                      */
/* ------------------------------------------------------------------ */

static int
mymodule_exec(PyObject* module)
{
    module_state* state;
    PyObject* my_error;

    /* Récupère l’état alloué par CPython grâce à m_size. */
    state = (module_state*)PyModule_GetState(module);

    if (state == NULL) {
        return -1;
    }

    /* Initialisation explicite de l’état. */
    state->counter = 0;
    state->message = NULL;

    /* Ajout des fonctions. */
    if (PyModule_AddFunctions(module, mymodule_methods) < 0) {
        return -1;
    }

    /* Ajout d’une constante. */
    if (PyModule_AddIntConstant(module, "VERSION", 1) < 0) {
        return -1;
    }

    /* Création et ajout d’une exception personnalisée. */
    my_error = PyErr_NewException("mymodule.MyError", NULL, NULL);

    if (my_error == NULL) {
        return -1;
    }

    if (PyModule_AddObjectRef(module, "MyError", my_error) < 0) {
        Py_DECREF(my_error);
        return -1;
    }

    Py_DECREF(my_error);

    return 0;
}

/* ------------------------------------------------------------------ */
/* 5. Libération de l’état                                             */
/* ------------------------------------------------------------------ */

static void
mymodule_free(void* raw_state)
{
    module_state* state = (module_state*)raw_state;

    if (state != NULL) {
        PyMem_Free(state->message);
        state->message = NULL;
    }
}

/* ------------------------------------------------------------------ */
/* 6. Définition multi-phase du module                                 */
/* ------------------------------------------------------------------ */

static struct PyModuleDef mymodule_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "mymodule",
    .m_doc = "Exemple complet de module Python en C avec initialisation multi-phase.",
    .m_size = sizeof(module_state),
    .m_methods = mymodule_methods,
    .m_slots = (PyModuleDef_Slot[]) {
        {Py_mod_exec, mymodule_exec},
        {0, NULL}
    },
    .m_free = mymodule_free
};

/* ------------------------------------------------------------------ */
/* 7. Point d’entrée obligatoire                                       */
/* ------------------------------------------------------------------ */

PyMODINIT_FUNC
PyInit_mymodule(void)
{
    return PyModuleDef_Init(&mymodule_module);
}</kbd>


External links to go further