昨天我在使用numpy matrices,我遇到了一个让我加倍考虑的行为,具体来说:
>>> X=np.array([[1,5],[2,4],[3,6]])
>>> X
array([[1, 5],
[2, 4],
[3, 6]]) # Perfecto
>>> a=X[:,0]
>>> a
array([1, 2, 3]) # Exactly
>>> X
array([[1, 5],
[2, 4],
[3, 6]]) #Yup
>>> a[0]=999
>>> a
array([999, 2, 3]) # :)
>>> X
array([[999, 5],
[ 2, 4],
[ 3, 6]]) # Errrrr, Huh?
#OK, Fine.
>>> b=np.matrix(X[:,1])
>>> b
matrix([[5, 4, 6]])
>>> b[0]=666
>>> X
array([[1, 5],
[2, 4],
[3, 6]]) # That's what I wanted
字符串
我最初期望Python会隐式地假定我的数据类型是np.matrix,并调用一个复制构造函数。我找到了现有母体的参考资料。作为一个C程序员,我讨厌引用变量。我宁愿得到一个内存位置,并在代码中显式地DE-REFERENCE内存位置并转换赋值。但是美丽新世界需要开放的思想我想这是Numpy的细微差别。所以我问,在Python库中,当我应该期望在赋值时获得引用时,是否有经验法则?那个意想不到的结果比猴子的腋窝还难看
3条答案
按热度按时间8fsztsew1#
我不明白C和推荐人的区别这并不比你在C中使用的引用更多(此外,它碰巧是C,因为这是numpy)。我是C程序员。我讨厌引用(我的意思是,C++的引用)。我想传递一个指向变量的指针,这个指针可以被修改。但这不是一个。
在C中
字符串
打印99这就是你在这里做的榜样。提取一个子数组。
该数组包含一个指向相同数据的指针
就像我说的,不是只有你才能用C语言编写这样的代码。但实际上它是用C编写的。在C中,数组也是指针。当你把一个数组arr传递给一个函数时,即使arr本身是通过值传递的,也就是说这个函数不能修改arr本身(就像你在例子中修改
X
一样),这个函数仍然可以修改arr
的内容。就像你在这里改变了X
的内容一样。真的,没有区别。您没有对X
的引用。但是,子数组仍然共享指向X
结构内部的数据。至于你的确切问题,严格的答案是“这取决于,但它是在文档中说”。但经验法则是“当它可以时,它通常会共享数据”(您称之为“hand a reference”)。要猜测它什么时候可以,你必须理解什么是numpy数组:它是一堆连续数据、一个形状、一个初始指针、一个数据类型和步幅。所以索引为
[i1,i2,...,in]
的元素存在当且仅当对所有k都是ik<shapeₖ
,并且该元素的地址为data+stride₁×i₁+stride₂×i₂+…+stridesₖ×iₖ
通常(经验法则),如果可以通过构建一组新的步幅/形状来完成操作,但使用相同的数据,则以这种方式完成,而无需复制数据。
例如
X[:,1]
:假设数据位于地址Xaddr
,形状为(shapeX1, shapeX2)
,跨距为(strideX1, strideX2)
,则地址为Yaddr=Xaddr+1×strideX2
,形状为(shapeX1,)
,跨距为(strideX1,)
的阵列Y
是X
的第二列。因此,由于可以这样做来计算X[:,1]
,因此可以避免复制(同样,这不是一个严格的规则,只是一个经验)。同样,
X.T
(转置)是地址为Xaddr
的数组,其形状为(shapeX2,shapeX1)
,步幅为(strideX2,strideX1)
。所以,不需要复制,所以它不是。所以呢型
展示
型
另一方面,复杂索引生成副本。
型
展示
型
因为你不能通过调整步幅和形状来做高级索引。
注意,
X[::2,:][0,0]=99
确实改变了X。当X[[0,2],:][0,0]
没有。然而X[::2,:]
和X[[0,2],:]
的意思是相同的(只保留偶数行)。但是,第一个总是可行的,只有步幅调整,当第二个碰巧只有这个数字,但不会如果线选择不均匀选择(如在我以前的[0,2,3]
例子)。这条经验法则甚至适用于非常复杂的案件。
比如说
型
因此,第一次重塑(Y)是“视图”,而第二次重塑(T)是副本。
为什么,因为X数据是连续的。有点像在C语言中,你是这样实现的
型
没有任何
tstride1
,tstride2
是真的。只需观察t
值:它们是从0,1,2,3,4,5,6,7,8,9,10,11
中提取的值0、1、3、4。它们在初始数据数组中不是均匀分布的。因此,没有一个唯一的tstrid2
来告诉有多少字节将0与1、1与3、3与4分开。因此,不可能在不构建新数据集的情况下构建T
。这就是为什么操作
.reshape(...)
有时返回一个视图,有时返回一个副本。所有这些都在文档中说明。某些操作明确返回一个副本。有些人明确地返回一个视图。而对于一些文档来说,“你不能假设它是一个视图还是一个副本”。因为这取决于实现检测是否可以重用初始数据的智能程度。
vybvopom2#
https://numpy.org/doc/stable/user/basics.copies.html
您的阵列:
字符串
这是
basic indexing
,所以a
是view
。检查其base
:型
有关阵列的更多信息:
型
请注意相同的
data
数字。这是一个指向数组(一个c数组)的数据缓冲区的指针。阅读更多numpy基础知识,了解如何存储数组。您的矩阵是一个副本,具有自己的基础:
型
矩阵的视图版本:
型
您还可以指定
copy
以确保新数组是它自己的基:型
advanced indexing
生成一个副本:型
注意
a
的不同形状,(3,1)而不是(3,)。型
检查基数可以发现索引的细微差别。这个基数是(1,3)数组,不是
X
或a
。索引步骤实际上创建了这个(1,3)数组,并通过返回其转置(视图创建步骤)完成了它。w8biq8rn3#
也许你应该使用'copy'来获得一个新的独立对象。我希望这就是你要找的。
字符串