【问题标题】:Use std::vector member variable as list in swig generated python interface在 swig 生成的 python 接口中使用 std::vector 成员变量作为列表
【发布时间】:2021-07-08 19:09:43
【问题描述】:

我正在使用 SWIG 为 C++ 库创建 python 包装器。该库有一些我想在 python 中用作列表的公共 std::vector 成员变量。但是,我还没有找到可行的解决方案。下面是一个简化的示例,说明了我当前的解决方案:

example.h

#pragma once

#include <vector>

#if defined _WIN32
    #define EXPORT __declspec(dllexport)
#else
    #define EXPORT
#endif

namespace Example {
    class EXPORT MyClass {
        public:
        std::vector<int> my_data{1, 2, 3};
        std::vector<int> get_vec();
    };
}

example.cpp

#include "example.h"

namespace Example {
    std::vector<int> MyClass::get_vec() {
        std::vector<int> v{1, 3, 5};
        return v;
    }
}

example.i

%module example

%include "stdint.i"
%include "std_vector.i"

%naturalvar;

%{
#include <example.h>
%}

%template(int_vector) std::vector<int>;
%include <example.h>

我还附上了我使用的 CMakeLists.txt 文件,以防有人想要构建项目。

cmake_minimum_required(VERSION 3.15)
project(CMakeSwigExample LANGUAGES CXX)

include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_library(example_cpp SHARED example.h example.cpp)
target_compile_features(example_cpp PUBLIC cxx_std_17)

if (UNIX)
set_target_properties(example_cpp PROPERTIES INSTALL_RPATH "$ORIGIN")
endif()

FIND_PACKAGE(SWIG REQUIRED)
INCLUDE(${SWIG_USE_FILE})

FIND_PACKAGE(PythonLibs)

SET(CMAKE_SWIG_FLAGS "")

SET_SOURCE_FILES_PROPERTIES(example.i PROPERTIES CPLUSPLUS ON)
set_property(SOURCE itnfileio.i PROPERTY SWIG_MODULE_NAME example)

SWIG_ADD_LIBRARY(example 
    TYPE SHARED
    LANGUAGE python
    SOURCES example.i)

target_include_directories(example
    PRIVATE 
    ${PYTHON_INCLUDE_DIRS})

target_link_libraries(example PRIVATE example_cpp ${PYTHON_LIBRARIES})

这是一个如何在 python 中使用生成的包装器的示例

>>> from example import MyClass
>>> m = MyClass()
>>> m.get_vec()
(1, 3, 5)
>>> m.my_data
(1, 2, 3)
>>> m.my_data = [9, 8, 7]
>>> m.my_data.append(6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'

这与我想要的很接近,但是由于 std_vector.i 将 std::vector 转换为元组,因此无法将元素附加到 my_data 变量。如果我从 example.i 中删除 %naturalvar,我可以使用列表方法,例如在 my_data 上附加。但是,如果我尝试将变量分配给 python 列表,则会收到一条错误消息(因为 my_data 的类型是 swig 对象的代理)。

我尝试将类型映射添加到 example.i 文件。然后get_vec方法返回了一个python列表,但是my_data的类型并没有改变。这是试图添加到 example.i

的类型图
%typemap(out) std::vector<int> (PyObject* tmp) %{

    tmp = PyList_New($1.size());
    for(int i = 0; i < $1.size(); ++i)
        PyList_SET_ITEM(tmp,i,PyLong_FromLong($1[i]));
    $result = SWIG_Python_AppendOutput($result,tmp);
%}

我需要做什么才能在 python 中将 my_data 用作普通列表?类型映射是要走的路吗?如果是,它会是什么样子?

【问题讨论】:

    标签: python c++ swig


    【解决方案1】:

    没有%naturalvar,这对你来说可能是可以接受的:

    >>> import example as e
    >>> x=e.MyClass()
    >>> x.my_data
    <example.int_vector; proxy of <Swig Object of type 'std::vector< int > *' at 0x0000029A09E95510> >
    >>> x.my_data.push_back(5)
    >>> list(x.my_data)
    [1, 2, 3, 5]
    

    如果您需要更强大的解决方案,可以使用%pythoncode 围绕 SWIG 包装器实现更自然的包装器:

    %module test
    
    %{
    #include <vector>
    #include <sstream>
    #include <string>
    
    class MyClass {
    public:
        std::vector<int> my_data{1, 2, 3};
        std::vector<int> get_vec() {
            std::vector<int> v{1, 3, 5};
            return v;
        }
    };
    %}
    
    %include <std_vector.i>
    %include <std_string.i>
    %template(vint) std::vector<int>;
    
    %rename(_MyClass) MyClass;  # Rename the original class
    
    class MyClass {
    public:
        std::vector<int> my_data;
        std::vector<int> get_vec();
    };
    
    %pythoncode %{
    
    # Wrap the my_data member so append/pop work on the actual member.
    class MyClassMyData:
    
        def __init__(self,myclass):
            self._myclass = myclass
    
        def append(self,value):
            self._myclass.my_data.push_back(value)
    
        def pop(self):
            return self._myclass.my_data.pop()
    
        def __repr__(self):
            return repr(list(self._myclass.my_data))
    
    # Expose the get_vec() and my_data the way you want
    class MyClass:
    
        def __init__(self):
            self._myclass = _MyClass()
    
        def get_vec(self):
            return list(self._myclass.get_vec())  # return a list instead of tuple
    
        @property
        def my_data(self):
            return MyClassMyData(self._myclass)  # return the wrapper
    
        @my_data.setter
        def my_data(self,value):
            self._myclass.my_data = vint(value)  # allows list assignment
    %}
    

    输出:

    >>> import test
    >>> t=test.MyClass()
    >>> x=t.get_vec()     # returns a list now
    >>> x
    [1, 3, 5]
    >>> x.append(4)       # it's a Python list, so append/pop work
    >>> x
    [1, 3, 5, 4]
    >>> x.pop()
    4
    >>> t.my_data             # actually a MyClassMyData proxy but displays as list
    [1, 2, 3]
    >>> t.my_data.append(4)   # using proxy append
    >>> t.my_data
    [1, 2, 3, 4]
    >>> t.my_data = [1]       # proxy property setter works
    >>> t.my_data
    [1]
    >>> t.my_data.pop()       # as does proxy pop
    1
    >>> t.my_data
    []
    

    【讨论】:

    • 如果我不能让任何其他工作,我可能不得不解决这个解决方案。我不喜欢的是,如果您尝试将列表分配给 my_data,则会收到错误消息,例如 x.my_data = [5, 6, 7]。我知道你可以通过写x.my_data = e.int_vector([5, 6, 7]) 来解决这个问题,但是图书馆的用户需要注意这种“非标准”的分配方式。
    • @larsjr 您总是可以将结果包装在更多 Python 代码中,以提供更 Pythonic 的界面。查看更新。
    猜你喜欢
    • 1970-01-01
    • 2021-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    • 2010-09-21
    相关资源
    最近更新 更多