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语句。
html
有一些事情你能够在真正开始移植前先作。首先是移除一些你根本不须要的老别名的用法。例如RO宏已经被删除了。它只是一个简写的READONLY,因些若是你在代码中用了RO,你可使用READONLY来替代它。其余常见的重定义是statichere
和 staticforward。他们是为了兼容特定编译器的变通方法。对于表现良好的编译器他们只是
pythonstatic
的重定义,所以在CPython支持的全部平台都有表现良好的编译器的当今Python 3已经不须要他们了。若是你在代码中使用了他们,你能够用static
来代替。
其余在移植你能够作的变化是摆脱
程序员PyClassObject
。这个因长期弃用而在Python 3中移除的“老式类”。你应该能够没有大问题地移动到PyTypeObject。
当迁移C扩展到Python 3时遇到的一个不太明显的错误是"missing braces around initializer",它是在你初始化一个Python对象时出现的。你确实能够在正确的地方使用一对花括号来解决,可是一个更好的解决办法是使用
apiPyVarObject_HEAD_INIT
宏来替换PyObject_HEAD_INIT
宏。这种变化是为了确认C的严格规则,若是你有兴趣的话能够在PEP 3123[1]找到更多的信息。
之前的代码看起来像这样:app
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
:spa
#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
。
.net
另外一个大的变化是在模块初始化里。在这里有不少变化,做为一个模块初始化的返回结果一般是最终看成带有更多预处理命令或宏的C扩展的一部分。像Py_InitModule3
这个种的用来初始化模块的函数族已通过时了。取而代之的是你应该使用PyModule_Create
。在Py_InitModule3
带了几个参数的地方PyModule_Create
须要一个PyModuleDef
结构体。若是你想要支持Python 2 当你定义这个结构体及使用它时须要用
包裹这个代码。#ifPY_MAJOR_VERSION >= 3
来
code
#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的变化固然反映在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的,因此没有向后兼容性问题。
在写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