为什么numpy ndarray的insert和append返回一个新数组而不是修改原始数组?

时间:2018-12-15 20:24:53

标签: python arrays numpy

对于numpy ndarray,没有添加和插入,就如本地python列表一样。

a = np.array([1, 2, 3])
a.append(5)  # this does not work
a = np.append(a, 5)  # this is the only way

原生python列表,

a = [1, 2, 3]
a.append(4)  # this modifies a
a  # [1, 2, 3, 4]

为什么numpy ndarray设计成这种方式?我正在写ndarray的子​​类,有没有办法像本地python数组一样实现“追加”?

2 个答案:

答案 0 :(得分:5)

NumPy大量使用了视图,Python列表不支持该功能。视图是一个使用另一个对象的内存而不拥有自己的内存的数组。例如,在以下代码段中

((y_true - y_true.mean()) ** 2).sum()

a = numpy.arange(5) b = a[1:3] b的视图。

视图与就地a或其他就地尺寸更改操作的交互非常差。数组将突然不是它们应作为其视图的数组的视图,或者它们将成为已释放的内存的视图,或者一个数组上的append是否会影响它作为其视图的数组或所有数组都将是不可预测的各种各样的其他问题。例如,append之后的a会是什么样?还是b.append(6)之后b会是什么样?您可以提供哪种性能保证?可能不是a.clear()的摊销固定时间保证。

如果您想使用list.append,则可能不应该使用NumPy数组。您应该使用一个列表,并在完成添加后从列表中构建一个数组。

答案 1 :(得分:1)

ndarray是使用固定大小的数据缓冲区创建的-大小足以容纳代表元素的字节。

arr.nbytes == arr.itemsize * arr.size

arr.resize可以就地更改数组。但是,请先阅读文档以了解局限性,尤其是在拥有自己的数据方面。这是少数几个就地操作之一,并且不经常使用。

相反,Python列表将对象指针存储在缓冲区中。缓冲区具有一定的增长空间,可以有效地append。它只需要向缓冲区添加一个新的指针。当缓冲区填满时,它将分配一个新的较大缓冲区并复制指针。

对于一维数组,ndarraylist的缓冲区至少在4字节或8字节数字dtypes中是相似的。但是对于多维数组,数据缓冲区可能非常大(所有维度的乘积),而等效嵌套数组的顶部缓冲区仅包含指向列表外层的指针(“行”)。

对象dtype数组像列表一样存储指针,但是数据缓冲区仍然具有固定的大小(没有增长空间)。性能介于数字数组和列表之间。

我可以想象编写一个使用resize方法的就地追加,然后将新值复制到0个填充中。

In [96]: arr = np.array([[1,3],[2,7]])
In [97]: arr.resize(3,2)
In [98]: arr
Out[98]: 
array([[1, 3],
       [2, 7],
       [0, 0]])
In [99]: arr[-1,:] = 10,11
In [100]: arr
Out[100]: 
array([[ 1,  3],
       [ 2,  7],
       [10, 11]])

但是请注意,当我们调整内轴大小时,值会发生什么:

In [101]: arr = np.array([[1,3],[2,7]])
In [102]: arr.resize(2,3)
In [103]: arr
Out[103]: 
array([[1, 3, 2],
       [7, 0, 0]])

因此,与concatenate(及其所有“堆栈”派生类)相比,这种追加非常有限。


您是否看过np.append的代码?确保参数为数组并调整其形状后,它将执行以下操作:

concatenate((arr, values), axis=axis)

换句话说,它只是调用concatenate的另一种方式。最好将单个值添加到一维数组中。它不应该在循环中重复使用,正是因为它返回了一个新数组,因此比较昂贵。否则,它的使用会使许多用户困惑。有些人忽略了轴参数。其他人则在创建正确的“空”数组时遇到了问题。串联也存在这些问题,但是至少用户必须自觉处理形状匹配的问题。

np.insert要复杂得多。根据索引(obj)是数字,切片还是数字列表,它会执行不同的操作。一种方法是创建大小合适的目标数组,然后从原始副本中复制切片并将值插入正确的插槽。另一个方法是使用布尔掩码将值复制到正确的位置。两者都必须适应多维-它沿一个轴插入,但必须对其他尺寸使用适当的slice(None)。这比列表插入要复杂得多,后者在1d的一个位置插入一个对象(指针)。