番外篇之副本和视图的相爱💕相杀🔪

🖖导读🖖

学习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()多得多,但是我们在项目中使用时,是根据情况而定的,并不是哪个消耗的少而频繁的进行使用。

相关推荐
ChoSeitaku25 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___34 分钟前
不使用递归的决策树生成算法
算法
我爱工作&工作love我39 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
秀儿还能再秀1 小时前
机器学习——简单线性回归、逻辑回归
笔记·python·学习·机器学习
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui11 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农2 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法