迁移C扩展
Python 3有很多在C语言API上的变化,包括在API中定义类和初妈化的模块。这意味着每一个C扩展为了能在Python 3 下运行需要进行修改。一些变化很简单而另一些不简单,但是因为2to3只处理Python代码,你不得不对这些变化进化手动处理。这也意味着不你能通过2to3的转换来同时实现Python 2和Python 3的支持。幸运的是,C预处理程序可以很容易地实现使用相同的代码支持Python 2和Python 3.这是一个使在C中支持不同版本API的标准方式,这也将成为C程序员的标准技能。因此没有丑陋的黑客参与,只是少了些漂亮的#if和#ifndef语句。
在你开始前
有一些事情你可以在真正开始移植前先做。首先是移除一些你根本不需要的老别名的用法。例如RO宏已经被删除了。它只是一个简写的READONLY,因些如果你在代码中用了RO,你可以使用READONLY来替代它。其他常见的重定义是statichere
和 staticforward。他们是为了兼容特定编译器的变通方法。对于表现良好的编译器他们只是
static
的重定义,因此在CPython支持的所有平台都有表现良好的编译器的当今Python 3已经不需要他们了。如果你在代码中使用了他们,你可以用static
来代替。
其他在移植你可以做的变化是摆脱
PyClassObject
。这个因长期弃用而在Python 3中移除的“老式类”。你应该可以没有大问题地移动到PyTypeObject。
对像初始化
当迁移C扩展到Python 3时遇到的一个不太明显的错误是"missing braces around initializer",它是在你初始化一个Python对象时出现的。你确实可以在正确的地方使用一对花括号来解决,但是一个更好的解决办法是使用
PyVarObject_HEAD_INIT
宏来替换PyObject_HEAD_INIT
宏。这种变化是为了确认C的严格规则,如果你有兴趣的话可以在PEP 3123[1]找到更多的信息。
以前的代码看起来像这样:
static PyTypeObject mytype = {
PyObject_HEAD_INIT(NULL)
0,
...
};
现在的代码应该像这样:
static PyTypeObject mytype = {
PyVarObject_HEAD_INIT(NULL, 0)
...
};
这将会在Python2.6和2.7中工作良好。如你需要支持Python2.5或者更早的版本,你可以在缺少PyVarObject_HEAD_INIT宏时像这样简单地定义它:
#ifndef PyVarObject_HEAD_INIT
#define PyVarObject_HEAD_INIT(type, size) \
PyObject_HEAD_INIT(type) size,
#endif
另一个在对像头中的变化是PyObject_HEAD
宏已经被改变了,因此现在是ob_type
嵌套结构。这意味着你再也不能直接从结构体中调用ob_type
了,因此代码中像ob->ob_type
这样的将停止工作。你使用Py_TYPE(ob)
来替代这些。Py_TYPE
宏只用于Python 2.6以后的版本,因此支持更早的版本我们建立另一个#ifndef
:
#ifndef Py_TYPE
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
#endif
这在两种情况下,上术定义直接从Python 2.6中为了与Python 3向前兼容的头文件中获取。他们在更前的版本中也工作良好,因此这是一个你可以作为通用规则的决窍。如果你需要用这个已经在Python 3和Python2.6中定义宏,只有欺骗Python 2.6和Python 2.7的定义并把它放进里一个#ifndef
。
模块初始化
另一个大的变化是在模块初始化里。在这里有很多变化,作为一个模块初始化的返回结果通常是最终当作带有更多预处理命令或宏的C扩展的一部分。像Py_InitModule3
这个种的用来初始化模块的函数族已经过时了。取而代之的是你应该使用PyModule_Create
。在Py_InitModule3
带了几个参数的地方PyModule_Create
需要一个PyModuleDef
结构体。如果你想要支持Python 2 当你定义这个结构体及使用它时需要用
包裹这个代码。#ifPY_MAJOR_VERSION >= 3
来
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"themodulename", /* m_name */
"This is a module", /* m_doc */
-1, /* m_size */
module_functions, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif
...
#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("themodulename",
module_functions, "This is a module");
#endif
如果你想从代码中分开#if
语句,可以建立一个宏声明。我用这一个,跃然它不支持像重载和遍历这样的额外功能。
#if PY_MAJOR_VERSION >= 3
#define MOD_DEF(ob, name, doc, methods) \
static struct PyModuleDef moduledef = { \
PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
ob = PyModule_Create(&moduledef);
#else
#define MOD_DEF(ob, name, doc, methods) \
ob = Py_InitModule3(name, methods, doc);
#endif
模块初始化函数的定义也变化了。在Python2 可以像这样声明一个函数来初始化模块:
PyMODINIT_FUNC init<yourmodulename>(void)
在Python 3变成了这样:
PyMODINIT_FUNC PyInit_<yourmodulename>(void)
这不仅仅是名字变化了;PyMODINIT_FUNC
的值也变了。在Python 2 它是一个典型的void
,但是在Python 3它现在返回了一个PyObject*
。如果发生了错误你必须返回NULL
,如果初始化成功了则必须返回一个模块对象。如果需要同时支持Python 3和Python 2 ,有各种在函数开头使用多重#if PY_MAJOR_VERSION >= 3
的方法来处理这个。然而,那些代码变得丑陋了,特别是在函数定义的地方:
PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
PyInit_<yourmodulename>(void)
#else
init<yourmodulename>(void)
#endif
{
...
它可以工作,但是它不是可读性很好的代码。通过使用宏,它将变得稍微更好一些:
#if PY_MAJOR_VERSION >= 3
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
#else
#define MOD_INIT(name) PyMODINIT_FUNC init##name(void)
#endif
MODINIT(themodulename)
{
...
}
但是如果你必须要返回一个值或者不返回时还是得在函数中使用#if
语句来判断或者作出又一个宏。另一种选择是定义三个功能。首先一个实际的模块初始化函数返回一个PyObject*
,然后是两个封装器。Python 3的一个调用第一个并返回一个值,Python 2的一个调用模块初始化不返回值:
// Python 3 module initialization
static PyObject *
moduleinit(void)
{
MOD_DEF(m, "themodulename",
"This is the module docstring",
module_methods)
if (m == NULL)
return NULL;
if (PyModule_AddObject(m, "hookable",
(PyObject *)&hookabletype) < 0)
return NULL;
return m;
}
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC initthemodulename(void)
{
moduleinit();
}
#else
PyMODINIT_FUNC PyInit_themodulename(void)
{
return moduleinit();
}
#endif
就像你看到的模块初始化在任何情况下都结束了使用很多#ifPY_MAJOR_VERSION >= 3
的情况。一个从zope.proxy
中取得的这些#if
语句完整例子是这样的:
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_zope_proxy_proxy", /* m_name */
module___doc__, /* m_doc */
-1, /* m_size */
module_functions, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif
static PyObject *
moduleinit(void)
{
PyObject *m;
#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("_zope_proxy_proxy",
module_functions, module___doc__);
#endif
if (m == NULL)
return NULL;
if (empty_tuple == NULL)
empty_tuple = PyTuple_New(0);
ProxyType.tp_free = _PyObject_GC_Del;
if (PyType_Ready(&ProxyType) < 0)
return NULL;
Py_INCREF(&ProxyType);
PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType);
if (api_object == NULL) {
api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL);
if (api_object == NULL)
return NULL;
}
Py_INCREF(api_object);
PyModule_AddObject(m, "_CAPI", api_object);
return m;
}
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC
init_zope_proxy_proxy(void)
{
moduleinit();
}
#else
PyMODINIT_FUNC
PyInit__zope_proxy_proxy(void)
{
return moduleinit();
}
#endif
如果你所有的这些测试版本,你把这些有区别的东西一起放在函数声明及使用宏之前。这个是我在开始地方用一片的定义替换#if
后的同一个zope.proxy
模块:
#if PY_MAJOR_VERSION >= 3
#define MOD_ERROR_VAL NULL
#define MOD_SUCCESS_VAL(val) val
#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
#define MOD_DEF(ob, name, doc, methods) \
static struct PyModuleDef moduledef = { \
PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
ob = PyModule_Create(&moduledef);
#else
#define MOD_ERROR_VAL
#define MOD_SUCCESS_VAL(val)
#define MOD_INIT(name) void init##name(void)
#define MOD_DEF(ob, name, doc, methods) \
ob = Py_InitModule3(name, methods, doc);
#endif
MOD_INIT(_zope_proxy_proxy)
{
PyObject *m;
MOD_DEF(m, "_zope_proxy_proxy", module___doc__,
module_functions)
if (m == NULL)
return MOD_ERROR_VAL;
if (empty_tuple == NULL)
empty_tuple = PyTuple_New(0);
ProxyType.tp_free = _PyObject_GC_Del;
if (PyType_Ready(&ProxyType) < 0)
return MOD_ERROR_VAL;
Py_INCREF(&ProxyType);
PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType);
if (api_object == NULL) {
api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL);
if (api_object == NULL)
return MOD_ERROR_VAL;
}
Py_INCREF(api_object);
PyModule_AddObject(m, "_CAPI", api_object);
return MOD_SUCCESS_VAL(m);
}
这是迄今为止我因格式原因的首选版本。但是如果你喜欢嵌套的#if
语句或者使用许多#define
宏,它最终是品味及代码风格的原因。
在Python中的变化
Python的变化当然反映在C API里了。这些通常容易处理。这里的一个典型例子是int
和long
类型的统一。虽然在Python里它就像是是long
类型离去了,它实际上是int
类型被移除并且把long
类型改名了。然而在C API里它没有被改名。那意味着所有返回Python int
对象的函数都没有了并且你需要用返回Python long
对象的函数开替代他们。这意味着必须用PyLong_FromLong
来替换PyInt_FromLong
,PyLong_FromString
来替换PyInt_FromString
等等。如果你需要保持Python2的兼容,你必须有条件地更换它:
#if PY_MAJOR_VERSION >= 3
PyModule_AddObject(m, "val", PyLong_FromLong(2));
#else
PyModule_AddObject(m, "val", PyInt_FromLong(2));
#endif
如果你需要做不止一次,也可以在这种情况下用#define
来使代码更干净:
#if PY_MAJOR_VERSION < 3
#define PyInt_FromLong PyLong_FromLong
#endif
PyModule_AddObject(m, "val", PyInt_FromLong(2));
在Python 3.2 CObject API 已经被移除。 它被替换成在Python 2.7 和3.1中同样可以用的Capsule API。对于仅包装一个C值的简单用例这个改变是很简单的:
#if PY_MAJOR_VERSION < 3
c_api = PyCObject_FromVoidPtr ((void *)pointer_to_value, NULL);
#else
c_api = PyCapsule_New((void *)pointer_to_value, NULL, NULL);
#endif
你可能会遇到的其他在Python中的变化是对__cmp__()
方法的取消支持。_typeobject
结构体是用来定义一个新的包含__cmp__()
方法定义地方的类型。它一直接在Python 3中存在兼容问题,但它现在被忽略了。cmpfunc
类型定义和PyObject_Compare
函数也已经被移除了。在这里得到完整的Python 3兼容性的唯一途径是实现丰富的比较功能。这里是向后支持到Python2.1的,所以没有向后兼容性问题。
Strings 和 Unicode
在写C扩展时strings、Unicode和bytes的变化当然是最大的变化。在C API中一直没有在字符串之间改名并且unicode
类型一直叫作unicode
。str
类型的及所有其附带的技能函数都没有了,一个新的bytes
类型取代了它。这意味着如果你扩展返回或处理二进制数据,你将会在Python2中获取并返回PyString
对象,但是你在Python3将要处理PyBytes
对象。在你处理文本数据的地方Python2中你将要同时接受PyString
和PyUnicode
,但是在Python3中只有PyUnicode
是相关的。还可能使用相同的技术来处理上面的int
和long
,你还可以写两个版本的代码并使用一个#if
来选择他们,或者根据你需要定义在Python 3 没有的PyString
函数为PyBytes
或者PyUnicode
。
附注:
[1] | http://www.python.org/dev/peps/pep-3123/ |
在湖闻樟注:
原文http://python3porting.com/cextensions.html#migrating-c-extensions
引导页Supporting Python 3:(支持Python3):深入指南
目录Supporting Python 3(支持Python 3)——目录
来源:oschina
链接:https://my.oschina.net/u/2288842/blog/401406