我正在编写SWIG c ++文件来生成python模块。我想让用户在Python2和Python3脚本中导入它。由于SWIG具有用于绑定Python2和Python 3的不同标志,我想知道是否有一种方法可以为两者编写一个通用模块。
答案 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
选项生成的代码构建,我们可以尝试执行以下两个操作:
后者实现起来要简单得多,所以让我们来看看。我通过使用以下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
模块。所以我们需要解决这个问题。有三种方法可以做到:
在第二次导入之前修改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)))
%}
写一个找到正确的_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__]
最后两个选项中的任何一个都有效,并导致import test
在所有三个版本的Python中都取得了成功。但是,只是从源头安装了3次,它的作弊有点真,好得多。