Supporting Python 3——迁移python2的c扩展到python3

妖精的绣舞 提交于 2019-12-06 05:45:49

迁移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来替代它。其他常见的重定义是staticherestaticforward。他们是为了兼容特定编译器的变通方法。对于表现良好的编译器他们只是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里了。这些通常容易处理。这里的一个典型例子是intlong类型的统一。虽然在Python里它就像是是long类型离去了,它实际上是int类型被移除并且把long类型改名了。然而在C API里它没有被改名。那意味着所有返回Python int对象的函数都没有了并且你需要用返回Python long对象的函数开替代他们。这意味着必须用PyLong_FromLong来替换PyInt_FromLongPyLong_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类型一直叫作unicodestr类型的及所有其附带的技能函数都没有了,一个新的bytes类型取代了它。这意味着如果你扩展返回或处理二进制数据,你将会在Python2中获取并返回PyString对象,但是你在Python3将要处理PyBytes对象。在你处理文本数据的地方Python2中你将要同时接受PyStringPyUnicode,但是在Python3中只有PyUnicode是相关的。还可能使用相同的技术来处理上面的intlong,你还可以写两个版本的代码并使用一个#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)——目录


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!