理解Numpy中的引用、视图、副本

引入:引用、副本、视图

在介绍Numpy中的切片前首先要引入三个概念:引用、副本和视图
引用即通过赋值操作使得不同标识符指向同一个对象。如果该对象为可变对象,对一个变量进行了修改,会同步修改。

副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。

视图是数据的一个别称或引用,通过该别称或引用亦便可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据。

引用一般发生在:

  • 赋值操作

视图一般发生在:

  • 1、numpy 的切片操作返回原数据的视图。
  • 2、调用 ndarray 的 view() 函数产生一个视图。

副本一般发生在:

  • Python中调用deepCopy()函数。
  • 调用 ndarray 的 copy() 函数产生一个副本。

对应python中的概念:numpy中引用 = python中引用;numpy中视图 = python中浅拷贝;numpy中副本 = python中深拷贝

回顾浅拷贝copy与深拷贝deepcopy概念

浅拷贝与深拷贝之间的区别仅当传入的对象为复合对象时(即传入的对象包含其他的对象,如包含列表等)会有所差异:

  1. 浅拷贝返回一个新的复合对象(不同id),但其中包含的对象会引用传入对象中包含的对象(使用同一id)
  2. 深拷贝返回一个新的复合对象(不同id),但其中包含的对象会创建副本而不是进行引用(不同id)
# 要导入copy库
import copy
a = [[1,2,3],4]
# 浅拷贝
b = copy.copy(a)
print(id(a),id(b),id(a[0]),id(b[0]))
# 深拷贝
c = copy.deepcopy(a)
print(id(a),id(c),id(a[0]),id(c[0]))
2890993692864 2890993695808 2890992900288 2890992900288
2890993692864 2890992901248 2890992900288 2890871413248

视图

可以通过切片操作返回原数据的视图;还可以调用 ndarray.view() 函数产生一个视图。
numpy中的视图 等价于 python中的浅拷贝
返回的一个新的数组对象(id不同)

arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = arr[:2]
print(id(arr),id(arr1),id(arr[0]),id(arr1[0]))
arr1[0][0] = 10
arr1.shape = (3,2)
print(arr,'\n',arr1)
2890993705584 2890993705680 2890993707024 2890993707024
[[10  2  3]
 [ 4  5  6]
 [ 7  8  9]] 
 [[10  2]
 [ 3  4]
 [ 5  6]]
# 通过view()方法创建视图
arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = arr.view()
print(id(arr),id(arr1),id(arr[0]),id(arr1[0]))
2890993564432 2890993705200 2890993675696 2890993675696

输出结果和想象的一致,但是这其中其实是有一个小坑的,那就是id(arr[0]),id(arr1[0])和上面拷贝列表时的id(a[0]),id(b[0])是两个完全不同的概念,不信你看下面的创建副本或深拷贝的id(arr[0]),id(arr1[0]

副本

可以通过copy.deepcopy()创建副本;也可以通过ndarray.copy()创建副本

arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = arr.copy()
id(arr),id(arr1),id(arr[0]),id(arr1[0])
(2890993675216, 2890993707792, 2890993564432, 2890993564432)
import copy
arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = copy.deepcopy(arr)
id(arr),id(arr1),id(arr[0]),id(arr1[0])
(1567028693200, 1567028540976, 1567028540304, 1567028540304)

你会发现id(arr[0]),id(arr1[0])竟然是同一个id,下面我将解释这背后的原理:

  1. 首先,要明白的一个概念就是我们这里arr[0]是对数组的索引,并且numpy文档中明确返回的将是原数组的视图

It must be noted that the returned array is a view, i.e., it is not a copy of the original, but points to the same values in memory as does the original array.

  1. 其次,就是我们这时候并没有将这个返回的视图用变量引用,相当于一个临时创建的ndarray对象,当id(arr[0])执行结束之后,这个对象就被释放了,但是这块内存并没有被释放,当执行id(arr1[0])又临时创建了一个ndarray对象,只不过它用的也是同一个内存,没有申请新的内存,这会让你以为arr[0]arr1[0]是同一个对象。使用is你就会发现这两个是不同的对象,它们的生命期不重叠

id():返回对象的“标识值”。该值是一个整数,在此对象的生命周期中保证是唯一且恒定的。两个生命期不重叠的对象可能具有相同的 id() 值。

arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = arr.copy()
arr[0] is arr1[0]
False

而我们把arr[0]arr1[0]通过变量进行引用,而不是作为临时对象,强行令他们的生存周期有重叠,然后你会发现他们作为不同对象,其地址确实是不一样的。

arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = arr.copy()
a = arr[0]
b = arr1[0]
id(a),id(b)
(1567028529552, 1567028423824)

下面这个例子,能更好的理解python的内容管理机制:

  1. 创建了一个临时对象arr[0],用a保存该临时对象的id后 --》释放该临时对象
  2. 又创建了一个对象arr[0],使用了上一块释放了临时对象的内存,并且将该对象用变量c保存了下来,此时该对象不会被释放
  3. 创建临时对象arr1[0],由于上一部分对象没有被释放,因此需要重新申请一块新的内存,用b保存该临时对象的id后 --》释放该临时对象
arr = np.array([[1,2,3],
                [4,5,6],
                [7,8,9]])
arr1 = arr.copy()
a = id(arr[0])
c = arr[0]
b = id(arr1[0])
print(a,id(c),b)
1567028527536 1567028527536 1567028691856

参考:
https://www.zhihu.com/question/275830564
https://www.bilibili.com/video/BV1yY4y1t77a?spm_id_from=333.999.0.0

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值