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&lt;std::string&gt;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&lt;std::string&gt; 具有完全不同的内存布局 - 因为 std::string 的内存布局不是通过 C++ 标准规定的,所以它可以在不同的实现之间改变。

上述观察的结果是,如果字符串以std::vector&lt;std::string&gt; 给出,则无法复制数据。该函数由三个步骤组成:

分配内存 将字符串复制到新位置 从上面构造的内存中创建 numpy-array。

以下是 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 高效地创建固定宽度的列记录器?

在 spark java 中读取具有固定宽度和分隔符的文本文件

如何从R中的大型固定宽度文件中读取特定列

ChartJS 条形图固定宽度动态数据集

如何以编程方式将 UIImageView 缩放到固定宽度和灵活高度?