numpy关于视图和副本的区别

本文深入解析了Numpy中的视图与副本概念,详细阐述了两者在数据处理过程中的区别与应用。通过实例演示了如何使用视图和副本进行高效的数据操作,避免原始数据的意外修改。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

numpy在数据处理时的优势时显而易见的,谈到numpy,会经常听到关于视图和副本的说法,今天就来记录一下视图个副本的区别。

首先讲一下两者的定义:

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

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

理解这个定义其实可以参考python的深浅拷贝的概念,副本其实就是类似于深拷贝,是完完整整的脱离了原来的数据,内存地址独立出来,可以进行任意的修改和操作,对原始的数据不会产生影响,视图类似于浅拷贝(可能这个对比并不完美),通俗一点说就是多了一个指向原来数据地址的路径,还是要依附于原来的数据,任意的操作都有可能对原始数据产生影响,所以,再进行数据处理的时候,最好是先做副本的处理,这样 数据处理过程中有什么差错都可以保留原始数据的完整性。

视图一般发生在:

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

副本一般发生在:

  • Python 序列的切片操作,调用deepCopy()函数。
  • 调用 ndarray 的 copy() 函数产生一个副本。

无复制

简单的赋值不会创建数组对象的副本。 相反,它使用原始数组的相同id()来访问它。 id()返回 Python 对象的通用标识符,类似于 C 中的指针。

 

此外,一个数组的任何变化都反映在另一个数组上。 例如,一个数组的形状改变也会改变另一个数组的形状。

视图或浅拷贝

ndarray.view() 方会创建一个新的数组对象,该方法创建的新数组的维数更改不会更改原始数据的维数

import numpy as np 
 
# 最开始 a 是个 3X2 的数组
a = np.arange(6).reshape(3,2)  
print ('数组 a:')
print (a)
print ('创建 a 的视图:')
b = a.view()  
print (b)
print ('两个数组的 id() 不同:')
print ('a 的 id():')
print (id(a))
print ('b 的 id():' )
print (id(b))
# 修改 b 的形状,并不会修改 a
b.shape =  2,3
print ('b 的形状:')
print (b)
print ('a 的形状:')
print (a)

运行结果是:

数组 a:
[[0 1]
 [2 3]
 [4 5]]
创建 a 的视图:
[[0 1]
 [2 3]
 [4 5]]
两个数组的 id() 不同:
a 的 id():
4314786992
b 的 id():
4315171296
b 的形状:
[[0 1 2]
 [3 4 5]]
a 的形状:
[[0 1]
 [2 3]
 [4 5]]

使用切片创建视图修改数据会影响到原始数组:

import numpy as np 
 
arr = np.arange(12)
print ('我们的数组:')
print (arr)
print ('创建切片:')
a=arr[3:]
b=arr[3:]
a[1]=123
b[2]=234
print(arr)
print(id(a),id(b),id(arr[3:]))

运行结果如下:

我们的数组:
[ 0  1  2  3  4  5  6  7  8  9 10 11]
创建切片:
[  0   1   2   3 123 234   6   7   8   9  10  11]
4545878416 4545878496 4545878576

变量 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 ('创建 a 的深层副本:')
b = a.copy()  
print ('数组 b:')
print (b)
# b 与 a 不共享任何内容  
print ('我们能够写入 b 来写入 a 吗?')
print (b is a)
print ('修改 b 的内容:')
b[0,0]  =  100  
print ('修改后的数组 b:')
print (b)
print ('a 保持不变:')
print (a)

运行结果如下:

数组 a:
[[10 10]
 [ 2  3]
 [ 4  5]]
创建 a 的深层副本:
数组 b:
[[10 10]
 [ 2  3]
 [ 4  5]]
我们能够写入 b 来写入 a 吗?
False
修改 b 的内容:
修改后的数组 b:
[[100  10]
 [  2   3]
 [  4   5]]
