Numpy视图和副本:从内存角度分析
在上一节《Numpy数组的切片操作详解》中,我们提到了视图与副本,在本节我们将详细的阐述它们的概念,通过本节的知识的学习,你将从根本上理解 Numpy 数组的切片操作,和哪些操作是创建数组视图,哪些是创建数组副本,以及它们的作用。
其实可以把它当做浅拷贝来理解,切片后产生的视图虽然有了新的内存 id,但是数组中的数据引用仍指向原数组的数据地址(即物理内存地址相同),也就是说它只是复制了一层外表而已。如果你熟悉 Python 中的浅拷贝和深拷贝理解起来会更加容易。
1. 数组视图
1) 多维数组的赋值操
在 Python 中 赋值是一种常见的操作,a=[1,3,4],则表明变量 a 指向列表 [1,2,3] 的内存地址,若 b=a 代表变量 b 指向相同的内存地址,这一点同样适应于 Numpy 数组,也就是说若 a、b 指向同一个数组,若数组的形状或者数据发生了变化,则 a、b都会发生相应的变化。如下所示:In [15]: 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 = (2,3) ...: print('修改b的形状:') ...: print(b) ...: print('a形状也发生改变:') ...: print(a) 数组a: [0 1 2 3 4 5] 数组b: [0 1 2 3 4 5] a内存id: 10712232 b内存id: 10712232 修改b的形状: [[0 1 2] [3 4 5]] a形状也发生改变: [[0 1 2] [3 4 5]]ndarray 对象的 view() 方法创建可以创建视图数组,若使用该方法创建a 的视图 b ,则修改 b 的形状后,a 的形状不会发生改变,大家可以自己尝试一下。
2) Numpy数组视图理解
这里的视图,并非 MVC 框架中的是视图,数组的视图是指与较大数组共享数据的较小数组(也可是原数组),既然是共享数据所示修改小数组中的数据会影响到大数组。这是一种简单的理解方式,通过视图可以访问、操作原有数据,但原有数据源不会产生拷贝,如果我们对视图进行修改,它会影响到原始数据。视图数组可以通过切片进行创建,还有一种方法就是可以是通过调用 ndarray 对象的 view() 方法。In [1]: import numpy as np In [2]: array=np.array([[1,3,5],[2,4,6],[7,9,10]]) In [3]: array Out[3]: array([[ 1, 3, 5], [ 2, 4, 6], [ 7, 9, 10]]) #查看内存id标识 In [4]: id(array) Out[4]: 10711472 #选取第二行所有列生成arry1视图数组 In [5]: array1=arry[2,:] In [6]: array1 Out[6]: array([ 7, 9, 10]) #查看内存id标识 In [7]: id(array1) Out[7]: 74924584 #索引操作修改视图数组 In [8]: array1[1]=64 In [9]: array1 Out[9]: array([ 7, 64, 10]) #原数组被改变 In [10]: array Out[10]: array([[ 1, 3, 5], [ 2, 4, 6], [ 7, 64, 10]]) #切片赋值操作视图数组 In [11]: array1[:]=1314 In [12]: array1 Out[12]: array([1314, 1314, 1314]) In [13]: array #原数组被改变 Out[13]: array([[ 1, 3, 5], [ 2, 4, 6], [1314, 1314, 1314]])从上述代码可以看出 Numpy 的切片操作会返回原数据的视图,切片产生的 array1 视图是原数组 array 的一部分,对视图的修改会直接反映到原数据中,但是可以发现 array 与 array1 的内存 id 并不相同,也就是视图虽然指向原数据,但是他们与赋值引用还是有区别的。
其实可以把它当做浅拷贝来理解,切片后产生的视图虽然有了新的内存 id,但是数组中的数据引用仍指向原数组的数据地址(即物理内存地址相同),也就是说它只是复制了一层外表而已。如果你熟悉 Python 中的浅拷贝和深拷贝理解起来会更加容易。
3) 产生视图的基本方法
在 Numpy 中,数组的基础切片操作、ndarray. view() 方法、数组转置操作(即 ndarray.T)以及数组的变维操作(ndarray.reshape)都会得到一个视图,我们可以通过视图数组的 base 属性访问原数组。In [21]: a=np.array([[1,3],[4,5]]) In [22]: b=a[0,:] In [23]: b[0]=9 In [24]: b Out[24]: array([9, 3]) In [25]: a Out[25]: array([[9, 3], [4, 5]]) #使用base属性访问原数组 In [26]: b.base Out[26]: array([[9, 3], [4, 5]])
2. 数组副本
副本是一个数据的完整拷贝,如果我们对副本进行修改,它不会影响到原始数据,因为物理内存不在同一位置,我们可以把副本理解为 Python 中的深拷贝,在 Numpy 中我们可以使用 ndarray.copy() 方法来生成副本。而在 Python 序列的切片操作时,调用 deepCopy() 函数进行深拷贝。下面我们看一下 Numpy 中如何生成副本。In [1]: import numpy as np In [2]: data=np.array([[1,3,5],[2,4,6],[7,9,10]]) #生成原数组 In [3]: data Out[3]: array([[ 1, 3, 5], [ 2, 4, 6], [ 7, 9, 10]]) #使用索引获取第一个维数组 In [4]: data[0] Out[4]: array([1, 3, 5]) #创建副本data1深拷贝操作 In [5]: data1=data[0].copy() #采用索引对改变原数组的值 In [6]: data[0]=11 In [7]: data #原数组数据发生改变 Out[7]: array([[11, 11, 11], [ 2, 4, 6], [ 7, 9, 10]]) In [8]: data[0] Out[8]: array([11, 11, 11]) #对data[0]再赋值为data1 In [9]: data[0]=data1 #结果变为初始的data数组 In [10]: data Out[10]: array([[ 1, 3, 5], [ 2, 4, 6], [ 7, 9, 10]])其实副本的操作不是很难理解,我们可以把它当做 Python 列表的深拷贝来理解,copy() 方法创建了 data1副本。因为经过拷贝的 data1 副本物理内存不同于原数组 data[0],所以如果对副本进行切片操作将不会影响到原数组数据。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算