Numpy 切片的视图与复制
在使用NumPy时,您需要考虑不同数组之间的关系。特别是在使用切片时,可能会遇到视图和副本的问题。理解它们之间的不同是使用NumPy的关键,因为这决定了您在处理数据时会发生什么。在本文中,我们将讨论切片的视图与复制之间的区别。
阅读更多:Numpy 教程
切片
在使用NumPy时,我们通常会创建一个数组,并寻找方法将其切片成所需大小的部分。与Python列表不同,NumPy数组允许您在不复制任何数据的情况下实现切片。但是,这些切片之间存在一些微妙的区别,这些区别可以影响您对代码的输出和维护。
下面是一个创建数组并通过切片操作来提取它的一部分的示例:
import numpy as np
a = np.array([10, 20, 30, 40, 50])
b = a[1:4]
print(b)
这将输出:
[20 30 40]
请注意,我们仅提取了a数组的索引1到3的元素。这是我们常用的切片操作,但是我们需要注意到我们得到了什么:视图还是副本。
视图
视图是一种对NumPy数组的另一种引用方式。当您创建一个新数组时,它存储了与原始数组相同的数据,但它不是原始数组本身。相反,它是指向原始数组数据的指针。这是一种节省空间的方法,因为它允许多个数组共享相同的数据。
对于视图,如果您对原始数组进行更改,则视图也会相应地更改,反之亦然。让我们来看一个例子:
import numpy as np
a = np.array([10, 20, 30, 40, 50])
b = a[1:4]
b[1] = 99
print(b)
print(a)
这将输出:
[20 99 40]
[10 20 99 40 50]
请注意,我们将b数组的索引1处的元素更改为99。但是,当我们打印a数组时,我们会注意到它也已经被更改了。
这是因为我们创建了b数组时,实际上只是创建了一个指向原始数据的指针,而不是创建了新的数据副本。因此,当我们在b数组中对数据进行更改时,实际上也更改了原始数据。
副本
相比之下,副本是原始数组的一个精确的复制。这意味着对副本数组进行的更改不会影响原始数组或其他副本。创建一个副本数组时,NumPy会为您复制原始数据,从而占用更多的内存。
让我们看一下这个例子:
import numpy as np
a = np.array([10, 20, 30, 40, 50])
b = a[1:4].copy()
b[1] = 99
print(b)
print(a)
这将输出:
[20 99 40]
[10 20 30 40 50]
请注意,在创建b数组时,我们使用了copy()方法。这将创建一个全新的数据副本,其中包含原始数组中的所有数据。
如何选择
在大多数情况下,视图比副本更快并占用更少的内存。因此,如果您需要更改原始数据,则可能需要使用视图。但是,如果您正在处理大型数组,可能需要使用副本来确保不会占用过多的内存。
您可以使用ndarray对象的flags属性来检查它是否是视图。该属性的输出包括:
- C_CONTIGUOUS:数组的数据在C语言中连续存储
- F_CONTIGUOUS:数组的数据在FORTRAN语言中连续存储
- OWNDATA:数组的数据是本地的(不是由其他对象所有)
- WRITEABLE:数组的数据是可写的。如果该值为False,则试图更改数组数据会引发ValueError。
- ALIGNED:数组的数据对齐正确可以提高性能
- UPDATEIFCOPY:这个数组是其他非相邻数组的副本,当这个数组被释放时,原始数据将被更新。
下面是一个示例,说明如何使用flags属性:
import numpy as np
a = np.array([10, 20, 30, 40, 50])
b = a[1:4]
print(b.flags)
这将输出:
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
请注意,此时b数组是一个视图,因为它的OWNDATA属性为False。
总结
在使用NumPy时,您需要考虑视图和副本之间的区别。视图是对原始数组的指针引用,这意味着对它们进行的更改也会影响原始数据。副本是原始数组的精确复制,这意味着尽管它们可能包含相同的数据,但它们是独立的对象。选择视图还是副本取决于您的特定应用程序。如果您不需要更改原始数据,并且想要节省空间和时间,请创建视图。但是,如果您确实需要更改原始数据或者处理大型数组,则可能需要创建副本。