Supporting Python 3——遷移python2的c擴展到python3

遷移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語句。
html

在你開始前

有一些事情你能夠在真正開始移植前先作。首先是移除一些你根本不須要的老別名的用法。例如RO宏已經被刪除了。它只是一個簡寫的READONLY,因些若是你在代碼中用了RO,你可使用READONLY來替代它。其餘常見的重定義是staticherestaticforward。他們是爲了兼容特定編譯器的變通方法。對於表現良好的編譯器他們只是static的重定義,所以在CPython支持的全部平臺都有表現良好的編譯器的當今Python 3已經不須要他們了。若是你在代碼中使用了他們,你能夠用static來代替。python

其餘在移植你能夠作的變化是擺脫PyClassObject。這個因長期棄用而在Python 3中移除的「老式類」。你應該能夠沒有大問題地移動到PyTypeObject。程序員

對像初始化

當遷移C擴展到Python 3時遇到的一個不太明顯的錯誤是"missing braces around initializer",它是在你初始化一個Python對象時出現的。你確實能夠在正確的地方使用一對花括號來解決,可是一個更好的解決辦法是使用PyVarObject_HEAD_INIT宏來替換PyObject_HEAD_INIT宏。這種變化是爲了確認C的嚴格規則,若是你有興趣的話能夠在PEP 3123[1]找到更多的信息。api

之前的代碼看起來像這樣: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之後的版本,所以支持更早的版本咱們創建另外一個#ifndefspa

#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中的變化

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)——目錄

相關文章
相關標籤/搜索