![](/img/trans.png)
[英]How to pass a default numpy array argument to a function in pybind11?
[英]How to expose an array of opaque type with pybind11 and NumPy
使用pybind11 ,如何使用Z3B7F949B2343F9E5390 E29F6EF5E1778Z 公開一個 POD 結構數組,同時讓它們在用戶看來是不錯的 Python 對象?
我正在使用pybind11將C 樣式 API暴露給Python 。 有一些類型,在 C 中實現為簡單的 POD 結構,在 Python 中作為不透明對象更有意義。 pybind11允許我這樣做並定義 object 在 Python 中的樣子。
我還想公開一個動態分配的數組。 使用pybind11和NumPy這樣做是可能的,但我還沒有找到一種與我已經公開類型本身的方式兼容的方法。
我最終得到了兩種不同的 Python 類型,它們彼此不兼容,即使底層 C 類型相同。
我正在尋找一種不涉及不必要副本的解決方案。 由於所有數據都是 POD,我認為應該可以將數據重新解釋為 C 端的結構或 Python 端的不透明對象。
C API 是固定的,但我可以自由選擇如何設計 Python ZDB974238714CA8DE6347ACE。
在 C/C++ 端,類型如下所示:
struct apiprefix_opaque_type
{
int inner_value;
};
使用pybind11 ,我將結構暴露為不透明的 object。 不暴露inner_value
並不重要,但它對用戶沒有太大的價值,擁有更高級別的類型更有意義。
namespace py = pybind11;
void bindings(py::module_& m)
{
py::class_<apiprefix_opaque_type>(m, "opaque_type")
.def(py::init([]() {
apiprefix_opaque_type x;
x.inner_value = -1;
return x;
}))
.def("is_set", [](const apiprefix_opaque_type& x) -> bool { return x.inner_value != -1; });
m.def("create_some_opaque", []() -> apiprefix_opaque_type {
apiprefix_opaque_type x;
x.inner_value = 42;
return x;
});
}
有了這個,在 Python 方面,我有我想要的 API 行為。
>>> a = apitest.opaque_type()
>>> a.inner_value # Demonstrating that inner_value is not exposed.
AttributeError: 'apitest.opaque_type' object has no attribute 'inner_value'
>>> a.is_set()
False
>>> b = apitest.create_some_opaque()
>>> b.is_set()
True
在 API 的其他地方,我有一個包含這些數組的結構,作為指針和計數對。 為了簡單起見,讓我們假設它是一個全局變量(盡管實際上它是另一個動態分配對象的成員)。
struct apiprefix_state
{
apiprefix_opaque_type* things;
int num_things;
};
apiprefix_state g_state = { nullptr, 0 };
這個數組足夠大,我關心性能。 因此,我限制避免不必要的副本。
從 Python 開始,我希望能夠讀取數組、修改數組或完全替換數組。 我認為如果最后設置數組的人保留對它的所有權會更有意義,但我並不完全確定。
這是我目前使用NumPy公開數組的嘗試。
void more_bindings(py::module_& m)
{
py::class_<apiprefix_state>(m, "state")
.def(py::init([]() {
return g_state;
}))
.def("create_things",
[](apiprefix_state&, int size) -> py::array {
auto arr = py::array_t<apiprefix_opaque_type>(size);
return std::move(arr);
})
.def_property(
"things",
[](apiprefix_state& state) {
auto base = py::array_t<apiprefix_opaque_type>();
return py::array_t<apiprefix_opaque_type>(state.num_things, state.things, base);
},
[](apiprefix_state& state, py::array_t<apiprefix_opaque_type> things) {
state.things = nullptr;
state.num_things = 0;
if (things.size() > 0)
{
state.num_things = things.size();
state.things = (apiprefix_opaque_type*)things.request().ptr;
}
});
}
鑒於我對 Python 中的 memory 管理的初步了解,我強烈懷疑所有權沒有正確實施。
但是這個問題的問題是,NumPy 不明白apiprefix_opaque_type
是什么。
>>> state = apitest.state()
>>> state.things
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: NumPy type info missing for struct apiprefix_opaque_type
>>>
如果我添加一個 dtype 聲明......
PYBIND11_NUMPY_DTYPE(apiprefix_opaque_type, inner_value);
...現在 NumPy 理解它,但現在有兩個不兼容的 Python 類型指的是相同的 C 類型。 此外,還公開了實現細節inner_value
。
>>> state = apitest.state()
>>> state.things
array([], dtype=[('inner_value', '<i4')])
>>> state.things = state.create_things(10)
>>> a = apitest.opaque_type()
>>> a
<apitest.opaque_type object at 0x000001BABE6E72B0>
>>> state.things[0] = a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'apitest.opaque_type'
>>>
如何公開我的不透明對象數組?
如果您只想公開一系列事物,那么您可以做這樣的事情
apiprefix_opaque_type& apiprefix_state_get(apiprefix_state& s, size_t j)
{
return s.things[j];
}
void apiprefix_state_set(apiprefix_state& s, size_t j, const apiprefix_opaque_type& o)
{
s.things[j] = o;
}
py::class_<apiprefix_state>(m, "state")
// ...
.def("__getitem__", &apiprefix_state_get)
.def("__setitem__", &apiprefix_state_set)
添加范圍檢查顯然是個好主意。 (你可以使用 lambdas,我只是發現顯式函數更具可讀性)。
當您將things
包裝在 numpy 數組中時,您將其公開為緩沖區,並且結構化 dtype 僅提供有關應將哪些偏移量處的哪些字節解釋為int
s的信息。 因此,您實際上可以在上面編寫state.things[0] = 42
(更一般地,對於具有多個成員的結構,您可以分配一個元組)。 但它不知道如何從apiprefix_opaque_type
中提取一個int
以將其分配給 dtype 定義的字段。
如果您想將things
公開為 numpy 陣列,那么正如您所指出的,所有權是一個重要問題。 如上所述,python 將擁有由create_things
創建的任何 arrays 並管理底層 memory。 但是,您的二傳手有幾個問題。 第一的
state.things = nullptr;
state.num_things = 0;
如果 state.things 所指向的state.things
不是由 Z23EEEB7347BDD25DFCA936 管理,則可能是 memory 泄漏。 其次在這一行
state.things = (apiprefix_opaque_type*)things.request().ptr;
you are referencing memory managed by python without reference counting , so there is a chance the apiprefix_state
will be left with things
pointing to memory that python has garbage collected.
看起來您可能想要公開可能由 C++ 管理的全局g_state
。 在這種情況下,一種可能的方法是
pybind11::capsule nogc(values, [](void *f) {});
return pybind11::array_t<apiprefix_opaque_type>(
{ g_state.num_things },
{ sizeof(apiprefix_opaque_type) },
g_state.things,
nogc
);
或者,您可以直接使用緩沖協議或 memory 視圖。
如果您確實想始終引用全局 state 則從初始化程序返回它是不尋常的
.def(py::init([]() { return g_state; }))
通常它會像
.def_static("get_instance", ... )
但請注意,這並不完全符合您的要求,因為它會復制g_state
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.