Используйте переменную-член std::vector в качестве списка в интерфейсе python, сгенерированном swig

#python #c #swig

Вопрос:

Я использую SWIG для создания оболочек python для библиотеки C . В библиотеке есть несколько общедоступных переменных-членов std::vector, которые я хотел бы использовать в качестве списков в python. Тем не менее, я не смог найти решение, которое работает. Ниже приведен упрощенный пример, иллюстрирующий мое текущее решение:

пример.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;
    }
}
 

пример.я

 %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 невозможно. Если я удалю %naturalvar из примера.i, я могу использовать методы списка, такие как добавление в my_data. Однако затем я получаю сообщение об ошибке, если пытаюсь назначить переменную списку python (поскольку тип my_data является прокси-сервером объекта swig).

Я попытался добавить карту типов в файл example.i. Затем метод get_vec вернул список python, но тип my_data не изменился. Это была типовая карта, которую пытались добавить в пример.я

 %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);
%}
 

Что мне нужно сделать, чтобы иметь возможность использовать my_data в качестве обычного списка в python? Можно ли использовать типографские карты, и если да, то как это будет выглядеть?

Ответ №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
[]
 

Комментарии:

1. Если я не смогу заставить что-то еще работать, мне, возможно, придется остановиться на этом решении. Что мне в этом не нравится, так это то, что вы получаете сообщение об ошибке, например, при попытке присвоить список my_data x.my_data = [5, 6, 7] . Я знаю , что вы можете обойти это, написав x.my_data = e.int_vector([5, 6, 7]) , но тогда пользователи библиотеки должны знать об этом «нестандартном» способе назначения.

2. @larsjr Вы всегда можете обернуть результат в больший код Python, чтобы придать ему более питонический интерфейс. См.Обновление.