C-Numpy:如何从现有数据创建固定宽度的字符串数组
Posted
技术标签:
【中文标题】C-Numpy:如何从现有数据创建固定宽度的字符串数组【英文标题】:C-Numpy: How to create fixed-width ndarray of strings from existing data 【发布时间】:2019-08-12 10:52:25 【问题描述】:我正在使用 Boost Python 用 C++ 编写 Python 扩展模块。我想将模块中的 numpy 数组返回给 Python。它适用于像 double
这样的数字数据类型,但有一次我需要从现有数据创建一个 string
数组。
对于数值数组,我使用了PyArray_SimpleNewFromData
,效果很好,但由于字符串不是固定长度,我使用PyArray_New
,我可以传入项目大小,在我的案例4中。这是一个最小的例子:
bool initNumpy()
Py_Initialize();
import_array();
return true;
class Foo
public:
Foo()
initNumpy();
data.reserve(10);
data = "Rx", "Rx", "Rx", "RxTx", "Tx", "Tx", "Tx", "RxTx", "Rx", "Tx";
PyObject* getArray()
npy_intp dims[] = data.size() ;
return (PyObject*)PyArray_New(&PyArray_Type, 1, dims, NPY_STRING, NULL, &data[0], 4, NPY_ARRAY_OWNDATA, NULL);
private:
std::vector<std::string> data;
;
我希望getArray()
的输出等于numpy.array(["Rx", "Rx" ...], dtype="S4")
的输出,即:
array([b'Rx', b'Rx', b'Rx', b'RxTx', b'Tx', b'Tx', b'Tx', b'RxTx', b'Rx',
b'Tx'], dtype='|S4')
但它看起来像这样:
array([b'Rx', b'', b'\xcc\xb3b\xd9', b'\xfe\x07', b'\x02', b'', b'\x0f',
b'', b'Rx\x00\x03', b''], dtype='|S4')
我尝试使用 npy_intp const* strides
参数,因为我认为问题在于基础数据的内存块。不幸的是,它没有达到我想要的结果。
我正在使用 Microsoft Build Tools 2017、Boost python、distutils 和 Python 3.7 来构建扩展。
【问题讨论】:
std::string
往往被构造为指向数组的指针 + 一些元数据,因此将字符串数组的位置传递给 Numpy 是毫无意义的。不过我不确定如何解决这个问题
【参考方案1】:
当使用PyArray_New
时,传递的数据必须有一个内存布局,这是 numpy-array 所期望的。 np.float64
这样的简单数据类型就是这种情况,但std::vector<std::string>
和dtype='|S4'
不是这种情况。
首先,PyArray_New
对|S4
的期望是什么内存布局?
让我们选择作为例子
array([b'Rx', b'RxTx', b'T'], dtype='|S4')
预期的内存布局将是:
| R| x|\0|\0| R| x| T| x| T|\0|\0|\0|
| | | |
|- 1. word -|- 2. word -|- 3. word -|
有以下值得注意的细节:
内存是连续且直接的。 每个元素都是 4 字节长,保存的字符串没有 NUL 终止符(参见 2.word),实际上不需要此信息。 如果一个词的长度小于 4 个字符,则剩余字符必须设置为\0
,即 NUL 字符。如果一个人想要存储带有尾随 \0
的字符串,那就不走运了 - 但这是另一回事了。
std::vector<std::string>
具有完全不同的内存布局 - 因为 std::string
的内存布局不是通过 C++ 标准规定的,所以它可以在不同的实现之间改变。
上述观察的结果是,如果字符串以std::vector<std::string>
给出,则无法复制数据。该函数由三个步骤组成:
以下是 C++11 的示例实现,其中错误处理留给读者练习:
PyObject* create_np_array(const std::vector<std::string> vals, size_t itemsize)
//1. step allocate memory
size_t mem_size = vals.size()*itemsize;
void * mem = PyDataMem_NEW(mem_size);
//ToDo: check mem!=nullptr
//ToDo: make code exception safe
//2. step initialize memory/copy data:
size_t cur_index=0;
for(const auto& val : vals)
for(size_t i=0;i<itemsize;i++)
char ch = i<val.size() ?
val[i] :
0; //fill with NUL if string too short
reinterpret_cast<char*>(mem)[cur_index] = ch;
cur_index++;
//3. create numpy array
npy_intp dim = static_cast<npy_intp>(vals.size());
return (PyObject*)PyArray_New(&PyArray_Type, 1, &dim, NPY_STRING, NULL, mem, 4, NPY_ARRAY_OWNDATA, NULL);
最后一件重要的事情:如果它应该由生成的 numpy-array(NPY_ARRAY_OWNDATA
- 标志)拥有,则应该使用 PyDataMem_NEW
而不是 malloc
来分配数据。这有两个优点:内存跟踪工作正常,我们不会(错误)使用实现细节。有关传递数据所有权的其他方式,请参阅SO-post。
【讨论】:
以上是关于C-Numpy:如何从现有数据创建固定宽度的字符串数组的主要内容,如果未能解决你的问题,请参考以下文章
如何从现有数据库数据生成 Symfony 固定装置 YAML?
如何使用 QPlaintTextEdit 高效地创建固定宽度的列记录器?