NumPy中的数组切片

6
今天我在进行一些计算时使用了numpy数组,并发现了一个奇怪的问题。例如,假设我已经在Ipython中导入了numpy.arange,并运行以下脚本:
In [5]: foo = arange(10)                                                      

In [8]: foo1 = foo[arange(3)]                                                 

In [11]: foo1[:] = 0                                                          

In [12]: foo
Out[12]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [16]: foo2 = foo[0:3]                                                      

In [19]: foo2[:]=0                                                            

In [21]: foo
Out[21]: array([0, 0, 0, 3, 4, 5, 6, 7, 8, 9])

上述内容表明,当我使用foo[arange(3)]对数组进行切片时,我得到了一个数组切片的副本,但是当我使用foo[0:3]对数组进行切片时,我得到了一个数组切片的引用,因此foo会随着foo2的改变而改变。然后我认为foo和foo2应该有相同的id,但事实似乎并非如此。

In [59]: id(foo)
Out[59]: 27502608

In [60]: id(foo2)
Out[60]: 28866880

In [61]: id(foo[0])
Out[61]: 38796768

In [62]: id(foo2[0])
Out[62]: 38813248

...

更加奇怪的是,如果我不断检查foo和foo2的id,它们并不是固定的,有时候它们会匹配!

In [65]: id(foo2[0])
Out[65]: 38928592

In [66]: id(foo[0])                                                          
Out[66]: 37111504

In [67]: id(foo[0])
Out[67]: 38928592

有人能简单解释一下这个吗?我对Python的这种动态特性感到非常困惑。

非常感谢。

1个回答

5
foo[arange(3)]

不是一个切片。使用arange(3)的元素来选择foo的元素来构建一个新数组。由于这样做无法有效地返回视图(视图中的每个元素都必须是一个独立的引用,并且对视图的操作需要跟随太多指针),因此它返回一个新的数组。

foo[0:3]

是一个切片。这可以通过视图有效地完成;它只需要调整一些边界。因此,它返回一个视图。

id(foo[0])

foo[0]并不是指代一个特定的Python对象。为每个数组元素保留单独的Python对象将会非常昂贵,这将抵消numpy所带来的大部分好处。因此,在对numpy ndarray进行索引操作时,numpy会构建一个新的对象进行返回。每次都会获得一个具有不同ID的不同对象。


那么为什么id(foo)也与id(foo2)不同呢?它们使用第一个元素的地址作为它们的地址吗? - shelper
1
@shelper:foo不是foo2。尽管它们具有相同的形状、dtype等,尽管它们使用相同的存储器来存储它们的元素,但它们是不同的对象。我认为您收到的ID与数组元素的地址没有任何关系;它是包含数组元数据和指向用于元素的存储器的指针的标头的地址。 - user2357112
1
我认为我理解了这个问题,foo和foo2都是良好封装的Python对象,id(foo)只显示Python对象的地址,而不是包含数据的内存,实际上可以通过“foo.array_interface['data']”获取数据。 - shelper
2
你可以通过比较两个数组的.base属性来检查它们是否共享相同的基础内存,即在你的情况下,foo.base is foo2.base应该评估为True - Jaime
1
@Jamie - 如果我没记错的话,有些情况下foo.basefoo2.base可能不相同,即使它们共享同一内存缓冲区。这对于所有切片操作都是正确的,但并非所有情况都适用。通过__array_interface__创建视图的东西(例如np.lib.stride_tricks.as_strided)不一定会显示相同的基础。据我所知,numpy.may_share_memory(foo, foo2)是检查两个数组是否共享相同内存的首选方法(尽管我可能错了)。 - Joe Kington

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