Commençons par le code source.
Il existe deux méthodes : la méthode en une seule phase et la méthode en plusieurs phases.
La méthode en une seule phase n'est plus recommandée (est-elle obsolète ? Je n'en suis pas encore sûr) ==> Pourquoi ne pas créer un programme permettant de migrer le code source de la méthode en une seule phase vers la méthode en plusieurs phases ? C'est peut-être possible ? Mais c'est un autre sujet.
Concentrons-nous sur le code source : voici un modèle générique. Commençons par nous exercer sur le code pour comprendre comment procéder.
Et au cœur de Python :
Include/pyport.hcontient le commentaire : « Désormais, PY_SSIZE_T_CLEAN est obligatoire. Ceci est uniquement pour des raisons de compatibilité ascendante. »- et il définit également :
typedef Py_ssize_t Py_ssize_clean_t;
La règle pratique est donc la suivante :
- avant Python 3.13 : le définir avant
#include <Python.h>pour utiliser les API avec# - à partir de Python 3.13 : ce n’est plus strictement nécessaire, mais on le conserve pour des raisons de compatibilité
En bref, PY_SSIZE_T_CLEAN cela sert à garantir le bon type Py_ssize_t dans les liaisons C/extensions, et le dépôt le met surtout en avant dans la documentation et les exemples, pas dans le corps de l’en-tête principal.#ifdef PY_SSIZE_T_CLEAN est une condition de compilation utilisée par l’API C de CPython pour basculer certaines fonctions vers les Py_ssize_tvariantes basées sur .
En pratique, le modèle courant est le suivant :
C
#define PY_SSIZE_T_CLEAN
#include <Python.h>
Ainsi, les API qui prennent en paramètre une longueur/un nombre ou une taille de tampon utilisent Py_ssize_t au lieu de int pour certaines unités de format telles que :
s#y#z#- et d’autres
#formats d’analyse d’arguments basés sur
Exemple :
C
char *buf;
Py_ssize_t len;
PyArg_ParseTuple(args, "s#", &buf, &len);
Sans PY_SSIZE_T_CLEAN, certaines anciennes versions de Python pouvaient considérer la longueur comme int au lieu de Py_ssize_t, ce qui n’est pas idéal pour les grandes tailles et constitue une source de problèmes d’incompatibilité ABI/API.
Remarques importantes concernant CPython :
- Sous Python 3.12 et versions antérieures, cette macro est nécessaire pour utiliser les
#variantes en toute sécurité. - Sous Python 3.13 et versions ultérieures, elle n’est plus nécessaire dans la plupart des cas.
- Dans le code CPython plus récent, elle est considérée comme une macro de compatibilité : le moteur la prend toujours en charge, mais le code moderne utilise principalement les
Py_ssize_tsans avoir besoin d’elle.
Vous pouvez également le constater dans la documentation de CPython :
Doc/c-api/arg.rstil est indiqué qu’elle doit être définie sous Python 3.12 et versions antérieures pour toutes les#variantes de format.Include/pyport.hindique que cela n’est désormais conservé que pour des raisons de compatibilité ascendante.
En résumé :
PY_SSIZE_T_CLEAN= « utiliserPy_ssize_tpour les arguments de type taille/nombre de l’API C »- Cela concerne principalement l’analyse des arguments et les API de tampon
- Il ne s’agit pas d’un indicateur d’exécution, mais simplement d’une macro qui modifie le contrat de l’API C lors de la compilation
Si vous le souhaitez, je peux également vous indiquer précisément quelles fonctions de l’API C de CPython sont concernées et comment les utiliser Py_ssize_t correctement dans un module d’extension.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 modifie plutôt la fonction d’analyse syntaxique 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é de 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ù intervient PY_SSIZE_T_CLEAN
La macro est utilisée dans Include/modsupport.h. Elle redirige les fonctions d’analyse 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
Ainsi, 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ée | Type attendu pour s#, y#, etc. |
|---|---|---|
| Non | PyArg_ParseTuple | int |
| Oui | _PyArg_ParseTuple_SizeT | Py_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 il doit s'agir d'un Py_ssize_t.cmi.ac+1
Où est implémenté le comportement
L’implémentation se trouve dans :
textPython/getargs.c
notamment dans les fonctions de conversion telles que 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 CPython | Rôle |
|---|---|
Include/pyport.h | Définit Py_ssize_t, PY_SSIZE_T_MAX, PY_SSIZE_T_MIN |
Include/Python.h | Inclut pyport.h, modsupport.h, etc. |
Include/modsupport.h | Utilise PY_SSIZE_T_CLEAN pour rediriger vers les variantes _SizeT |
Python/getargs.c | Implémente l'analyse et le traitement des formats # |
Python/modsupport.c | Implé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 par défaut. La macro reste recommandée dans le code source pour des raisons de compatibilité avec les versions antérieures.github
En un mot
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.
#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) -> intnnReturn the sum of two integers."},
{"divide", divide, METH_VARARGS,
"divide(a, b) -> floatnnReturn a divided by b."},
{"increment_counter", increment_counter, METH_VARARGS,
"increment_counter() -> intnnIncrement and return the module counter."},
{"get_counter", get_counter, METH_VARARGS,
"get_counter() -> intnnReturn the current module counter."},
{"set_message", set_message, METH_VARARGS,
"set_message(text) -> NonennStore a message in the module state."},
{"get_message", get_message, METH_VARARGS,
"get_message() -> str or NonennReturn 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>