Pybind11: создать простое представление данных

Я хочу создать numpy представление данных в классе С++.

Но следующее делает копию вместо представления.

Тест на питоне:

import _cpp
a = _cpp.A()
print(a)
a.view()[:] = 100  # should make it all 100.
print(a)

Результат:

40028064 0 0 0  // Fail: Modifying a.mutable_data() in C++ doesn't 
                //       change _data[4]
40028064 0 0 0  // Fail: Modifying a.view() in Python 3 doesn't 
                //       change data in a

Строка C++ a.mutable_data()[0] = -100; не изменяет 0-й элемент на -100. Это показывает, что py::array_t<int> a(4, &_data[0]); создает копию вместо представления int _data[4];

Изменение массива a.view() не изменяет данные в a на 100 секунд. Это показывает, что a.view() является копией, а не представлением данных в a.

основной.cpp:

#include <iostream>
#include "pybind11/pybind11.h"
#include "pybind11/numpy.h"

namespace py = pybind11;
class A {
public:
    A() {}
    std::string str() {
        std::stringstream o;
        for (int i = 0; i < 4; ++i) o << _data[i] << " ";
        return o.str();
    }
    py::array view() {
        py::array_t<int> a(4, &_data[0]);
        a.mutable_data()[0] = -100;
        return a;
    }
    int _data[4];
};

PYBIND11_MODULE(_cpp, m) {
    py::class_<A>(m, "A")
        .def(py::init<>())
        .def("__str__", &A::str)
        .def("view", &A::view, py::return_value_policy::automatic_reference);
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.9)
project(test_pybind11)

set(CMAKE_CXX_STANDARD 11)

# Find packages.
set(PYTHON_VERSION 3)
find_package( PythonInterp ${PYTHON_VERSION} REQUIRED )
find_package( PythonLibs ${PYTHON_VERSION} REQUIRED )

# Download pybind11
set(pybind11_url https://github.com/pybind/pybind11/archive/stable.zip)

set(downloaded_file ${CMAKE_BINARY_DIR}/pybind11-stable.zip)
file(DOWNLOAD ${pybind11_url} ${downloaded_file})
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${downloaded_file}
        SHOW_PROGRESS)
file(REMOVE ${downloaded_file})

set(pybind11_dir ${CMAKE_BINARY_DIR}/pybind11-stable)
add_subdirectory(${pybind11_dir})
include_directories(${pybind11_dir}/include)

# Make python module
pybind11_add_module(_cpp main.cpp)

person R zu    schedule 08.03.2018    source источник


Ответы (1)


Следуя комментарию в выпуске 308, в котором говорится py::cast(self), я пробую py::cast(*this).

Это работает. Меня немного беспокоит аннулирование представлений, но numpy делает то же самое.

Тест Python:

import _cpp
import numpy as np
a = _cpp.A()
print(a)
a.view()[:] = 100  # should make it all 100.
print(a)

Результат испытаний:

1480305816 32581 19420784 0 // original data of `a`
100 100 100 100 // It works: changing `a.view()` changes data of `a`.

основной.cpp:

#include <iostream>
#include "pybind11/pybind11.h"
#include "pybind11/numpy.h"

namespace py = pybind11;
class A {
public:
    A() {}
    std::string str() {
        std::stringstream o;
        for (int i = 0; i < 4; ++i) o << _data[i] << " ";
        return o.str();
    }
    py::array view() {
        return py::array(4, _data, py::cast(*this));  // <---
    }
    int _data[4];
};

PYBIND11_MODULE(_cpp, m) {
    py::class_<A>(m, "A")
        .def(py::init<>())
        .def("__str__", &A::str)
        .def("view", &A::view, py::return_value_policy::reference_internal);
}

Я использую reference_internal, чтобы связать время жизни a.view() со временем жизни a.


Представление становится недействительным при удалении родительского объекта.

После удаления a в тесте python python соберет данные a в неопределенное время. Это означает, что если я ранее сохранял представление b = a.view(), b становится недействительным после удаления a.

Я пытаюсь сделать a._data пустым массивом на стороне С++, но это не помогает аннулированию.

основной.cpp:

class A {
public:
    A() : _data(4, new int[4]) {}
    std::string str() {
        std::stringstream o;
        for (int i = 0; i < 4; ++i) o << _data.data()[i] << " ";
        return o.str();
    }
    py::array view() {
        return py::array(4, _data.data(), py::cast(*this));
    }
    py::array_t<int> _data;
};

Тест Python:

import _cpp
import numpy as np
a = _cpp.A()
print(a)
a.view()[:] = 100  # should make it all 100.
b = a.view()
print('b is base?', b.base is None)
del a
print('b is base after deleting a?', b.base is None)

c = np.zeros(4)
print('c is base?', c.base is None)
d = c.view()
print('d is base?', d.base is None)
del c
print('d is base after deleting c?', d.base is None)

Результат:

-6886248 32554 16092080 0 
// c++ code's management of views
b is base? False
b is base after deleting a? False
// numpy's management of views
c is base? True
d is base? False
d is base after deleting c? False

Похоже, что при удалении базового массива numpy право собственности на память не передается ни одному из представлений. То же самое верно и для класса C++. Думаю, я буду придерживаться предыдущего решения.

person R zu    schedule 08.03.2018
comment
Это работает, и это полный ответ. Представление становится недействительным при удалении исходного объекта a. Но представление numpy кажется недействительным при том же условии. Так что я думаю, это нормально. - person R zu; 10.03.2018
comment
Я добавил несколько слов в ответ, чтобы пояснить, почему это работает сейчас. надеюсь теперь более понятно. - person R zu; 10.03.2018