a 保持不变:
[[10 10]
 [ 2  3]
 [ 4  5]]

ps:这里再强调一下赋值和浅拷贝的区别,赋值就是引用,赋值的两端的id是一样的,改变两个中的任意一个都会使另一个发生变化,浅拷贝的话,id是不一样的,只拷贝了第一层,深层的并没有拷贝。

 

 

<think>在Python中,numpy数组列表都是常用的数据结构,但它们有显著的区别。以下是它们的主要区别:1.**数据类型**:-列表(list)可以包含不同类型的元素(异构),例如一个列表可以同时包含整数、字符串对象。-numpy数组(ndarray)通常要求所有元素是同一类型(同构),这有助于提高运算效率存储效率。2.**性能**:-numpy数组在数值计算方面性能远高于列表。因为numpy是用C编写的,并且针对向量化操作进行了优化。-列表在Python中是动态数组,每次操作都可能涉及类型检查函数调用,因此在大量数值计算时较慢。3.**功能**:-numpy数组提供了大量的数学函数(如线性代数、傅里叶变换等)操作(如广播、索引、切片等)。-列表内置的方法相对较少,主要支持基本的增删改查。4.**内存使用**:-numpy数组在内存中连续存储,因此占用空间小,且可以充分利用CPU缓存。-列表中的每个元素都是一个独立的对象,因此需要额外的空间存储指针对象信息,内存占用更大。5.**维度**:-列表可以嵌套形成多维结构(如列表的列表),但操作复杂且效率低。-numpy数组原生支持多维数组,并提供了丰富的多维操作接口。6.**广播**:-numpy支持广播机制,允许不同形状的数组进行算术运算。-列表不支持广播,需要手动实现或使用循环。7.**修改大小**:-列表可以动态改变大小(通过append,extend等方法)。-numpy数组在创建后大小是固定的,改变大小需要创建一个新数组(使用resize函数除外,但通常不推荐)。下面通过一些代码示例来展示它们的区别:**示例1:数据类型**```python#列表可以包含不同类型list_example=[1,"hello",3.14,[1,2]]print(list_example)#[1,'hello',3.14,[1,2]]#numpy数组要求同类型,如果类型不同,会尝试向上转型(如intfloat转为float,数值字符串转为字符串)importnumpyasnparr_example=np.array([1,2.0,'3'])#全部转为字符串类型print(arr_example)#['1''2.0''3']```**示例2:性能对比(向量加法)**```pythonimportnumpyasnpimporttime#列表加法size=1000000list1=list(range(size))list2=list(range(size))start=time.time()list_result=[a+bfora,binzip(list1,list2)]list_time=time.time()-startprint(f"Listtime:{list_time:.6f}seconds")#numpy数组加法arr1=np.arange(size)arr2=np.arange(size)start=time.time()arr_result=arr1+arr2numpy_time=time.time()-startprint(f"Numpytime:{numpy_time:.6f}seconds")#通常numpy会快很多print(f"Speedup:{list_time/numpy_time:.1f}times")```**示例3:多维数组**```python#列表嵌套实现多维数组list_2d=[[1,2,3],[4,5,6],[7,8,9]]print("List:",list_2d[1][2])#6#numpy多维数组arr_2d=np.array([[1,2,3],[4,5,6],[7,8,9]])print("Numpy:",arr_2d[1,2])#6#切片操作print("Listslice:",[row[0:2]forrowinlist_2d[1:3]])#[[4,5],[7,8]]print("Numpyslice:",arr_2d[1:3,0:2])#[[4,5],[7,8]]```**示例4:广播机制**```python#列表不支持广播,需要循环list_a=[1,2,3]list_b=[10,20,30]#列表相加:对应元素相加list_sum=[a+bfora,binzip(list_a,list_b)]#尝试将列表与一个数相加,需要循环list_add_10=[x+10forxinlist_a]#numpy支持广播arr_a=np.array([1,2,3])arr_b=np.array([10,20,30])arr_sum=arr_a+arr_barr_add_10=arr_a+10#广播:将10加到每个元素```**示例5:数学函数**```python#列表求平方,需要循环或列表推导式list_sq=[x**2forxinlist_a]#numpy数组求平方arr_sq=np.square(arr_a)#或者arr_a**2```总结:在需要进行大量数值计算、多维数组操作时,应优先使用numpy数组。而对于一般性的数据存储处理,尤其是元素类型多样时,使用列表更为灵活。§§相关问题§§:1.如何在numpy中创建一个全零数组?2.numpy的广播机制是什么?请举例说明。3.如何改变numpy数组的形状?4.在什么情况下应该使用列表而不是numpy数组?</think>### NumPy数组与列表的区别 #### 1. **内存布局与性能** ```python import numpy as np import sys # 列表内存占用 list_data = [1, 2, 3, 4, 5] print("List memory:", sys.getsizeof(list_data) + sum(sys.getsizeof(x) for x in list_data)) # NumPy数组内存占用 arr_data = np.array([1, 2, 3, 4, 5]) print("Array memory:", arr_data.nbytes) ``` **解释**: - **列表**:存储对象的指针,每个元素独立分配内存(非连续),内存占用高 - **数组**:连续内存块存储同类型数据,内存占用低(示例中列表占用约200字节,数组仅20字节) - **性能**:数组的向量化操作比列表循环快10-100倍(C语言底层优化) --- #### 2. **数据类型要求** ```python # 列表支持混合类型 mixed_list = [1, "text", 3.14, True] print("List types:", [type(x) for x in mixed_list]) # 数组自动类型转换 mixed_arr = np.array([1, "text", 3.14]) print("Array type:", mixed_arr.dtype) # 输出: <U32 (Unicode字符串) ``` **解释**: - **列表**:可容纳任意Python对象(异构数据) - **数组**:要求元素类型一致(同构数据),不同类型时自动向上转型(如int→float→str) --- #### 3. **功能差异** ```python # 向量化运算示例 arr = np.arange(1e6) %timeit arr * 2 # 约1ms (微秒级) lst = list(range(int(1e6))) %timeit [x*2 for x in lst] # 约100ms (毫秒级) # 广播机制 matrix = np.array([[1,2],[3,4]]) print("Broadcast:\n", matrix * [10, 100]) ``` **功能对比**: | **操作** | **列表** | **NumPy数组** | |----------------|-------------------|---------------------| | 数学运算 | 需循环 | 向量化操作 | | 维度支持 | 嵌套列表 | 原生多维数组 | | 广播机制 | 不支持 | 支持自动广播 | | 高级索引 | 基础索引 | 布尔/花式索引 | | 数学函数 | 需math模块+循环 | np.sin()等直接调用 | --- #### 4. **修改行为** ```python # 列表副本示例 original_list = [1, 2, 3] copy_list = original_list[:] copy_list[0] = 99 print("Original list:", original_list) # 不变 # 数组视图示例 arr = np.array([1, 2, 3]) view = arr[:2] view[0] = 99 print("Original array:", arr) # [99, 2, 3] 被修改! ``` **关键区别**: - **列表切片**:创建独立副本 - **数组切片**:默认创建视图(共享内存),需显式`.copy()`避免意外修改 --- ### 总结对比表 | **特性** | **Python列表** | **NumPy数组** | |------------------|-----------------------------------|-----------------------------------| | 内存效率 | 低(存储指针+对象) | 高(连续内存块) | | 数据类型 | 支持异构数据 | 要求同构数据 | | 性能 | 慢(解释器开销) | 快(C优化+向量化) | | 多维支持 | 需嵌套列表 | 原生ndarray结构 | | 广播机制 | 不支持 | 支持 | | 复制行为 | 切片创建新对象 | 切片创建视图 | | 数学操作 | 需循环或map() | 内置向量化操作 |
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值