如何使用给定的字节缓冲区、数据类型、形状和步幅创建Numpy ndarray?

10
我有一个缓冲区、数据类型、形状和步幅。我想创建一个Numpy ndarray,它可以重用缓冲区的内存。
有一个numpy.frombuffer,它可以从缓冲区创建一个一维数组并重用内存。然而,我不确定是否可以轻松安全地调整其形状和设置步幅。
有一个numpy.ndarray构造函数可以引用一个缓冲区,但我不确定它是否会重用内存还是复制它(文档中没有明确说明)。
那么,numpy.ndarray构造函数会实现我的需求吗?还是有其他替代方法?

好的,我现在正在尝试弄清楚numpy.ndarray构造函数究竟在做什么。代码在这里。它使用PyArray_BufferConverter来转换缓冲区参数。然后它会调用PyArray_NewFromDescr_int,可以在这里看到。如果数据传入其中,它将fa->flags &= ~NPY_ARRAY_OWNDATA;


1
你只是在寻找创建“视图”的方法吗?我建议学习lib/stride_tricks文件。as_strided是一个方便的工具,可以创建具有特定形状和步幅的视图。但要注意,使用你的视图的numpy代码可能最终会复制它。 - hpaulj
@hpaulj:我明白了。代码在这里。这完全回答了我的问题(至少加上numpy.frombuffer,这将是一个视图)。你应该把它作为答案发布。 - Albert
3个回答

6

如@hpaulj的评论中所述,您可以使用stride_tricks模块来实现此目的。 您需要同时使用 np.frombuffer np.lib.stride_tricks.as_strided

从NumPy数组中收集数据

In [1]: import numpy as np
In [2]: x = np.random.random((3, 4)).astype(dtype='f4')
In [3]: buffer = x.data
In [4]: dtype = x.dtype
In [5]: shape = x.shape
In [6]: strides = x.strides

重新创建NumPy数组

In [7]: xx = np.frombuffer(buffer, dtype)
In [8]: xx = np.lib.stride_tricks.as_strided(xx, shape, strides)

验证结果

In [9]: x
Out[9]: 
array([[ 0.75343359,  0.20676662,  0.83675659,  0.99904215],
       [ 0.37182721,  0.83846378,  0.6888299 ,  0.57195812],
       [ 0.39905572,  0.7258808 ,  0.88316005,  0.2187883 ]], dtype=float32)

In [10]: xx
Out[10]: 
array([[ 0.75343359,  0.20676662,  0.83675659,  0.99904215],
       [ 0.37182721,  0.83846378,  0.6888299 ,  0.57195812],
       [ 0.39905572,  0.7258808 ,  0.88316005,  0.2187883 ]], dtype=float32)

In [11]: x.strides
Out[11]: (16, 4)
In [12]: xx.strides
Out[12]: (16, 4)

2

我建议使用frombuffer,因为它专门用于此目的,并且可以清楚地表明你正在做什么。以下是一个例子:

In [58]: s0 = 'aaaa'   # a single int32

In [59]: s1 = 'aaabaaacaaadaaae'  # 4 int32s, each increasing by 1

In [60]: a0 = np.frombuffer(s0, dtype='>i4', count=1)   # dtype sets the stride

In [61]: print a0
[1633771873]

In [62]: a1 = np.frombuffer(s, dtype='>i4', count=4)

In [63]: print a1
[1633771874 1633771875 1633771876 1633771877]

In [64]: a2 = a1.reshape((2,2))   # do a reshape, which also sets the strides

In [65]: print a2
[[1633771874 1633771875]
 [1633771876 1633771877]]

In [66]: a2 - a0     # do some calculation with the reshape
Out[66]: 
array([[1, 2],
       [3, 4]], dtype=int32)

这个功能是否不能满足您的需求?


2
我想设置一些自定义步幅。 - Albert
我喜欢frombuffer,因为这个名字清楚地告诉未来的读者你正在做什么。如果您想设置步幅,可以在打开缓冲区后进行,就像我在这里所做的那样。 dtypereshape都设置了步幅,如果您想设置一些不寻常的步幅,可以使用stride_tricks来实现,但如果问题中没有对此进行说明,我将仅保留正常的步幅设置(长度和形状)。 - tom10
1
我在问题中说过“给定一个...和步长”,所以显然我希望它使用给定的步长。 - Albert

2

你可以使用任何一种方法 - 两种方法都不会生成副本:

s = b'aaabaaacaaadaaae'
a1 = np.frombuffer(s, np.int32, 4).reshape(2, 2)
a2 = np.ndarray((2, 2), np.int32, buffer=s)

print(a1.flags.owndata, a1.base.tostring())
# (False, b'aaabaaacaaadaaae')
print(a2.flags.owndata, a2.base)
# (False, b'aaabaaacaaadaaae')

请注意,由于它们由只读内存支持,因此两个数组都无法就地修改:
a1[:] = 0  # ValueError: assignment destination is read-only

在numpy 1.18和python3中,两者都无法正常工作:TypeError: a bytes-like object is required, not 'str' - Koshmaar
@Koshmaar 我最初是为Python2编写了那个答案。在Python3中,s将是Unicode而不是字节(我已将其更改为字节文字)。 - ali_m

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接