将扩展模块移植到 Python 3
-
author
- Benjamin Peterson
Abstract
尽管更改 C-API 并不是 Python 3 的目标之一,但是许多 Python 级别的更改使得完整保留 Python 2 的 API 成为不可能。实际上,在 C 级别上,诸如int()和long()统一之类的某些更改更为明显。本文档致力于记录不兼容问题以及如何解决这些不兼容问题。
Conditional compilation
仅编译 Python 3 某些代码的最简单方法是检查PY_MAJOR_VERSION
是否大于或等于 3.
#if PY_MAJOR_VERSION >= 3
#define IS_PY3K
#endif
在条件块中,不存在的 API 函数可以别名为它们的等效项。
对象 API 的更改
Python 3 将具有相似Function的某些类型合并在一起,而将其他类型完全分开。
str/unicode Unification
Python 3 的str()类型等效于 Python 2 的unicode();这两个 C 函数都称为PyUnicode_*
。旧的 8 位字符串类型已变为bytes()
,而 C 函数称为PyBytes_*
。 Python 2.6 和更高版本提供了兼容性 Headersbytesobject.h
,将PyBytes
名称 Map 到PyString
名称。为了与 Python 3 达到最佳兼容性,应将PyUnicode
用于文本数据,将PyBytes
用于二进制数据。同样重要的是要记住,Python 3 中的PyBytes
和PyUnicode
不可互换,就像 Python 2 中的PyString
和PyUnicode
一样。以下示例显示了有关PyUnicode
,PyString
和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;
}
long/int Unification
Python 3 只有一种整数类型int()。但是它实际上对应于 Python 2 的long()类型-删除了 Python 2 中使用的int()类型。在 C-API 中,PyInt_*
函数被其PyLong_*
等效项替代。
模块初始化和状态
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
在 python 3.1 和 2.7 中引入了Capsule
对象,以替换CObject
。 CObject 很有用,但是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 版本,则必须同时支持 CObjects 和 Capsules。 (请注意,不再支持 Python 3.0,并且不建议将其用于生产环境.)
以下示例头文件capsulethunk.h
可能会为您解决问题。只需针对Capsule
API 编写代码,并将此头文件包含在Python.h
之后。您的代码将在具有 Capsules 的 Python 版本中自动使用 Capsules,并在 Capsules 不可用时切换到 CObjects。
capsulethunk.h
使用 CObjects 模拟胶囊。但是,CObject
没有提供存储胶囊“名称”的位置。结果,由capsulethunk.h
创建的模拟Capsule
对象的行为与实际的 Capsules 略有不同。特别:
Note
-
传递给PyCapsule_New()的 name 参数将被忽略。
-
传递给PyCapsule_IsValid()和PyCapsule_GetPointer()的 name 参数将被忽略,并且不会对名称进行错误检查。
-
PyCapsule_GetName()始终返回 NULL。
-
PyCapsule_SetName()总是引发异常并返回失败。 (由于无法在 CObject 中存储名称,因此在这里PyCapsule_SetName()的嘈杂故障比静默故障更为可取。如果这样做不方便,请根据需要随意修改本地副本。)
您可以在 Python 源代码发行版中找到Doc/includes/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 */
Other options
如果要编写新的扩展模块,则可以考虑Cython。它将类似 Python 的语言转换为 C。它创建的扩展模块与 Python 3 和 Python 2 兼容。