将扩展模块移植到Python 3

author: Benjamin Peterson

摘要

虽然改变C-API不是Python 3的目标之一,许多Python级别的更改使得Python 2的API无法实现。事实上,在//级别上,int()long()统一等一些变化更为明显。本文档努力记录不兼容性以及它们如何解决.

条件编译

只编译Python 3的一些代码的最简单方法是检查PY_MAJOR_VERSION大于或等于3.

#if PY_MAJOR_VERSION >= 3
#define IS_PY3K
#endif

API不存在的函数可以在条件块中等同于它们的等价物

更改为对象API

Python 3将一些具有类似函数的类型合并在一起同时干净地分离其他人.

str/unicode统一

Python 3的str()类型相当于Python 2的unicode();这两个函数被称为PyUnicode_*。旧的8位字符串类型已成为bytes(),其中C函数称为PyBytes_*。Python 2.6及更高版本提供了兼容性标题,bytesobject.h,将PyBytes名称映射到PyString。为了与Python 3实现最佳兼容性,PyUnicode应该用于文本数据,PyBytes应该用于二进制数据。同样重要的是要记住,在Python 3中PyBytesPyUnicode不可互换,如PyStringPyUnicode在Python 2中。以下示例显示了最佳实践关于PyUnicode, PyStringPyBytes.

#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;
}

long / int统一

Python 3只有一个整数类型,int()。但它实际上对应于Python 2的long()类型 – 删除了在Python 2中使用的int()类型。在C-API中,PyInt_*函数替换为它们的PyLong_* equivalent.

模块初始化和状态

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
}

对话框替换了

Capsule对象是在Python 3.1和2.7中引入来替换CObject。CObjects很有用,但是CObject API是有问题的:它不允许区分有效的CObject,这允许不匹配的CObject使解释器崩溃,并且它的一些API依赖于C中的未定义行为。(有关进一步阅读胶囊背后的基本原理,请参阅bpo-5630。)

如果您目前正在使用CObjects,并且想要迁移到3.1或更新版本,则需要切换到Capsules.CObject在3.1和2.7中弃用,在Python 3.2中完全删除。如果你只支持2.7或3.1及以上,你只需切换到Capsule。如果你需要支持Python 3.0或早于2.7的Python版本,你必须同时支持CObject和Capsule。(注意,不再支持Python 3.0,不建议用于生产用途。)

以下示例头文件capsulethunk.h为您解决问题。只需在Capsule API上编写代码,并在Python.h之后包含此头文件。您的代码将自动使用Capsulesin版本的Python与Capsules,并在胶囊不可用时切换到CObjects。

capsulethunk.h使用CObjects模拟Capsules。但是,CObject没有地方存放胶囊的“名称”。因为Capsule创建的模拟capsulethunk.h对象与真实的Capsule略有不同。具体来说:

  • 传入PyCapsule_New()的名称参数被忽略.
  • 传入PyCapsule_IsValid()PyCapsule_GetPointer()的名称参数被忽略,并且没有执行错误检查名称.
  • PyCapsule_GetName()总是返回NULL.
  • PyCapsule_SetName()总是引发异常并且意外失败。(由于无法在CObject中存储名称,因此PyCapsule_SetName()的嘈杂失败被认为优于静音失败。如果这样不方便,请随意根据需要修改本地复制。)

你可以在Python源代码中找到capsulethunk.h 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))

/* 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 */

其他选项

如果您正在编写新的扩展模块,您可能会考虑使用Cython。它将类似Python的语言转换为C.它创建的扩展模块与Python 3和Python 2兼容.