我正在编写一个 SWIG c++ 文件来生成一个 python 模块。我想让用户在 Python2 和 Python3 脚本中导入它。由于 SWIG 具有用于绑定 Python2 和 Python 3 的不同标志,我想知道有没有一种方法可以为两者编写一个通用模块。
让我们倒退一点,让 SWIG 本身远离问题开始。
从历史上看,有必要为 Python 解释器的每个(有时甚至是次要的)版本更改编译一个 Python 模块。这导致了PEP-384,它为 Python 3.2 以后的 Python C-API 的子集定义了一个稳定的 ABI。很明显,这实际上并不能满足您的要求,因为您对 2 vs 3 感兴趣。此外,SWIG 本身实际上并不生成PEP-384 兼容的代码。
为了进一步试验并了解更多关于正在发生的事情,我制作了以下 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 版本上没有。
那么,这让我们何去何从?好吧,关于 Python 3 支持的SWIG 3.0 文档说了一些有趣的事情:
SWIG 能够支持 Python 3.0。SWIG 生成的包装器代码可以使用 Python 2.x 或 3.0 进行编译。此外,通过将 -py3 命令行选项传递给 SWIG,可以生成具有一些 Python 3 特定功能的包装器代码(有关这些功能的详细信息,请参见下面的小节)。-py3 选项还禁用了 Python 3 的一些不兼容功能,例如 -classic。
我对那句话的理解是,如果你想生成可以用 2.x 和 3.x Python 头编译的源代码,你需要做的就是-py3
在运行 SWIG 时省略参数。这似乎适用于我的测试,由 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
这会在以它们打算支持的 Python 版本命名的目录中构建 3 个版本的 _test.so。如果我们现在尝试导入 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 次以上确实是作弊,而且要好得多。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句