繁体   English   中英

为什么 np.split 在传递到现有数组时创建一个副本?

[英]Why does np.split create a copy when passing into an existing array?

我是 Python 的新手,正在尝试了解视图和副本的行为,如果这是一个明显的问题,我深表歉意,在下面的示例中,我使用np.split()拆分数组x 当我将 np.split 传递到一个新的 object( x1 ,一个 3 1D arrays 的列表或x2, x3, x4 ,三个独立的一维数组)时,对象是x的视图,如预期的那样:

import numpy as np

x = np.arange(1, 10)                        # create an array of length 9

x1 = np.split(x, (3,6))                     # split array into 1 new object (list of arrays)
print(x1[0].base)                           # each array in list is a view

x2, x3, x4 = np.split(x, (3, 6))            # split array into 3 new objects
print(x2.base)                              # these objects are views

但是,如果我创建一个空的 (3,3) 数组x5并将 np.split 传递到该数组的每一行(我知道这是一件愚蠢的事情,我只是想弄清楚拆分是如何工作的),一个副本被建造:

x5 = np.empty((3,3), dtype = np.int32)      # create an uninitialised array
x5[0], x5[1], x5[2] = np.split(x, (3, 6))   # split x into each row of x5
print(x5.base)                              # this object is a COPY

我想也许是对 x5 的切片导致了复制,但如果我对x2, x3, x4进行切片,它们仍然是视图:

x2[:], x3[:], x4[:] = np.split(x, (3, 6))   # split array into 3 existing objects using indexing
print(x2.base)                              # these objects are views

我还没有在视图和副本或 np.split 的任何解释中找到对此的解释 - 我错过了什么?

您看到的行为仅与split松散相关。 numpy arrays 后面只有一个“矩形块”数据,它们不能引用多个独立分配。 该块可能会以奇怪的方式(通过跨步、屏蔽、转置等)查看,因此各个条目不一定是连续的,但它们都由一个且只有一个连续的批量分配支持。

该分配对于阵列或某个其他阵列的视图可以是唯一的,但给定阵列 object 在拥有存储和查看存储之间不会改变,它是其中之一。 数组绑定的名称可以重新分配,但所有名称都可以重新分配( x = 1不会使x总是那个特定的int ,甚至总是一些int ; x = ANYTHING稍后会将它重新绑定到一个全新的 object任意类型,忽略它曾经是什么)。

知道这一点就很清楚,不可能将数组的一部分重新分配为其他数组的视图。 因此,解释您的各种观察结果:

x2, x3, x4 = np.split(x, (3, 6))            # split array into 3 new objects
print(x2.base)                              # these objects are views

如您所述,这是预期的行为。 np.split返回一个list ,其中包含三个新的 arrays 视图类型,每个都由x的一部分支持。

x5 = np.empty((3,3), dtype = np.int32)      # create an uninitialised array
x5[0], x5[1], x5[2] = np.split(x, (3, 6))   # split x into each row of x5
print(x5.base)                              # this object is a COPY

此处的第 1 行创建了一个拥有类型的新数组。 即使可以用不同的缓冲区替换底层存储缓冲区(拥有一个新缓冲区,或者查看其他数组的缓冲区),我也不会发誓这是不可能的(我还没有充分探索numpy可以肯定地说),要使它成为拥有和观看的混合体是绝对不可能的。 考虑更简单的代码:

x5[0] = x[:3]

如果该代码有效,并且没有将数据从x的视图复制到x5[0]的视图,那么不知何故, x5[0]需要是x的视图,而x5的数据的 rest将被拥有。 缓冲区中支持x5[0]的三个元素会发生什么变化? 您可能会想“但我要一次更换所有这些”,但实际上不是。 x5[0], x5[1], x5[2] = np.split(x, (3, 6))实际上等同于__unnamedtemp = [x[:3], x[3:6], x[6:]] ,然后是x5[0] = __unnamedtemp[0] ,然后是x5[1] = __unnamedtemp[1] ,然后是x5[2] = __unnamedtemp[2] 解包工作必须一点一点地完成(解包是 Python 的一般特征, numpy无法 hook),所以即使理论上最终结果可以离开x5查看x ,实际上它不能,即使numpy想要,因为中间阶段是非法的 state。

