使用SWIG将向量作为内存视图 [英] Expose a vector as a memoryview using SWIG
问题描述
我有一个头文件如:
#include< vector&
inline std :: vector< uint8_t>& vec(){
static std :: vector< uint8_t> v {'a','b','c','d'};
return v;
}
inline const std :: vector< uint8_t>& cvec(){
return vec();
}
我可以使用std_vector.i和pyabc.i在SWIG中包装它,但是这是非常低效的(有一个跳C ++和Python代码为每个访问),并且假定这些字面上只是一堆字节我应该能够包装他们与 Python的 memoryview
界面。
std :: vector< uint8_t>
作为Python memoryview
?
Py_buffer
。在Python 3.3+中有一个方便的帮助函数, PyMemoryView_FromMemory
为我们做了很多工作。在早期版本中,虽然我们需要额外的一些步骤,所以我们的基本输出框如下: %typemap(out)std :: vector< uint8_t>& const std :: vector< uint8_t> {
Py_buffer * buf =(Py_buffer *)malloc(sizeof * buf);
const bool ro = info< $ 1_type> :: is_readonly();
if(PyBuffer_FillInfo(buf,NULL,&((* $ 1)[0]),(* $ 1).size(),ro,PyBUF_ND)){
//错误,句柄
}
$ result = PyMemoryView_FromBuffer(buf);
}
这里我们基本上为 Py_buffer
。这只包含Python内部缓冲区的详细信息。我们分配的内存将由 memoryview
对象创建后拥有。不幸的是,因为它将通过调用 free()
释放,我们需要用 malloc()
除了 Py_buffer
和一个可选的 Py_Object
PyBuffer_FillInfo
需要一个 void *
(缓冲区本身),缓冲区的大小,一个布尔值,表示它是否可写和一个标志。在这种情况下,我们的标志只是表示我们为缓冲区提供了C型连续内存。
为了决定它是否只读,我们使用SWIG的内置 $ n_type
变量和助手(可能如果我们想要一个C ++ 11类型trait)。
要完成我们的SWIG接口,我们需要提供该帮助并包括头文件,所以整个事情变得:
%module test
%{
#includetest.hh
namespace {
template< typename T>
struct info {
static bool is_readonly(){
return false;
}
};
template< typename T>
struct info< const T&> {
static bool is_readonly(){
return true;
}
};
}
%}
%typemap(out)std :: vector< uint8_t>& const std :: vector< uint8_t> {
Py_buffer * buf =(Py_buffer *)malloc(sizeof * buf);
const bool ro = info< $ 1_type> :: is_readonly();
if(PyBuffer_FillInfo(buf,NULL,&((* $ 1)[0]),(* $ 1).size(),ro,PyBUF_ND)){
//错误,句柄
}
$ result = PyMemoryView_FromBuffer(buf);
}
%includetest.hh
然后可以用以下方法测试它:
import test
print test.vec()
打印len(test.vec())
打印test.vec()[0]
打印test.vec()。readonly
test.vec()[0]
print test.vec()[0]
print这应该失败:
test.cvec()[0] = 0
它的工作原理是使用Python 2.7测试。
它使用std_vector.i这种方法确实有一些缺点。最大的是,我们不能调整矢量的大小,或将其转换回一个矢量后来微不足道。我们可以解决这个问题,至少部分地通过创建像正常的向量的SWIG代理,并使用
PyBuffer_FillInfo
的第二个参数在内部存储它。 (如果我们必须管理向量的所有权,这也将是需要的)。I have a header file like:
#include <vector> inline std::vector<uint8_t>& vec() { static std::vector<uint8_t> v { 'a', 'b', 'c', 'd' }; return v; } inline const std::vector<uint8_t>& cvec() { return vec(); }
I can wrap it in SWIG using std_vector.i and pyabc.i but that is quite inefficient (there's a jump between C++ and Python code for every access) and given that these are literally just a bunch of bytes I ought to be able to wrap them with Python's
memoryview
interface.How can I expose my
std::vector<uint8_t>
as a Pythonmemoryview
?解决方案Exposing it as a
memoryview
requires creating aPy_buffer
first. In Python 3.3+ there is a convenient helper function,PyMemoryView_FromMemory
that does a lot of the work for us. In earlier versions though we'll need to take a few extra steps, so our basic out typemap looks like:%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& { Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf); const bool ro = info<$1_type>::is_readonly(); if (PyBuffer_FillInfo(buf, NULL, &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) { // error, handle } $result = PyMemoryView_FromBuffer(buf); }
Here we're basically allocating some memory for the
Py_buffer
. This just contains the details of the buffer internally for Python. The memory we allocate will be owned by thememoryview
object once it's created. Unfortunately since it's going to be released with a call tofree()
we need to allocate it withmalloc()
, even though it's C++ code.Besides the
Py_buffer
and an optionalPy_Object
PyBuffer_FillInfo
takes avoid*
(the buffer itself), the size of the buffer, a boolean indicating if it's writeable and a flag. In this case our flag simply indicates that we have provided C-style contiguous memory for the buffer.For deciding if it is readonly or not we used SWIG's built in
$n_type
variable and a helper (which could be a C++11 type trait if we wanted).To complete our SWIG interface we need to provide that helper and include the header file, so the whole thing becomes:
%module test %{ #include "test.hh" namespace { template <typename T> struct info { static bool is_readonly() { return false; } }; template <typename T> struct info<const T&> { static bool is_readonly() { return true; } }; } %} %typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& { Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf); const bool ro = info<$1_type>::is_readonly(); if (PyBuffer_FillInfo(buf, NULL, &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) { // error, handle } $result = PyMemoryView_FromBuffer(buf); } %include "test.hh"
We can then test it with:
import test print test.vec() print len(test.vec()) print test.vec()[0] print test.vec().readonly test.vec()[0]='z' print test.vec()[0] print "This should fail:" test.cvec()[0] = 0
Which worked as expected, tested using Python 2.7.
Compared to just wrapping it using std_vector.i this approach does have some drawbacks. The biggest being that we can't resize the vector, or convert it back to a vector later trivially. We could work around that, at least partially by creating a SWIG proxy for the vector like normal and using the second parameter of
PyBuffer_FillInfo
to store it internally. (This would also be needed if we had to manage the ownership of the vector for instance).这篇关于使用SWIG将向量作为内存视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!