有没有办法使用SWIG C ++创建一个python模块,可以在Python2和Python3

时间:2017-05-20 05:49:12

标签: python python-2.7 python-3.x swig

我正在编写SWIG c ++文件来生成python模块。我想让用户在Python2和Python3脚本中导入它。由于SWIG具有用于绑定Python2和Python 3的不同标志,我想知道是否有一种方法可以为两者编写一个通用模块。

1 个答案:

答案 0 :(得分:1)

让我们稍微回过头来,让SWIG本身不受质疑。

历史上,为Python解释器的每个(有时甚至是次要的)版本更改编译Python模块都是必要的。这导致PEP-384,它从Python 3.2开始为Python C-API的子集定义了稳定的ABI。很明显,这并不适用于您的请求,因为您对2 vs 3感兴趣。此外,SWIG本身并没有实际生成PEP-384 compatible代码。

要进一步尝试并详细了解我发生了什么,我已制作了以下SWIG文件:

%module test
%inline %{
  char *frobinate(const char *in) {
    static char buf[1024];
    snprintf(buf, 1024, "World of hello: %s", in);
    return buf;
  }
%}

如果我们尝试用-DPy_LIMITED_API编译它,那就失败了:

swig3.0 -Wall -python test.i && gcc -Wall -Wextra -I/usr/include/python3.2 -shared -o _test.so test_wrap.c -DPy_LIMITED_API 2>&1|head
test_wrap.c: In function ‘SWIG_PyInstanceMethod_New’:
test_wrap.c:1114:3: warning: implicit declaration of function ‘PyInstanceMethod_New’ [-Wimplicit-function-declaration]
   return PyInstanceMethod_New(func);
   ^
test_wrap.c:1114:3: warning: return makes pointer from integer without a cast
test_wrap.c: In function ‘SWIG_Python_UnpackTuple’:
test_wrap.c:1315:5: warning: implicit declaration of function ‘PyTuple_GET_SIZE’ [-Wimplicit-function-declaration]
     Py_ssize_t l = PyTuple_GET_SIZE(args);
     ^
test_wrap.c:1327:2: warning: implicit declaration of function ‘PyTuple_GET_ITEM’ [-Wimplicit-function-declaration]

即。没有人拿到那张票,至少不是我正在测试的SWIG 3.0.2版本。

那么我们离开了哪里?那么SWIG 3.0 documentation on Python 3 support说了一些有趣的事情:

  

SWIG能够支持Python 3.0。 SWIG生成的包装器代码可以使用Python 2.x或3.0 进行编译。此外,通过将-py3命令行选项传递给SWIG,可以生成具有一些Python 3特定功能的包装器代码(有关这些功能的详细信息,请参阅下面的小节)。 -py3选项还禁用了Python 3的一些不兼容的功能,例如-classic。

我对该语句的解读是,如果要生成可以使用2.x和3.x Python头编译的源代码,则只需在运行SWIG时省略-py3参数即可。这似乎与我的测试一致,SWIG生成的相同代码编译得很好:

$ gcc -Wall -Wextra -I/usr/include/python2.7/ -shared -o _test.so test_wrap.c 
$ gcc -Wall -Wextra -I/usr/include/python3.2 -shared -o _test.so test_wrap.c 
$ gcc -Wall -Wextra -I/usr/include/python3.4 -shared -o _test.so test_wrap.c

(注意3.4会产生一些警告,但没有错误,它确实有效)

问题是任何给定版本的编译_test.so之间都没有可互换性。尝试从另一个版本导入版本将失败,动态链接器中有关丢失或未定义符号的错误。所以我们仍然坚持最初的问题,虽然我们可以为任何Python解释器编译我们的模块,但我们无法为所有版本编译它。

在我看来,处理这个问题的正确方法是使用distuitls,只需将模块安装到您希望在任何给定计算机上支持的每个Python版本的搜索路径中。

那说我们可以使用一种解决方法。基本上我们想为每个解释器构建我们模块的多个版本,并使用一些通用代码在各种实现之间切换。假设您没有使用SWIG -builtin选项生成的代码构建,我们可以尝试执行以下两个操作:

  1. 在共享对象本身
  2. 在某些Python代码中
  3. 后者实现起来要简单得多,所以让我们来看看。我通过使用以下bash脚本为我打算支持的每个Python版本构建共享对象做了一些事情:

    #!/bin/bash
    
    for v in 2.7 3.2 3.4
    do
        d=$(echo $v|tr . _)
        mkdir -p $d
        touch $d/__init__.py
        gcc -Wall -Wextra -I/usr/include/python$v -shared -o $d/_test.so test_wrap.c
    done
    

    这构建了3个版本的_test.so,在以他们想要支持的Python版本命名的目录中。如果我们现在尝试导入SWIG生成的模块,它会抱怨,因为没有迹象表明在哪里可以找到我们编译的_test模块。所以我们需要解决这个问题。有三种方法可以做到:

    1. 修改生成的SWIG import code。我没有设法使用SWIG 3.0.2进行这项工作 - 也许它太旧了?
    2. 在第二次导入之前修改Python搜索路径。我们可以使用%pythonbegin

      来实现
      %pythonbegin %{
      import os.path
      import sys
      sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '%d_%d' % (sys.version_info.major, sys.version_info.minor)))
      %}
      
    3. 写一个找到正确的_test.py存根,然后切换它们:

      from sys import version_info,modules
      from importlib import import_module
      real_mod = import_module('%d_%d.%s' % (version_info.major, version_info.minor, __name__))
      modules[__name__] = modules[real_mod.__name__] 
      
    4. 最后两个选项中的任何一个都有效,并导致import test在所有三个版本的Python中都取得了成功。但是,只是从源头安装了3次,它的作弊有点真,好得多。