將擴展模塊移植到 Python 3?
- 作者
Benjamin Peterson
摘要
盡管更改 C-API 并不是 Python 3 的目標之一,但許多 Python 級別的更改使得 Python 2 的 API 無法完整實現。實際上,一些變化如 int() 和 long()?的統一在 C 級別更明顯。本文檔致力于記錄不兼容性以及如何解決這些問題。
條件編譯?
僅編譯 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() ; C函數被稱為 PyUnicode_* 。舊的 8 位字符串類型變為 bytes() ,其中 C 函數稱為 PyBytes_* 。 Python 2.6 及更高版本提供了一個兼容性頭文件 bytesobject.h ,將 PyBytes 名稱映射到 PyString 。 為了保持與 Python 3 的最佳兼容性, PyUnicode 應該用于文本數據,并且 PyBytes 用于二進制數據。同樣重要的是要記住 pyBytes 和 Python 3中的 PyUnicode 不可互換,如 PyString 和 PyUnicode 在 Python 2 以下中示例顯示了以下方面的最佳實踐 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;
}
模塊初始化和狀態?
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?
Capsule 對象是在 Python 3.1 和 2.7 中引入的,用于替換 CObject 。 CObject 是有用的,但是 CObject API 是有問題的:它不允許區分有效的 CObject ,這導致不匹配的 CObject 使解釋器崩潰,并且它的一些 API 依賴于 C 中的未定義行為。有關 Capsule 背后的基本原理的進一步閱讀,請參閱 bpo-5630 。)
如果你當前正在使用 CObject ,并且想要遷移到 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 。你的代碼將自動在帶有 Capsule 的 Python 版本中使用 Capsules ,并在 Capsule 不可用時切換到 CObjects 。
capsulethunk.h 使用 CObject 模擬 Capsules 。 但是, CObject 沒有提供存儲膠囊的“名稱”的地方。因此,模擬 Capsule 對象由 capsulethunk.h 創建,其行為與真實 Capsule 略有不同。特別地:
傳遞給
PyCapsule_New()的 name 參數被忽略。傳入以下命令的 name 參數
PyCapsule_IsValid()和PyCapsule_GetPointer()被忽略,并且不執行錯誤檢查。
PyCapsule_GetName()總是返回 NULL 。
PyCapsule_SetName()總是引發異常并返回失敗。(由于無法在 CObject 中存儲名稱,因此PyCapsule_SetName()的明顯失敗被認為優于靜默失敗。如果這樣不方便,請隨意根據需要修改本地副本。)
你可以在 Python 源代碼分發中的 Doc/includes/capsulethunk.h 找到 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 */
