Search code examples
pythonc++swigtypemaps

SWIG convert vector of maps to python list of dictionaries


Hi I am working on wrapping C++ with SWIG for use in python. Inorder to wrap C++ classes I am using SWIG. I have no good idea about typemaps and hence I am stuck. I have a vector consisiting of multiple maps ie. std::vector<std::map<std::string, int>> and I want to return it from C++ function and convert it to list of dict in python. I searched online but couldnot find anything.

I have a class like this:

std::vector<std::map<std::string, int>> WapperCode::wrapper_fuc(){
std::vector<std::map<std::string, int>> outer_vector;
for(int i = 0 ; i < 10 ; i++){
    std::map<std::string, int> inner_dict;
    inner_dict.insert(std::pair<std::string, int>("first", 1));
    inner_dict.insert(std::pair<std::string, int>("second", 2));
    inner_dict.insert(std::pair<std::string, int>("third", 3));
    outer_vector.insert(outer_vector.begin() + i, inner_dict);
}
return outer_vector;}

My interface file contains this


%module wrapper
%{
#include "wrapper.h"
#include <cstdio>
#include <cstdlib>
#include <sys/stat.h>
#include <map> 
#include <vector>
#define SWIG_PYTHON_STRICT_BYTE_CHAR // Swig is giving const char while wrapping bytes.
%}

%include "std_vector.i"
%include "std_map.i"
%include "std_pair.i"
%include "std_wstring.i" // Use this for abby wchar dependency
%include "std_string.i" // Get C++ string
%include "./lib/wrapper.h"


%typemap(out) std::vector< std::map< std::string,int,std::less< std::string >,std::allocator< std::pair< std::string const,int > > >,std::allocator< std::map< std::string,int,std::less< std::string >,std::allocator< std::pair< std::string const,int > > > > > & 
{
    for(int i = 0; i < $1->size(); ++i)
    {       
        int subLength = $1->data()[i].size();
        npy_intp dims[] = { subLength };
        PyObject* temp = PyArray_SimpleNewFromData(1, dims, NPY_INT, $1->data()[i].data());
        $result = SWIG_Python_AppendOutput($result, temp);
    }       
}

But when I call this module from python I get this:

Out[3]: <Swig Object of type 'std::vector< std::map< std::string,int,std::less< std::string >,std::allocator< std::pair< std::string const,int > > >,std::allocator< std::map< std::string,int,std::less< std::string >,std::allocator< std::pair< std::string const,int > > > > > *' at 0x7f4025d8e810>

Solution

  • You do need typemaps if you want the return value to be an actual Python list of Python dicts, but the included pre-defined templates might work for you. Here's an example:

    test.i:

    %module test
    
    %{
    #include <vector>
    #include <map>
    #include <string>
    
    std::vector<std::map<std::string, int>> func() {
        std::vector<std::map<std::string, int>> outer_vector;
        for(int i = 0 ; i < 10 ; i++){
            std::map<std::string, int> inner_dict;
            inner_dict.insert(std::pair<std::string, int>("first", 1));
            inner_dict.insert(std::pair<std::string, int>("second", 2));
            inner_dict.insert(std::pair<std::string, int>("third", 3));
            outer_vector.insert(outer_vector.begin() + i, inner_dict);
        }
        return outer_vector;
    }
    %}
    
    // These declare the templates
    %include <std_vector.i>
    %include <std_map.i>
    %include <std_string.i>
    
    // You must declare the template instances used so SWIG builds wrappers for them.
    // Declare the inner templates before the outer ones.
    %template(SiMap) std::map<std::string,int>;
    %template(VectorSiMap) std::vector<std::map<std::string,int>>;
    
    // Tell SWIG to wrap the function.
    std::vector<std::map<std::string, int>> func();
    

    Use case:

    >>> import test
    >>> d=test.func()
    >>> len(d)
    10
    >>> d
    (<test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA68E0B10> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA712A570> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA692A750> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA7110750> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA7110480> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA71105A0> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA71107E0> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA7110840> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA7110900> >, <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA71109F0> >)
    

    It doesn't look pretty, but can be converted to Python list/dicts with:

    >>> [dict(x) for x in d]
    [{'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}, {'first': 1, 'second': 2, 'third': 3}]
    

    It can also still be accessed like a list or dict, even without conversion to print nicely as above:

    >>> d[0]
    <test.SiMap; proxy of <Swig Object of type 'std::map< std::string,int > *' at 0x0000028EA68E0B10> >
    >>> d[0]['first']
    1
    >>> d[1]['second']
    2
    

    The names used in the templates can be used to build objects to pass to C++ code as well:

    >>> m = test.SiMap()
    >>> m['first'] = 2
    >>> v = test.VectorSiMap()
    >>> v.push_back(m)
    >>> v
    <test.VectorSiMap; proxy of <Swig Object of type 'std::vector< std::map< std::string,int,std::less< std::string >,std::allocator< std::pair< std::string const,int > > > > *' at 0x0000028EA71381E0> >
    >>> [dict(x) for x in v]
    [{'first': 2}]