Содержание страницы
Перенос модулей расширения на Python 3¶Porting Extension Modules to Python 3
- автор
Benjamin Peterson
Аннотация
Хотя изменение C-API не было одной из целей Python 3,
многочисленные изменения на уровне Python сделали невозможным сохранение API Python 2 нетронутым.
На самом деле, некоторые изменения, такие как унификация int() и
long(), более очевидны на уровне C. Этот
документ стремится описать несовместимости и способы их
обхода.
Условная компиляция¶Conditional compilation
Самый простой способ скомпилировать только часть кода для Python 3 – проверить,
что PY_MAJOR_VERSION больше или равно 3.
#if PY_MAJOR_VERSION >= 3
#define IS_PY3K
#endif
Отсутствующие функции API можно заменить псевдонимами на их эквиваленты в условных блоках.
Изменения в API объектов¶Changes to Object APIs
Python 3 объединил некоторые типы со схожими функциями, чётко разделив другие.
Унификация str/unicode¶str/unicode Unification
Тип str() в Python 3 эквивалентен типу unicode() в Python 2; для обоих используются C-функции PyUnicode_*. Старый 8-битный строковый тип стал
bytes() с C-функциями PyBytes_*. Python 2.6 и выше предоставляют заголовочный файл совместимости
bytesobject.h, отображающий имена PyBytes на PyString. Для наилучшей
совместимости с Python 3 PyUnicode следует использовать для текстовых данных, а
PyBytes для двоичных. Также важно помнить, что
PyBytes и PyUnicode в Python 3 не взаимозаменяемы, как
PyString и PyUnicode в Python 2. Следующий пример
демонстрирует лучшие практики в отношении PyUnicode, PyString,
и PyBytes.
#include "stdlib.h"
#include "Python.h"
#include "bytesobject.h"
/* пример текста */
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;
}
/* просто пересылка */
static char * do_encode(PyObject *);
/* пример с байтами */
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;
}
Инициализация и состояние модуля¶Module initialization and state
В Python 3 переработана система инициализации модулей расширения. (См. PEP 3121.) Вместо хранения состояния модуля в глобальных переменных его следует хранить в структуре, специфичной для интерпретатора. Создание модулей, корректно работающих как в Python 2, так и в Python 3, нетривиально. Следующий простой пример показывает, как это сделать.
#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 заменён на Capsule¶CObject replaced with Capsule
Объект Capsule был представлен в Python 3.1 и 2.7 для замены
CObject. Объекты CObject были полезны,
но API CObject был проблематичным: он не позволял различать
корректные CObject, что при несоответствии CObject приводило к аварийному
завершению интерпретатора, а некоторые его API полагались на неопределённое поведение в C.
(Для дополнительного чтения о причинах появления капсул см.
bpo-5630.)
Если вы в настоящее время используете CObject и хотите перейти на Python 3.1 или новее,
вам нужно переключиться на капсулы.
CObject был объявлен устаревшим в Python 3.1 и 2.7 и полностью удалён в
Python 3.2. Если вы поддерживаете только Python 2.7 или Python 3.1 и выше,
вы можете просто переключиться на Capsule. Если вам нужно поддерживать Python 3.0
или версии Python, предшествующие 2.7,
вам придётся поддерживать и CObject, и капсулы.
(Обратите внимание, что Python 3.0 больше не поддерживается и не рекомендуется
для использования в производственной среде.)
Следующий пример заголовочного файла capsulethunk.h может
решить проблему. Просто пишите свой код, используя
API Capsule, и включайте этот заголовочный файл после
Python.h. Ваш код будет автоматически использовать капсулы
в версиях Python, где они есть, и переключаться на CObject,
когда капсулы недоступны.
capsulethunk.h имитирует капсулы с помощью CObject. Однако,
CObject не предоставляет места для хранения «имени» капсулы. В результате
имитируемые объекты Capsule, создаваемые capsulethunk.h,
ведут себя немного иначе, чем настоящие капсулы. В частности:
Параметр name, передаваемый в
PyCapsule_New(), игнорируется.Параметр name, передаваемый в
PyCapsule_IsValid()иPyCapsule_GetPointer(), игнорируется, и проверка имени на ошибки не выполняется.
PyCapsule_GetName()всегда возвращает NULL.
PyCapsule_SetName()всегда возбуждает исключение и возвращает признак ошибки. (Поскольку нет способа сохранить имя в CObject, было решено, что явное сообщение об ошибке приPyCapsule_SetName()здесь предпочтительнее молчаливого отказа. Если это неудобно, вы можете изменить свою локальную копию по своему усмотрению.)
Вы можете найти capsulethunk.h в дистрибутиве исходных кодов Python
как Doc/includes/capsulethunk.h. Мы также включили его сюда для
вашего удобства:
#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))
/* Не вызывайте PyCObject_SetPointer здесь, он завершится ошибкой, если есть деструктор */
#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)
/*
* Извините, места просто нет
* для хранения имени "name" Capsule в 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 */