Документация Python неофициальный перевод
Содержание страницы

Перенос модулей расширения на Python 3Porting 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/unicodestr/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;
}

Унификация long/intlong/int Unification

В Python 3 есть только один целочисленный тип – int(). Но на самом деле он соответствует типу long() из Python 2 – тип int(), использовавшийся в Python 2, был удалён. В C-API функции PyInt_* заменены их эквивалентами PyLong_*.

Инициализация и состояние модуля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

PyObject *
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 заменён на CapsuleCObject replaced with Capsule

Объект Capsule был введён в Python 3.1 и 2.7 для замены CObject. CObject были полезны, но API CObject был проблематичным: он не позволял различать допустимые CObject, что позволяло несовпадающим CObject приводить к аварийному завершению интерпретатора, а некоторые его API полагались на неопределённое поведение в C. (Дополнительную информацию о причинах появления Capsules см. в issue 5630.)

Если в настоящее время используются CObject и требуется переход на 3.1 или новее, потребуется переключиться на Capsules. CObject был объявлен устаревшим в 3.1 и 2.7 и полностью удалён в Python 3.2. Если поддерживаются только версии 2.7, 3.1 и выше, можно просто переключиться на Capsule. Если требуется поддержка Python 3.0 или версий Python ранее 2.7, придётся поддерживать как CObject, так и Capsules. (Обратите внимание, что Python 3.0 больше не поддерживается и не рекомендуется для использования в производственной среде.)

Следующий пример заголовочного файла capsulethunk.h может решить эту проблему. Для этого достаточно писать код с использованием API капсулы и подключать этот заголовочный файл после Python.h. Код будет автоматически использовать капсулы в версиях Python, где они доступны, и переключаться на CObject, когда капсулы недоступны.

capsulethunk.h имитирует Capsules с помощью CObject. Однако CObject не предоставляет места для хранения «имени» капсулы. В результате имитируемые объекты Capsule, созданные capsulethunk.h, ведут себя несколько иначе, чем настоящие Capsules. А именно:

  • Параметр 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 */

Другие вариантыOther options

Если вы пишете новый модуль расширения, рассмотрите Cython. Он транслирует язык, похожий на Python, в C. Создаваемые им модули расширения совместимы с Python 3 и Python 2.