🖖导读🖖
学习NumPy已经有一段时间,感觉NumPy库真的是太方便了。但是唯独没有看到如何快速复制一个数组的这么一个概念,但是不能轻易放弃,好东西总是放在后面的,这不,本节我们就带来了副本和视图,让我们开始进行体验吧!
🎎 概念
什么是副本?
副本(copy)是一个新的数组对象,它是原始数组的一个独立的拷贝。对副本进行的任何更改都不会影响到原始数组。
什么是视图?
视图(view)是一个指向原始数组的引用,它是原始数组的一个别名。视图与原始数组共享数据,因此对视图进行的任何更改都会影响原始数组。
🧩 使用
我们先看一下传统Python的使用方式
python
# -*- coding: utf-8 -*-
"""
@Created on : 2024/5/22 11:16
@creator : er_nao
@File :numpy_28_副本和视图.py
@Description :
"""
import numpy as np
# 传统数组之间相互赋值
x = np.array([1, 2, 3, 4, 5, 6, 7, 8])
print('原数组')
print(x)
# 获取数组在内存中的id
print('x数组的id: ')
print(id(x))
print('\n')
# 将x数组赋值给y
y = x
print('y数组的id:')
print(id(y))
print('\n')
# 修改y数组,测试x数组是否会发生变化
y.shape = 2, 4
print('打印y数组')
print(y)
print('\n')
print('打印x数组')
print(x)
# 输出结果
# 原数组
# [1 2 3 4 5 6 7 8]
# x数组的id:
# 1955896948176
#
#
# y数组的id:
# 1955896948176
#
#
# 打印y数组
# [[1 2 3 4]
# [5 6 7 8]]
#
#
# 打印x数组
# [[1 2 3 4]
# [5 6 7 8]]
可以看到,数组y的变化也引起了x数值的变化,且两个数组的格式最后也是一样的,因为他们有共同的id,都是在内存中读取的。
接下来,我们看看NumPy给我们都提供了哪些函数?
首先我们看副本相关知识,副本这边NumPy给我们提供的函数是:copy()函数。copy()函数是先创建一个副本,然后对副本的数据进行修改,它主要的一点就是不会修改原数组的内容,这一点跟传统的数组之间的赋值非常的像。但是实践是检验的唯一真理,我们在编辑器中进行测试一下,用代码说话。
相关代码:
bash
# copy()函数的使用
x = np.arange(16).reshape(2, 8)
print('原数组')
print(x)
# 获取数组在内存中的id
print('x数组的id: ')
print(id(x))
print('\n')
# 将x数组copy给y
y = x.copy()
print('y数组的id:')
print(id(y))
print('\n')
# 修改y数组,测试x数组是否会发生变化
y.shape = 4, 4
print('打印y数组')
print(y)
print('\n')
print('打印x数组')
print(x)
# # 输出结果
# 原数组
# [[ 0 1 2 3 4 5 6 7]
# [ 8 9 10 11 12 13 14 15]]
# x数组的id:
# 2507393856880
#
#
# y数组的id:
# 2507951925520
#
#
# 打印y数组
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]
# [12 13 14 15]]
#
#
# 打印x数组
# [[ 0 1 2 3 4 5 6 7]
# [ 8 9 10 11 12 13 14 15]]
从上面的代码中可以看出,改变数组y的结构,并没有影响到数组x.因为它们在内存中的指向id都是不同的,它们不是共用的一个内存同一位置。
再来看看视图究竟是怎么个回事儿?同样的,NumPy库给我们也提供了view()函数。view()函数是指向原始数组的引用,从上面的概念可以看到,它是和原始数组共用数据的。检验一下吧!
相关代码:
bash
# view()函数的使用
x = np.arange(16).reshape(2, 8)
print('原数组')
print(x)
# 获取数组在内存中的id
print('x数组的id: ')
print(id(x))
print('\n')
# 将x数组view给y
y = x.view()
print('y数组的id:')
print(id(y))
print('\n')
# 修改y数组,测试x数组是否会发生变化
y.shape = 4, 4
print('打印y数组')
print(y)
print('\n')
print('打印x数组')
print(x)
# 输出结果
# 原数组
# [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
# x数组的id:
# 1875672167888
#
#
# y数组的id:
# 1875675695120
#
#
# 打印y数组
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]
# [12 13 14 15]]
#
#
# 打印x数组
# [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
可以看到,使用view()函数也是没有影响到原数组的。
那么NumPy为什么给我们提供两种不同的函数,但返回的结果都是不影响原数组。它们到底有什么区别呢?
🎭 区别
副本和视图的应用它们的区别到底是什么,这得根据项目环境和当下环境来选择进行使用的。
主要区别在于空间的利用和时间的损耗上:
- 当需要对数组进行操作,但不希望更改原始数组时,应使用副本。
- 当需要共享数据以节省内存时,应使用视图。
- 对于大型数组,创建副本可能会消耗大量内存和时间。在这种情况下,可以考虑使用视图。
- 当需要将数组转换为不同的数据类型时,可以使用
astype()
方法创建视图。但是,请注意,这可能会导致数据截断或溢出。
这里我们主要测试一下内存的消耗和时间的情况下,两种类型的表现。
可以看到,copy()其实是新创建了一个数组,而view()是直接进行引用的,这占用内存的大小是相当恐怖的,而且在消耗时间上,copy函数消耗了283毫秒,而view函数几乎是不占用时间的。
这里做一个总结:虽然copy()的消耗比view()多得多,但是我们在项目中使用时,是根据情况而定的,并不是哪个消耗的少而频繁的进行使用。