通过对比,

x2[:], x3[:], x4[:] = np.split(x, (3, 6))   # split array into 3 existing objects using indexing
print(x2.base)

“有效”只是因为x2x3x4已经x的视图。 但是副本仍在制作中; x2已经是x[:3]的视图,你只是告诉numpyx[:3]的内容复制到x2[:] 在引擎盖下,一旦x2.__setitem__以完整切片和另一个numpy视图的参数调用,并且numpy具有足够的信息和控制权,它可能会注意到原始 memory 地址是相同的并避免复制,但我如果它只是盲目地将视图中每个地址的数据复制到自身,也不会感到惊讶。

如果您没有通过x4重用x2 ,您将能够看到它没有产生新的视图:

x2, x3, x4 = np.arange(1, 4), np.arange(1, 4), np.arange(1, 4)  # Three owning arrays
x2[:], x3[:], x4[:] = np.split(x, (3, 6))  # Makes three views, then copies from views to owned buffers
print(x2.base)  # Does not have a base, because it's still not a view

这里的简短版本是:

  1. 任何内容分配给普通名称(无索引/切片)将重新绑定该名称,而无需复制数据(忽略用于绑定到该名称的任何内容); 如果您分配了一个查看数组,它现在是一个视图,如果您分配了一个拥有数组,它现在是一个拥有数组。
  2. 视图或拥有 arrays 分配给现有数组(视图或拥有)的索引或切片会将数据从一个数组复制到另一个数组(如果适用,复制到查看缓冲区)

这就是它对 Python 中所有类型的工作方式numpy案例唯一不同寻常的是您可以创建视图; 内置的 Python 序列(保留memoryview的怪异内存numpy )没有视图的概念,因此等号右侧的切片将是浅拷贝,但分配等号右侧的切片左侧仍将复制(无论右侧是视图还是副本)。

在 ipython session 中,创建x并拆分

In [23]: x=np.arange(1,10);x
Out[23]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
In [24]: x2,x3,x4 = np.split(x,(3,6))

考察一:

In [25]: x2
Out[25]: array([1, 2, 3])    
In [26]: x2.base
Out[26]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])

我发现以下显示的数组信息很有帮助。 “数据”字段“指向”底层数据缓冲区。 它不能在代码中使用,但数字是值实际存储位置的有用标识符。

In [28]: x.__array_interface__
Out[28]: 
{'data': (2389644026224, False),
 'strides': None,
 'descr': [('', '<i4')],
 'typestr': '<i4',
 'shape': (9,),
 'version': 3}

x2具有相同的“数据”值:

In [29]: x2.__array_interface__['data']
Out[29]: (2389644026224, False)

x3x4的值略有不同,指向缓冲区中更远的字节(例如, x32389644026236 ,更远的为 3*4)。

x5是一个新数组,有自己的数据缓冲区:

In [30]: x5 = np.empty((3,3),int);x5
Out[30]: 
array([[4128860, 6029375, 3801155],
       [5570652, 6619251, 7536754],
       [7340124, 7667809,     108]])    
In [31]: x5.__array_interface__['data']
Out[31]: (2389645593712, False)

x5赋值会复制这些值,但不会更改其数据缓冲区位置:

In [32]: x5[:] = x2,x3,x4    
In [33]: x5
Out[33]: 
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])    
In [34]: x5.__array_interface__['data']
Out[34]: (2389645593712, False)

xx5仍然是它们自己的“基地”:

In [35]: x.base, x5.base
Out[35]: (None, None)

我们可以从x索引另外 3 个值并将它们分配给x2视图:

In [38]: x[-2:-5:-1]
Out[38]: array([8, 7, 6])    
In [39]: x2[:]=x[-2:-5:-1]      # not x2=x[...]

x2将被更改,但它的基数也会更改:

In [40]: x2.base
Out[40]: array([8, 7, 6, 4, 5, 6, 7, 8, 9])    
In [41]: x
Out[41]: array([8, 7, 6, 4, 5, 6, 7, 8, 9])

[39] 中的赋值与将 3 个值复制到x5一样“昂贵”(时间方面)。 还在抄袭。 但不同之处在于值也被复制到哪里。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM