有没有办法使用 SWIG C++ 创建一个 python 模块,它可以在 Python2 和 Python3 中导入
Is there a way to create a python module using SWIG C++ which can be imported in both Python2 and Python3
我正在编写一个 SWIG c++ 文件来生成一个 python 模块。我想让用户在 Python2 和 Python3 脚本中导入它。由于 SWIG 具有不同的绑定标志 Python2 和 Python 3,我想知道是否有一种方法可以为两者编写一个通用模块。
让我们稍微回顾一下,让 SWIG 本身不在讨论范围之内。
从历史上看,有必要为 Python 解释器的每个(有时甚至是次要的)版本更改编译一个 Python 模块。这导致了PEP-384, which defined a stable ABI for a subset of the Python C-API from Python 3.2 onwards. So obviously that doesn't actually work for your request, because you're interested in 2 vs 3. Furthermore SWIG itself doesn't actually generate 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 is able to support Python 3.0. The wrapper code generated by SWIG can be compiled with both Python 2.x or 3.0. Further more, by passing the -py3 command line option to SWIG, wrapper code with some Python 3 specific features can be generated (see below subsections for details of these features). The -py3 option also disables some incompatible features for Python 3, such as -classic.
我对该声明的理解是,如果你想生成可以用 2.x 和 3.x Python headers 编译的源代码,你只需要do 是在 运行 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
选项生成的代码进行构建,我们可以在两个地方尝试这样做:
- 在共享中object本身
- 在一些Python代码中
后者实现起来要简单得多,让我们看一下。我通过使用以下 bash 脚本为每个我打算支持的 Python 版本构建一个共享 object 做了一些事情:
#!/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
这构建了 _test.so 的 3 个版本,在以它们打算支持的 Python 版本命名的目录中。如果我们现在尝试导入 SWIG 生成的模块,它会报错,因为没有指示在哪里可以找到我们编译的 _test
模块。所以我们需要解决这个问题。共有三种方法:
- 修改生成的 SWIG import code。我没能在 SWIG 3.0.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)))
%}
编写一个 _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 次以上确实有点作弊而且要好得多。
我正在编写一个 SWIG c++ 文件来生成一个 python 模块。我想让用户在 Python2 和 Python3 脚本中导入它。由于 SWIG 具有不同的绑定标志 Python2 和 Python 3,我想知道是否有一种方法可以为两者编写一个通用模块。
让我们稍微回顾一下,让 SWIG 本身不在讨论范围之内。
从历史上看,有必要为 Python 解释器的每个(有时甚至是次要的)版本更改编译一个 Python 模块。这导致了PEP-384, which defined a stable ABI for a subset of the Python C-API from Python 3.2 onwards. So obviously that doesn't actually work for your request, because you're interested in 2 vs 3. Furthermore SWIG itself doesn't actually generate 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 is able to support Python 3.0. The wrapper code generated by SWIG can be compiled with both Python 2.x or 3.0. Further more, by passing the -py3 command line option to SWIG, wrapper code with some Python 3 specific features can be generated (see below subsections for details of these features). The -py3 option also disables some incompatible features for Python 3, such as -classic.
我对该声明的理解是,如果你想生成可以用 2.x 和 3.x Python headers 编译的源代码,你只需要do 是在 运行 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
选项生成的代码进行构建,我们可以在两个地方尝试这样做:
- 在共享中object本身
- 在一些Python代码中
后者实现起来要简单得多,让我们看一下。我通过使用以下 bash 脚本为每个我打算支持的 Python 版本构建一个共享 object 做了一些事情:
#!/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
这构建了 _test.so 的 3 个版本,在以它们打算支持的 Python 版本命名的目录中。如果我们现在尝试导入 SWIG 生成的模块,它会报错,因为没有指示在哪里可以找到我们编译的 _test
模块。所以我们需要解决这个问题。共有三种方法:
- 修改生成的 SWIG import code。我没能在 SWIG 3.0.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))) %}
编写一个 _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 次以上确实有点作弊而且要好得多。