Portage des modules d’extension vers Python 3¶
- author
Benjamin Peterson
Résumé
Changer l’API C n’était pas l’un des objectifs de Python 3, cependant les nombreux changements au niveau Python ont rendu impossible de garder l’API de Python 2 comme elle était. Certains changements tels que l’unification de int()
et long()
sont plus apparents au niveau C. Ce document s’efforce de documenter les incompatibilités et la façon dont elles peuvent être contournées.
Compilation conditionnelle¶
La façon la plus simple de compiler seulement une section de code pour Python 3 est de vérifier si PY_MAJOR_VERSION
est supérieur ou égal à 3. :
#if PY_MAJOR_VERSION >= 3
#define IS_PY3K
#endif
Les fonctions manquantes dans l’API peuvent être remplacées par des alias à leurs équivalents dans des blocs conditionnels.
Modifications apportées aux API des objets¶
Python 3 a fusionné certains types avec des fonctions identiques tout en séparant de façon propre, d’autres.
Unification de str et unicode¶
Le type str()
de Python 3 est l’équivalent de unicode()
sous Python 2 ; Les fonctions C sont appelées PyUnicode_*
pour les deux versions. L’ancien type de chaîne de caractères de 8 bits est devenue bytes()
, avec des fonctions C nommées PyBytes_*
. Python 2.6 et toutes les versions supérieures fournissent un en-tête de compatibilité, bytesobject.h
, faisant correspondre les noms PyBytes
aux PyString
. Pour une meilleure compatibilité avec Python 3, PyUnicode
doit être utilisé seulement pour des données textuelles et PyBytes
pour des données binaires. Il est important de noter que PyBytes
et PyUnicode
en Python 3 ne sont pas remplaçables contrairement à PyString
et PyUnicode
dans Python 2. L’exemple suivant montre l’utilisation optimale de PyUnicode
, PyString
, et PyBytes
.
#include "stdlib.h"
#include "Python.h"
#include "bytesobject.h"
/* text example */
static PyObject *
say_hello(PyObject *self, PyObject *args) {
PyObject *name, *result;
if (!PyArg_ParseTuple(args, "U:say_hello", &name))
return NULL;
result = PyUnicode_FromFormat("Hello, %S!", name);
return result;
}
/* just a forward */
static char * do_encode(PyObject *);
/* bytes example */
static PyObject *
encode_object(PyObject *self, PyObject *args) {
char *encoded;
PyObject *result, *myobj;
if (!PyArg_ParseTuple(args, "O:encode_object", &myobj))
return NULL;
encoded = do_encode(myobj);
if (encoded == NULL)
return NULL;
result = PyBytes_FromString(encoded);
free(encoded);
return result;
}
Initialisation et état du module¶
Python 3 a remanié son système d’initialisation des modules d’extension (Voir PEP 3121.). Au lieu de stocker les états de module dans les variables globales, les états doivent être stockés dans une structure spécifique à l’interpréteur. Créer des modules qui ont un fonctionnement correct en Python 2 et Python 3 est délicat. L’exemple suivant montre comment.
#include "Python.h"
struct module_state {
PyObject *error;
};
#if PY_MAJOR_VERSION >= 3
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
#else
#define GETSTATE(m) (&_state)
static struct module_state _state;
#endif
static PyObject *
error_out(PyObject *m) {
struct module_state *st = GETSTATE(m);
PyErr_SetString(st->error, "something bad happened");
return NULL;
}
static PyMethodDef myextension_methods[] = {
{"error_out", (PyCFunction)error_out, METH_NOARGS, NULL},
{NULL, NULL}
};
#if PY_MAJOR_VERSION >= 3
static int myextension_traverse(PyObject *m, visitproc visit, void *arg) {
Py_VISIT(GETSTATE(m)->error);
return 0;
}
static int myextension_clear(PyObject *m) {
Py_CLEAR(GETSTATE(m)->error);
return 0;
}
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"myextension",
NULL,
sizeof(struct module_state),
myextension_methods,
NULL,
myextension_traverse,
myextension_clear,
NULL
};
#define INITERROR return NULL
PyMODINIT_FUNC
PyInit_myextension(void)
#else
#define INITERROR return
void
initmyextension(void)
#endif
{
#if PY_MAJOR_VERSION >= 3
PyObject *module = PyModule_Create(&moduledef);
#else
PyObject *module = Py_InitModule("myextension", myextension_methods);
#endif
if (module == NULL)
INITERROR;
struct module_state *st = GETSTATE(module);
st->error = PyErr_NewException("myextension.Error", NULL, NULL);
if (st->error == NULL) {
Py_DECREF(module);
INITERROR;
}
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
CObject remplacé par Capsule¶
L’objet Capsule
a été introduit dans Python 3.1 et 2.7 pour remplacer CObject
. Le type CObject
était utile, mais son API posait des soucis : elle ne permettait pas la distinction entre les objets C valides, ce qui permettait aux objets C assortis incorrectement de planter l’interpréteur, et certaines des API s’appuyaient sur un comportement indéfini en C. (Pour plus de détails sur la logique de Capsules, veuillez consulter bpo-5630).
Si vous utilisez actuellement CObjects et que vous voulez migrer vers la version 3.1 ou plus récente, vous devrez passer à Capsules. CObject
est déprécié dans 3.1 et 2.7 et est supprimé dans Python 3.2. Si vous ne gérez que les versions 2.7, ou 3.1 et supérieures, vous pouvez simplement passer à Capsule
. Si vous avez besoin de gérer Python 3.0, ou des versions de Python antérieures à 2.7, vous devez gérer CObjects et Capsules. (Notez que Python 3.0 n’est plus maintenu, et qu’il n’est pas recommandé pour une utilisation en production).
L’exemple suivant d’en-tête de fichier capsulethunk.h
peut résoudre le problème. Il suffit d’écrire votre code dans l’API Capsule
et d’inclure ce fichier d’en-tête après Python.h
. Votre code utilisera automatiquement Capsules dans les versions de Python avec Capsules, et passera à CObjects lorsque les Capsules ne sont pas disponibles.
capsulethunk.h
reproduit le fonctionnement de Capsules en utilisant CObjects. Cependant, CObject
ne permet pas de stocker le « nom » de la capsule. Les objets simulés Capsule
créés par capsulethunk.h
se comportent légèrement différemment des véritables Capsules. Ainsi :
Le paramètre name passé à
PyCapsule_New()
est ignoré.Le paramètre name passé à
PyCapsule_IsValid()
etPyCapsule_GetPointer()
est ignoré et il n’y a pas de vérification d’erreur du nom.
PyCapsule_GetName()
renvoie toujours un NULL.
PyCapsule_SetName()
lève toujours une exception et renvoie un échec. Note : Puisqu’il n’y a aucun moyen de stocker un nom dans un CObject, l’échec verbeux dePyCapsule_SetName()
a été jugé préférable à un échec non-verbeux dans ce cas. Si cela ne vous convenait pas, vous pouvez modifier votre copie locale selon vos besoins.
Vous pouvez trouver capsulethunk.h
dans la distribution source de Python comme Doc/includes/capsulethunk.h. Nous l’incluons ici pour votre confort :
#ifndef __CAPSULETHUNK_H
#define __CAPSULETHUNK_H
#if ( (PY_VERSION_HEX < 0x02070000) \
|| ((PY_VERSION_HEX >= 0x03000000) \
&& (PY_VERSION_HEX < 0x03010000)) )
#define __PyCapsule_GetField(capsule, field, default_value) \
( PyCapsule_CheckExact(capsule) \
? (((PyCObject *)capsule)->field) \
: (default_value) \
) \
#define __PyCapsule_SetField(capsule, field, value) \
( PyCapsule_CheckExact(capsule) \
? (((PyCObject *)capsule)->field = value), 1 \
: 0 \
) \
#define PyCapsule_Type PyCObject_Type
#define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule))
#define PyCapsule_IsValid(capsule, name) (PyCObject_Check(capsule))
#define PyCapsule_New(pointer, name, destructor) \
(PyCObject_FromVoidPtr(pointer, destructor))
#define PyCapsule_GetPointer(capsule, name) \
(PyCObject_AsVoidPtr(capsule))
/* Don't call PyCObject_SetPointer here, it fails if there's a destructor */
#define PyCapsule_SetPointer(capsule, pointer) \
__PyCapsule_SetField(capsule, cobject, pointer)
#define PyCapsule_GetDestructor(capsule) \
__PyCapsule_GetField(capsule, destructor)
#define PyCapsule_SetDestructor(capsule, dtor) \
__PyCapsule_SetField(capsule, destructor, dtor)
/*
* Sorry, there's simply no place
* to store a Capsule "name" in a CObject.
*/
#define PyCapsule_GetName(capsule) NULL
static int
PyCapsule_SetName(PyObject *capsule, const char *unused)
{
unused = unused;
PyErr_SetString(PyExc_NotImplementedError,
"can't use PyCapsule_SetName with CObjects");
return 1;
}
#define PyCapsule_GetContext(capsule) \
__PyCapsule_GetField(capsule, descr)
#define PyCapsule_SetContext(capsule, context) \
__PyCapsule_SetField(capsule, descr, context)
static 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);
if (!name_dup) {
return NULL;
}
memcpy(name_dup, name, name_length);
trace = name_dup;
while (trace) {
char *dot = strchr(trace, '.');
if (dot) {
*dot++ = '\0';
}
if (object == NULL) {
if (no_block) {
object = PyImport_ImportModuleNoBlock(trace);
} else {
object = PyImport_ImportModule(trace);
if (!object) {
PyErr_Format(PyExc_ImportError,
"PyCapsule_Import could not "
"import module \"%s\"", trace);
}
}
} else {
PyObject *object2 = PyObject_GetAttrString(object, trace);
Py_DECREF(object);
object = object2;
}
if (!object) {
goto EXIT;
}
trace = dot;
}
if (PyCObject_Check(object)) {
PyCObject *cobject = (PyCObject *)object;
return_value = cobject->cobject;
} 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;
}
#endif /* #if PY_VERSION_HEX < 0x02070000 */
#endif /* __CAPSULETHUNK_H */