SWIG如何支持std::tuple 提取返回元组内的元素值
问题描述
在Linux环境下使用SWIG生成Python可调用的C接口时,调用返回值类型为std::tuple的SWIG生成函数,会得到对应std::tuple类型的Swig指针对象(SwigPyObject),无法直接提取元组内部存储的数值。
已知将C返回值修改为std::vector类型后,引入<std_vector.i>并实例化对应模板即可在Python侧正常使用,但该方案需要修改原有C++层代码,预期通过SWIG原生机制实现无侵入适配。
最小复现信息
- 头文件
foo.h:定义foo类,声明返回类型为std::tuple<double, double>的return_thing方法 - 实现文件
foo.cpp:实现foo类构造、析构函数及return_thing方法,方法返回存储两个double值的元组 - SWIG接口文件
foo.i:仅做基础模块配置,直接引入foo.h声明 - 编译流程:
- 执行
swig -python -c++ -o foo_wrap.cpp foo.i生成包装代码 - 用g添加Python3.8头文件路径、开启C17标准编译为位置无关代码
- 链接生成供Python调用的动态库
_foo.so
- 执行
- 测试现象:Python侧导入生成的foo模块,实例化foo类后调用
return_thing()方法,返回值类型为SwigPyObject,打印结果为<Swig Object of type 'std::tuple< double,double > *'>,无法直接访问内部值
预期效果
Python侧可直接获取元组内的数值,效果等价于C中使用C17结构化绑定auto [s1, s2] = Foo.return_thing();的使用体验,可直接拿到两个double值操作。
解决方案
SWIG未对std::tuple提供全版本默认适配,和std::vector有内置<std_vector.i>支持不同,可通过自定义typemap(类型映射)实现C++ std::tuple到Python原生tuple的自动转换,无需修改原有C++业务代码。
实现方式
方式一:使用内置库(SWIG 4.1及以上版本推荐)
SWIG 4.1版本开始官方内置std_tuple.i标准库适配文件,直接在接口文件中引入并声明需要实例化的tuple类型即可:
%{ #include <tuple> #include "foo.h" %} // 引入tuple内置适配 %include <std_tuple.i> // 声明代码中用到的tuple实例 %template(DoublePair) std::tuple<double, double>; // 原有头文件引入逻辑 %include "foo.h"
方式二:自定义typemap(兼容低版本SWIG)
SWIG 4.0及更早版本没有内置tuple适配,手动编写out类型映射即可完成返回值转换,针对std::tuple<double, double>的示例代码如下:
%{ #include <tuple> #include "foo.h" %} // 值返回类型的tuple转Python原生tuple %typemap(out) std::tuple<double, double> { PyObject* res_tup = PyTuple_New(2); PyTuple_SetItem(res_tup, 0, PyFloat_FromDouble(std::get<0>($1))); PyTuple_SetItem(res_tup, 1, PyFloat_FromDouble(std::get<1>($1))); $result = res_tup; } // 指针/引用返回类型的tuple转Python原生tuple %typemap(out) std::tuple<double, double>* { PyObject* res_tup = PyTuple_New(2); PyTuple_SetItem(res_tup, 0, PyFloat_FromDouble(std::get<0>(*$1))); PyTuple_SetItem(res_tup, 1, PyFloat_FromDouble(std::get<1>(*$1))); // 若指针所有权归SWIG管理,需在此处补充内存释放逻辑,避免内存泄漏 $result = res_tup; } // 原有头文件引入逻辑 %include "foo.h"
验证方法
按照原有编译流程重新生成_foo.so动态库后,Python侧可直接解包返回值:
import foo f = foo.foo() s1, s2 = f.return_thing() # 直接解包获取两个double值,和C++结构化绑定使用体验一致 print(s1, s2)
扩展说明
- 若需要支持其他长度、其他元素类型的
std::tuple,参照上述typemap逻辑调整PyTuple_New的长度参数、std::get<N>的索引以及对应类型转Python对象的API即可 - 若tuple内存储自定义C++类型,将基础类型转换API(如
PyFloat_FromDouble)替换为SWIG_NewPointerObj等对应类型的转换接口即可 - 处理指针返回的tuple时,必须明确内存所有权归属,避免出现double free或内存泄漏问题
内容的提问来源于stack exchange,提问作者Simon_lb
相关产品推荐
相关产品推荐

