用SWIG向Python提供C++里STL的容器


Python

C

在Python项目中使用C/C++的代码,除了少数场景,其它都有数据交换的需求。

而C++的vector、map等,则是常见的数据容器。

本文介绍如何利用SWIG,在Python中调用STL的string、vector和map。

string ¶

C++的string可以在interface文件里声明时,自动转换成Python文件的str。

但是需要在%module ...下,声明%include "std_string.i"。

%module example_string
%include "std_string.i"
%{
std::string echo(std::string msg);
%}
std::string echo(std::string msg);

如果只是一些内容简单、结构复杂的数据交换,可以考虑以某种方式序列化为字符串,到上层再解析为Python层的数据结构。

这里展示的是一个返回自身的echo函数,实现如下:

#include 
using namespace std;
string echo(string msg) {
return msg;
}

在Python中,调用结果如下:

>>> import example_string
>>> msg = example_string.echo('message')
>>> msg
'message'
>>> isinstance(msg, str)
True

可见,传入和返回,都是Python自带的str类型,非常方便。

vector ¶

vector应该是最常见的顺序容器了。

它比string要更麻烦一层,因为模板类是特殊的,需要用%template声明一下。

%module example_vector
%include "std_string.i"
%include "std_vector.i"
%{
using namespace std;
vector vector_int2str(vector input);
%}
namespace std {
%template(StringVector) vector;
%template(IntVector) vector;
}
using namespace std;
vector vector_int2str(vector input);

Python层会自动生成StringVector和IntVector这两个类,作为类型的替代。

这两个类的命名可以随意,它们都实现了list的相关协议,可以当作list来使用。

示例中的vector_int2str函数,就是把vector的int转换为string,实现如下:

#include 
#include 
using namespace std;
vector vector_int2str(vector input) {
vector result;
for (vector::const_iterator it = input.begin();
it != input.end();
++it) {
result.push_back(to_string(*it));
}
return result;
}

然而,实际在Python层获取返回值时,却是tuple类型。

传入时,也可直接使用list或tuple等类型——这大概就是动态语言、鸭子类型的魅力吧。

>>> import example_vector
>>> data = example_vector.vector_int2str([1, 2, 3])
>>> data
('1', '2', '3')
>>> isinstance(data, tuple)
True
map ¶

map是最常见的的关联容器。

和vector一起,可以简单表达一切线性数据结构。

与vector类似,使用时也需要用%template声明。

此外,它还有一个特殊情况。

%module example_map
%include "std_string.i"
%include "std_map.i"
%{
using namespace std;
map reverse_map(map input);
%}
namespace std {
%template(Int2strMap) map;
%template(Str2intMap) map;
}
using namespace std;
map reverse_map(map input);

上面的形式,和vector类似。

其中,reverse_map反转了映射关系,示例代码如下:

#include 
#include 
#include 
using namespace std;
map reverse_map(map input) {
map result;
for (map::const_iterator it = input.begin();
it != input.end();
++it) {
result[it->second] = it->first;
}
return result;
}

特殊情况就是,虽然Str2intMap和Int2strMap也实现了dict的协议,但是使用时不能直接用Python的dict。

str2int = example_map.Str2intMap()
str2int['1'] = 1
str2int['2'] = 2
str2int['3'] = 3
result = example_map.reverse_map(str2int)
assert isinstance(result, example_map.Int2strMap)
for key, value in result.items():
assert str2int[value] == key

这就有些不方便了。

不过,虽然没有vector方便,但还是可以接受。

换个角度看,也许vector才是特殊情况吧。

总结 ¶

其它数据结构,比如list、set等,都有对应的转换方式,这里不一一介绍。

C++ class
C++ Library file
SWIG Interface library file
std::auto_ptr
memory
std_auto_ptr.i
std::deque
deque
std_deque.i
std::list
list
std_list.i
std::map
map
std_map.i
std::pair
utility
std_pair.i
std::set
set
std_set.i
std::string
string
std_string.i
std::vector
vector
std_vector.i
std::array
array (C++11)
std_array.i
std::shared_ptr
shared_ptr (C++11)
std_shared_ptr.i

在这些内置*.i的支持下,Python与C++的数据交换也变得轻松起来。

参考 ¶

以下是相关的官网文档: