Numpy | 副本和视图
在操作数组的时候返回的不是视图就是副本。
副本:复制
视图:链接
副本是一个数据的完整拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。
视图是数据的一个别称或引用,通过该别称或引用亦可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据,物理内存在同一位置。
视图一般发生在:
- 1、numpy 的切片操作返回原数据的视图。
- 2、调用 ndarray 的 view() 函数产生一个视图。
副本一般发生在:
- Python 序列的切片操作,调用deepCopy()函数。
- 调用 ndarray 的 copy() 函数产生一个副本。
赋值
赋值操作,两个变量a, b都指向同一个内存地址, b与原始数组a的id()相同。 id()返回 Python 对象的通用标识符,类似于 C 中的指针。
一个数组的任何变化都反映在另一个数组上。 例如,一个数组的形状改变也会改变另一个数组的形状。
import numpy as np a = np.arange(6) b = a print('数组a:',a) print('数组b:',b) print('a 调用 id() 函数:',id(a)) print('b 调用 id() 函数:',id(b)) b.shape = 3, 2 print('修改 b 的形状:') print(b) print('a 的形状也修改了:') print(a)
输出结果为:
数组a: [0 1 2 3 4 5]
数组b: [0 1 2 3 4 5]
a 调用 id() 函数: 2026424909056
b 调用 id() 函数: 2026424909056
修改 b 的形状:
[[0 1]
[2 3]
[4 5]]
a 的形状也修改了:
[[0 1]
[2 3]
[4 5]]
视图或浅拷贝
Numpy的切片操作会返回原数据的视图,切片产生的视图是原视图的一部分视图,对视图的修改会直接反映到原数据中,但它们的id是不同的。也就是说,视图虽然指向原数据,但是他们和赋值引用还是有区别的。
ndarray.view() 方会创建一个新的数组对象,该方法创建的新数组的维数更改不会更改原始数据的维数。
import numpy as np # 最开始 a 是个 3X2 的数组 a = np.arange(6).reshape(3, 2) print('数组 a:') print(a) print('\n') b = a.view() print('创建 a 的视图b:') print(b) print('\n') print('a 的 id():',id(a)) print('b 的 id():',id(b)) print('\n') # 修改 b 的形状,并不会修改 a b.shape = 2, 3 print('修改形状后的b:') print(b) print('\n') print('修改b形状后的a:') print(a)
输出结果为:
数组 a:
[[0 1]
[2 3]
[4 5]]
创建 a 的视图b:
[[0 1]
[2 3]
[4 5]]
a 的 id(): 1601808711552
b 的 id(): 1601809504256
修改形状后的b:
[[0 1 2]
[3 4 5]]
修改b形状后的a:
[[0 1]
[2 3]
[4 5]]
使用切片创建视图修改数据会影响到原始数组:
import numpy as np arr = np.arange(12) print('我们的数组:',arr) print('\n') a = arr[3:] b = arr[3:] print('切片a:',a) print('切片b:',b) print('\n') a[1] = 123 b[2] = 234 print('修改后的数组arr:') print(arr) print('修改后的数组a:') print(a) print('修改后的数组b:') print(b) print('\n') print('a的id:',id(a)) print('b的id:',id(b)) print('arr的id:',id(arr))
输出结果为:
我们的数组: [ 0 1 2 3 4 5 6 7 8 9 10 11]
切片a: [ 3 4 5 6 7 8 9 10 11]
切片b: [ 3 4 5 6 7 8 9 10 11]
修改后的数组arr:
[ 0 1 2 3 123 234 6 7 8 9 10 11]
修改后的数组a:
[ 3 123 234 6 7 8 9 10 11]
修改后的数组b:
[ 3 123 234 6 7 8 9 10 11]
a的id: 1173376024448
b的id: 1173376023968
arr的id: 1173376023888
变量 a,b 都是 arr 的一部分视图,对视图的修改会直接反映到原数据中。但是我们观察 a,b 的 id,他们是不同的,也就是说,视图虽然指向原数据,但是他们和赋值引用还是有区别的。
副本或深拷贝
ndarray.copy() 创建一个副本。 对副本数据进行修改,不会影响到原始数据,它们物理内存不一样。
import numpy as np a = np.array([[10, 10], [2, 3], [4, 5]]) print('数组 a:') print(a) print('\n') b = a.copy() print('创建 a 的副本数组 b:') print(b) print('\n') # b 与 a 不共享任何内容 print('我们能够写入 b 来写入 a 吗?') print(b is a) print('\n') b[0, 0] = 100 print('修改副本b:') print(b) print('\n') print('修改副本b后,a数组不变:') print(a)
输出结果为:
数组 a:
[[10 10]
[ 2 3]
[ 4 5]]
创建 a 的副本数组 b:
[[10 10]
[ 2 3]
[ 4 5]]
我们能够写入 b 来写入 a 吗?
False
修改副本b:
[[100 10]
[ 2 3]
[ 4 5]]
修改副本b后,a数组:
[[10 10]
[ 2 3]
[ 4 5]]
Python 中 list 的拷贝与 numpy 的 array 的拷贝
1.python中列表list的拷贝,会有什么需要注意的
Python 变量名相当于标签名。
list2=list1 直接赋值,实质上指向的是同一个内存值。任意一个变量 list1(或list2)发生改变,都会影响另一个 list2(或list1)。
切片产生新的列表,所以内存地址不一样,修改一个,不会影响另外一个。
l1=[1,2,3,4,5,6] l2=l1 l3=l1[2:] l4=l1[:] l1[2]=88 print(l1) print(l2) print(l3) print(l4)
输出结果:
[1, 2, 88, 4, 5, 6]
[1, 2, 88, 4, 5, 6]
[3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]
而 list3 和 list4 是通过切片对 list1 的复制操作,分别指向了新的值。任意改变 list3 或 list4 的值,不会影响其他。
2.要使用 ndarray 类型的数组,需要 from numpy import * 引用工具包 numpy。
而对 ndarray 类型的数据进行拷贝时,跟 list 类型有一点区别。
数组切片是原始数组的视图,这意味着数据不会被复制,视图上的任何修改都会被直接反映到源数组上。
array1, array2, array3, array4 实际指向同一个内存值,任意修改其中的一个变量,其他变量值都会被修改。
若想要得到的是 ndarray 切片的一份副本而非视图,就需要显式的进行复制操作函数 copy()。
import numpy as np l = [1,2,3,4,5,6] arr1 = np.asarray(l) # 对原始的 array1 的复制 arr2 = arr1 arr3 = arr1.copy() # 对切片 array1[1:4] 的复制 arr4 = arr1[1:4].copy() # 改变a中第一个元素的值 arr1[2] = 100 print(arr1) print(arr2) print(arr3) print(arr4)
结果输出:
[ 1 2 100 4 5 6]
[ 1 2 100 4 5 6]
[1 2 3 4 5 6]
[2 3 4]
那么,修改 arr3 或 arr4,就不会影响 arr1。可以看到,改变 arr1 后,arr2 的值也跟着变了,这是为什么呢?
实际上,变量 arr1 中并没有存储任何的值,它只是指向了一个内存地址,而这个地址里存储着 array 具体的内容,当把arr1赋值给arr2的时候,实际上是把 arr1 指向内存中某对象的链接赋给了arr2,也就是说,现在 arr1 和 arr2 都指向了同一个对象。因此,在改变了内存中 array 的值后,而 arr1 与 arr2 都引用了该 array 对象,所以都一起发生了变化。这种将内存引用赋值给另一个变量的操作叫做浅拷贝。
那什么是深拷贝呢,其实就是在赋值的时候,不把同一个内存对象的引用赋值给另一个变量,令两个变量所指向的对象不一样,更改值的时候不相互影响,这种操作就是深拷贝。