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

Posted

技术标签:

【中文标题】有没有办法使用 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 【发布时间】:2017-05-20 05:49:11 【问题描述】:

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

【问题讨论】:

【参考方案1】:

让我们稍微回顾一下,让 SWIG 本身一开始就没有问题。

从历史上看,对于 Python 解释器的每个(有时甚至是很小的)版本更改,都需要编译一个 Python 模块。这导致了PEP-384,它从 Python 3.2 开始为 Python C-API 的一个子集定义了一个稳定的 ABI。很明显,这实际上不适用于您的请求,因为您对 2 与 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 版本的搜索路径中。 p>

也就是说,我们可以使用一种解决方法。本质上,我们希望为每个解释器构建多个版本的模块,并使用一些通用代码在各种实现之间切换。假设您没有使用 SWIG 的 -builtin 选项生成的代码进行构建,我们可以在两个地方尝试这样做:

    在共享对象本身中 在一些 Python 代码中

后者的实现要简单得多,所以让我们来看看。我通过使用以下 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 模块。所以我们需要解决这个问题。有以下三种方法:

    修改生成的 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 次以上确实有点作弊,而且要好得多。

【讨论】:

所以我必须在接口文件中包含“#if PY_MAJOR_VERSION 您需要的所有 ifdef 都是自动生成的,如果您不使用 -py3,代码将适用于 2 和 3。不过,您需要为每个 python 版本构建一次。

以上是关于有没有办法使用 SWIG C++ 创建一个 python 模块,可以在 Python2 和 Python3 中导入的主要内容,如果未能解决你的问题,请参考以下文章

将 SWIG 与 C++ 的 std::map 一起使用时,Java 没有迭代器

在 SWIG 中携带自定义 Python 数据的 C++ 对象

在 swig 接口文件中没有调用 C++ 析构函数

无法从使用 SWIG 创建的 python 访问 C++ 扩展模块及其方法

通过 SWIG 链接 C++ 和 Java GUI

如何在 SWIG 中为 c++ 创建模板函数?