Можете ли вы создать класс Python через pybind11?

В настоящее время при работе с python + pybind11 мне неприятно работать с типизированными классами/структурами С++.

Я хотел бы изменить свои привязки, чтобы они генерировали простой класс Python с __init__ и простой функцией, как показано ниже. Что-то похожее на осуществимое?

Обоснование:
В настоящее время у меня есть структура, которую я генерирую с помощью C++, но в ней есть много тяжелых std::vector<float>, которые я хотел бы передать в python и сохранить в виде массивов numpy внутри аналогичного класса интерфейса python. (бонусные баллы, если вы можете сказать мне, как быстро перемещать векторы в массивы numpy!)

Я уже полностью связал свою структуру С++ с pybind11, поэтому мне кажется, что я знаю, что делаю... однако я не могу понять, возможно ли это!

Итак, в качестве учебного упражнения, могу ли я создать следующий класс Python через pybind11?

>>> python
class MyStruct:
    def __init__(self, A_in, descriptor_in):
        self.A = A_in
        self.descriptor = descriptor_in

    def add_to_vec(f_in):
        self.A.append(f_in)
<<< python

Редактировать: я хочу сказать, что я «думаю», что это выполнимо с API-интерфейсом Python C, но я бы хотел избежать использования этого напрямую, если смогу. (но если вы думаете, что это единственный способ, пожалуйста, дайте мне знать:))

Edit2: (ответ @Erwan)
Единственный известный мне способ получить переменные класса по отдельности - это (показано ниже). Вы не можете использовать рекламируемый pybind интерфейс buffer_protocol, если у вас есть более одного массива numpy в структуре, которую вы хотите получить. Однако для этого требуется создать только функцию .def интерфейса python (не идеальную), которая указывает (что, как я думаю, является копией) исходных данных (так что это вероятно медленно, я не проверял это, но я не уверен, был ли это взлом или правильный способ получить векторы в массивы numpy).

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <vector>
#include <string>


struct Pet {
    Pet(const std::string &name) : name(name) { 
            bdata.push_back(22.);
            bdata.push_back(23.1);
            bdata.push_back(24.);
            bdata.push_back(2222.);
        }
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }

    std::string name;
    std::vector<float> bdata;
};


namespace py = pybind11;

PYBIND11_MODULE(example, m) {
    py::class_<Pet>(m, "Pet")
            .def(py::init<const std::string &>())
            .def("setName", &Pet::setName)
            .def("getName", &Pet::getName)
            .def("bdata", [](Pet &m) -> py::array {
                    py::buffer_info buff_info(py::buffer_info(
                            m.bdata.data(),                               /* Pointer to buffer */
                            sizeof(float),                          /* Size of one scalar */
                            py::format_descriptor<float>::format(), /* Python struct-style format descriptor */
                            m.bdata.size()                                      /* Number of dimensions */
                    ));
                    return py::array(buff_info);
            });
}

person Mr. Buttons    schedule 10.09.2019    source источник
comment
pybind11 поддерживает преобразование stl std::vector-›list, и после этого вы можете преобразовать список в массив numpy. если вы предпочитаете вектор прямого преобразования вместо numpy, вам придется использовать интерфейс буфера, описанный здесь pybind11.readthedocs.io/en/master/advanced/pycpp/numpy.html   -  person Erwan    schedule 10.09.2019
comment
@Erwan Я ответил на ваш комментарий в исходном сообщении.   -  person Mr. Buttons    schedule 10.09.2019
comment
Если я правильно понял ваш вопрос, вы хотите определить класс на стороне Python, а не на стороне C++? для преобразования между numpy и vector вы можете определить заклинатель типа pybind11.readthedocs .io/en/stable/advanced/cast/custom.html кстати, я думаю, что преобразование скопирует данные   -  person Erwan    schedule 10.09.2019
comment
Если вы хотите проверить, у меня есть пример преобразования между opencv Mat и массивом numpy.   -  person Erwan    schedule 10.09.2019
comment
пожалуйста! пример был бы очень хорош. До этого я несколько раз читал документацию по заклинателям типов, и это никогда не имело для меня никакого смысла.   -  person Mr. Buttons    schedule 11.09.2019
comment
извините, забыл ссылку github.com/edmBernard/pybind11_opencv_numpy   -  person Erwan    schedule 11.09.2019


Ответы (1)


Я не понимаю вашего вопроса в целом, но я возьму эту часть:

бонусные баллы, если вы можете сказать мне, как быстро перемещать векторы в массивы numpy!

Если вы используете возвращаемый результат bdata.data() в сочетании с numpy.frombuffer() и bdata.size(), если это необходимо, вы можете получить представление о векторных данных, которые гарантированно будут непрерывными, начиная с C++ 11. (Обычный вызов numpy.array() не будет учитывать copy=False в этом случае, но frombuffer действует как приведение.) Поскольку копии нет, это, вероятно, так же быстро, как и получается.

Ниже приведен пример в cppyy (который позволяет легко тестировать, но использование которого в остальном не имеет значения для ответа на вопрос, как смешивать std::vector и numpy.array как таковые). Соус находится в последних нескольких строках: обновление «arr» будет отображаться в исходном векторе (и v.v.), b/c frombuffer — это представление, а не копия:

import cppyy
import numpy as np

# load struct definition
cppyy.cppdef("""
struct Pet {
    Pet(const std::string &name) : name(name) {
        bdata.push_back(22.);
        bdata.push_back(23.1);
        bdata.push_back(24.);
        bdata.push_back(2222.);
    }
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }

    std::string name;
    std::vector<float> bdata;
};""")

# create a pet object
p = cppyy.gbl.Pet('fido')

print(p.bdata[0]) # for reference (prints 22, per above)

# create a numpy view on the std::vector's data
#   add count=p.bdata.size() if need be
arr = np.frombuffer(p.bdata.data(), dtype=np.float32)

# prove that it worked as intended
arr[0] = 14
print(p.bdata[0]) # shows update to 14
p.bdata[2] = 17.5
print(arr[2])     # shows update to 17.5

который будет печатать:

22.0
14.0
17.5

'arr' может стать недействительным, если размер std::vector изменится. Однако, если вы знаете максимальный размер, и он не слишком велик или наверняка будет полностью использован, вы можете зарезервировать его, чтобы внутренние данные вектора не были перераспределены.

В зависимости от того, как и где вы храните массив numpy, я также рекомендую привязать время жизни «p» (и, следовательно, «p.bdata») к «arr», например. сохраняя их обоих в качестве членов данных в экземпляре класса-оболочки, который вам нужен.

Если вместо этого вы хотите выполнить преобразование на C++, используйте PyArray_FromBuffer из API массива NumPy.

person Wim Lavrijsen    schedule 11.09.2019