一些评论(然后是一个有效的实现)。
- pybind11 的 C++ 对象包装器围绕 Python 类型(例如
pybind11::object
, pybind11::list
,并且,在这种情况下,pybind11::array_t<T>
)实际上只是底层 Python 对象指针的包装器。在这方面,已经承担了共享指针包装器的角色,因此将其包装在unique_ptr
: 返回py::array_t<T>
直接对象本质上已经只是返回一个美化的指针。
-
pybind11::array_t
可以直接从数据指针构造,因此您可以跳过py::buffer_info
中间步骤,只需将形状和步幅直接提供给pybind11::array_t
构造函数。以这种方式构造的 numpy 数组不会拥有自己的数据,它只会引用它(即 numpyowndata
标志将被设置为 false)。
- 内存所有权可以与 Python 对象的生命周期联系在一起,但您仍然需要正确地进行释放。 Pybind11 提供了
py::capsule
课程来帮助你做到这一点。您想要做的是将 numpy 数组指定为base
论证array_t
。这将使 numpy 数组引用它,只要数组本身还活着,它就保持活动状态,并在不再引用它时调用清理函数。
- The
c_style
旧版本(2.2 之前)中的 flag 仅对新数组有效,即在不传递值指针时。这个问题在 2.2 版本中得到了修复,如果您仅指定形状而不指定步幅,也会影响自动步幅。如果您自己直接指定步幅(就像我在下面的示例中所做的那样),它根本没有任何效果。
因此,将各个部分放在一起,此代码是一个完整的 pybind11 模块,它演示了如何完成您正在寻找的内容(并包含一些 C++ 输出以证明确实工作正常):
#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
PYBIND11_PLUGIN(numpywrap) {
py::module m("numpywrap");
m.def("f", []() {
// Allocate and initialize some data; make this big so
// we can see the impact on the process memory use:
constexpr size_t size = 100*1000*1000;
double *foo = new double[size];
for (size_t i = 0; i < size; i++) {
foo[i] = (double) i;
}
// Create a Python object that will free the allocated
// memory when destroyed:
py::capsule free_when_done(foo, [](void *f) {
double *foo = reinterpret_cast<double *>(f);
std::cerr << "Element [0] = " << foo[0] << "\n";
std::cerr << "freeing memory @ " << f << "\n";
delete[] foo;
});
return py::array_t<double>(
{100, 1000, 1000}, // shape
{1000*1000*8, 1000*8, 8}, // C-style contiguous strides for double
foo, // the data pointer
free_when_done); // numpy array references this parent
});
return m.ptr();
}
编译它并从 Python 调用它表明它可以工作:
>>> import numpywrap
>>> z = numpywrap.f()
>>> # the python process is now taking up a bit more than 800MB memory
>>> z[1,1,1]
1001001.0
>>> z[0,0,100]
100.0
>>> z[99,999,999]
99999999.0
>>> z[0,0,0] = 3.141592
>>> del z
Element [0] = 3.14159
freeing memory @ 0x7fd769f12010
>>> # python process memory size has dropped back down