在 swig 生成的 python 接口中使用 std::vector 成员变量作为列表
Use std::vector member variable as list in swig generated python interface
我正在使用 SWIG 为 C++ 库创建 python 包装器。该库有一些 public std::vector 成员变量,我想将它们用作 python 中的列表。但是,我一直无法找到有效的解决方案。下面是一个简化的示例,它说明了我当前的解决方案:
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(.size());
for(int i = 0; i < .size(); ++i)
PyList_SET_ITEM(tmp,i,PyLong_FromLong([i]));
$result = SWIG_Python_AppendOutput($result,tmp);
%}
我需要做什么才能将 my_data 用作 python 中的普通列表?类型映射是可行的方法吗?如果是,它会是什么样子?
如果没有%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
[]
我正在使用 SWIG 为 C++ 库创建 python 包装器。该库有一些 public std::vector 成员变量,我想将它们用作 python 中的列表。但是,我一直无法找到有效的解决方案。下面是一个简化的示例,它说明了我当前的解决方案:
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(.size());
for(int i = 0; i < .size(); ++i)
PyList_SET_ITEM(tmp,i,PyLong_FromLong([i]));
$result = SWIG_Python_AppendOutput($result,tmp);
%}
我需要做什么才能将 my_data 用作 python 中的普通列表?类型映射是可行的方法吗?如果是,它会是什么样子?
如果没有%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
[]