[python学习笔记]对象、引用、浅复制、深复制

学了这么多年编程,发现在学校都是浑水摸鱼,从来没有精通过一门语言,一个月熟悉python和算法。不积硅步,无以至千里。 

本文笔记来自以下博客,请参考原文。

Python:深拷贝与浅拷贝 - 七落安歌 - 博客园

https://zhuanlan.zhihu.com/p/9312146798

【Python基础】变量?对象?引用?赋值?一个例子解释清楚!_python 对象赋值-CSDN博客

对象:被分配的一块内存,存储所代表的值

在 Python 中,所有的数据都是对象,包括基本数据类型(例如整数、浮点数、字符串等)以及用户自定义的类型(类的实例等)。

什么是对象

  1. 在Python中,对象是指内存中存储的数据结构。
  2. 它可以是任何数据类型,如数字、字符串、列表、字典等。
  3. 当在Python中创建一个变量时,实际上是在创建一个指向对象的引用
  4. 每个对象都有一个唯一的标识符(ID)和一个类型。

对象的三大特征

  1. 类型(Type)决定对象可以进行的操作、决定对象的行为和属性
  2. 标识(Identity)唯一标识符、使用id()函数查看、类似内存地址的概念
  3. 值(Value)对象存储的实际数据、可以是数字、文本、集合等

引用:自动形成的从变量到对象的指针

  1. 引用是指向某个对象的指针。你可以把它看作是变量与其对应对象之间的联系。
  2. Python中的变量并不是直接存储数据,而是存储对象的引用
  3. 当你给一个变量赋值时,实际发生的是创建一个对对象的引用。

引用允许多个变量指向同一个对象,这样可以在不复制数据的情况下进行操作,节省内存。

变量-对象-引用的关系

  1. 变量是对对象的引用,多个变量可以指向同一个对象。
    1. 变量本身并不存储数据,而是指向内存中的某个对象。
    2. 对象是存储数据的地方,变量通过引用该对象。
  2. 引用 是指向某个对象的内存地址或位置。对象 是存储在内存中的数据。
  3. 变量 存储 引用,而 引用 指向对象(在堆内存中)的存储位置(地址)。
  4. 对象 本身是存储在 堆内存 中的, 就是对象存储的具体数据。
  5. 地址 是堆内存中对象的具体位置,每个对象在内存中都有唯一的地址。
  6. 引用 是指一个变量指向内存中的某个对象。
    1. 当你创建一个变量并将它赋值给另一个变量时,
    2. 实际是在创建一个对相同对象的引用,而不是复制对象的内容。

赋值和引用传递

当你将一个对象赋值给另一个变量时,实际上是将对象的引用传递给了新的变量,而不是复制对象本身。这意味着两个变量将指向同一个对象。

a = 1
b = a
a = a + 1

首先将 1 赋值于 a,即 a 指向了 1 这个对象。

接着 b = a 则表示让变量 b 也同时指向 1 这个对象。Python 的对象可以被多个变量所指向(引用)。

最后执行 a = a + 1,在这里需要注意的是,Python 的基础数据类型(例如整型(int)、字符串(string)等)是不可变的

所以,a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它。但是 b 仍然不变,仍然指向 1 这个对象。

因此最后的结果是,a 的值变成了 2,而 b的值不变仍然是 1。
 

简单的赋值 b = a,并不表示重新创建了新对象,只是让同一个对象被多个变量指向或引用。 

a = [1, 2]
a[1] = a
print(a[1])

 这段代码中创建了一个列表 a,其中包含两个元素(1 和 2),然后 a[1] 被赋值为整个列表 aa[1] = a),当你打印 a[1] 时,它实际上是指向列表 a 本身。

 可变对象和不可变对象

  • 不可变对象:一旦创建就不可修改的对象(值内存地址固定后不可以再修改其值),包括字符串、元组、数值类型(整型、浮点型)、布尔类型。该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。修改变量值不会影响其他变量。

  • 可变对象:可以修改的对象(值内存地址固定后还可以修改其值),包括列表、字典、集合。该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。修改对象时会影响所有引用该对象的变量。

内存管理和引用计数

python使用 引用计数 来管理内存。这意味着每个对象都有一个与之关联的计数器,记录了有多少个变量引用了该对象。引用计数的工作原理:

  1. 每当你创建一个对象时,Python会自动为这个对象分配内存并将引用计数设置为 1。
  2. 如果你将这个对象赋值给另一个变量,引用计数会增加,
  3. 直到没有任何变量再引用这个对象时,引用计数会减为零。
  4. 此时,Python会自动将该对象删除,释放内存。

Python 内不可变对象的内存管理方式是引用计数。

import copya = "张小鸡"
b = a
c = copy.copy(a)
d = copy.deepcopy(a)print "赋值:id(b)->>>", id(b)
print "浅拷贝:id(c)->>>", id(c)
print "深拷贝:id(d)->>>", id(c)
#三个都相同

 因为我们这里操作的是不可变对象,Python 用引用计数的方式管理它们,所以 Python 不会对值相同的不可变对象,申请单独的内存空间。只会记录它的引用次数。

浅拷贝与深拷贝

拷贝指的是创建一个新对象,这个新对象的内容是源对象的副本。拷贝可以分为 浅拷贝 和 深拷贝,不论对于浅拷贝还是深拷贝对可变对象他们都会生成新的引用,对不可变对象不生成新的引用,它们的区别主要体现在对象中是否包含对其他对象的引用

浅拷贝只复制了对象本身的引用,而不会复制对象内部的子对象。因此,如果对象内部包含可变对象(如列表、字典),这些子对象依然是共享的。

如果对象内有可变元素,修改这些元素会影响到原始对象

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)  # 浅拷贝b[2].append(5)
print(a)  # 输出 [1, 2, [3, 4, 5]]
print(b)  # 输出 [1, 2, [3, 4, 5]]

对于简单的可变数据类型,浅拷贝相当于将原对象的值进行拷贝,需要在内存空间中开辟一块新的内存空间

import copy# 对于可变数据类型的浅拷贝
list1 = [1, 3, 5]
list2 = copy.copy(list1)
print(list1, id(list1))  # [1, 3, 5] 2029785917952
print(list2, id(list2))  # [1, 3, 5] 2029788974656

对于复杂的可变数据类型,浅拷贝只能拷贝可变数据类型的最外层对象,而无法拷贝内层对象,所以只需要为最外层对象开辟内存空间,内层对象拷贝之后的引用关系和原对象保持不变。

import copylist1 = [1, 3, 5, [7, 9]]
list2 = copy.copy(list1)
print(list1, id(list1))  # [1, 3, 5, [7, 9]] 1757227908928
print(list2, id(list2))  # [1, 3, 5, [7, 9]] 1757227822720# 但是对于内层[7.9],这也是一个列表,也需要占用内存地址,那么其拷贝过程的内存是否相同呢
print(id(list1[3]))  # 2710364155712
print(id(list2[3]))  # 2710364155712

对于简单的不可数据类型,由于不可变数类型地址一旦固定,其值就无法改变了,又由于浅拷贝需要把自身的对象空间赋值给另外一个对象,为了保持数据一致,只能让其指向相同的内存空间(不需要额外开辟内存空间)

import copytuple1 = (1, 3, 5)
tuple2 = copy.copy(tuple1)print(tuple1, id(tuple1))  # (1, 3, 5) 1831160185728
print(tuple2, id(tuple2))  # (1, 3, 5) 1831160185728

 对复杂的不可变数据类型,浅拷贝也只能拷贝最外层对象,无法拷贝内层对象

1)当浅复制的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。

2)当浅复制的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:

第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。

深拷贝与浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关系。

import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)  # 深拷贝b[2].append(5)
print(a)  # 输出 [1, 2, [3, 4]]
print(b)  # 输出 [1, 2, [3, 4, 5]]

import copya = [1, 3, 5]
b = copy.deepcopy(a)
print(a, id(a))  # [1, 3, 5] 2434688069312
print(b, id(b))  # [1, 3, 5] 2434690765888
import copya = [1, 3, 5, [7, 9]]
b = copy.deepcopy(a)
print(a, id(a))  # [1, 3, 5, [7, 9]] 2162810741504
print(b, id(b))  # [1, 3, 5, [7, 9]] 2162810727424
print(id(a[3]))  # 2162810727040
print(id(b[3]))  # 2162810727232

对于可变数据类型的深拷贝,深拷贝拷贝了所有的数据并开辟对应的内存空间储存。

import copya = (1, 3, 5, (7, 9))
b = copy.deepcopy(a)
print(a, id(a))  # (1, 3, 5, (7, 9)) 2029346995712
print(b, id(b))  # (1, 3, 5, (7, 9)) 2029346995712print(id(a[3]))  # 2029346562432
print(id(b[3]))  # 2029346562432

对于不可变数据类型的深拷贝,不管是简单的还是复杂的,深拷贝都只能对象的引用关系,他们指向了相同的内存空间。

可变嵌套不可变数据类型

外层对象是可变数据类型,所以可以进行完全拷贝(需要生成内存空间),但是内层对象是不可变数据类型,所以只能进行拷贝引用关系

import copya = [1, 3, 5, (7, 9)]
b = copy.copy(a)
c = copy.deepcopy(a)
print(id(a))  # 1186799997824
print(id(b))  # 1186799988352
print(id(c))  # 1186799988672print(id(a[3]))  # 1700778811776
print(id(b[3]))  # 1700778811776
print(id(c[3]))  # 1700778811776

不可变嵌套可变数据类型

如果一个不可变数据类型包含了可变数据类型,浅拷贝的结论与之前一致,都只能拷贝引用关系。但是对于深拷贝而言,这种数据类型整体都可以进行完全拷贝

d = (1, 3, 5, [7, 9])
e = copy.copy(d)
f = copy.deepcopy(d)print(id(d))  # 2193468143712
print(id(e))  # 2193468143712
print(id(f))  # 2193468262576 内存地址不一样了print(id(d[3]))  # 2764873530496
print(id(e[3]))  # 2764873530496
print(id(f[3]))  # 2764873530560

参数传递机制:传值还是传引用?

函数的参数传递机制是基于对象的引用的。因此,参数的传递机制实际上是通过引用传递的,但具体的行为取决于对象的可变性。

不可变数据类型(Immutable Types)
定义:创建后不能被修改的数据类型,每次修改都会创建一个新的对象

可变数据类型(Mutable Types)
定义:创建后可以被修改的数据类型,可以直接在原对象上进行修改

变量 

将变量视为一个容器,它用来存放某个值。当你给一个变量赋值时,实际上是将数据存放在这个“盒子”里。这个观点对于基本类型的数据(如整数、浮点数、字符串等)是成立的,因为这些数据是值类型,赋值操作是将数据的副本存储到变量中。

x = 10
y = x  # 复制 x 的值给 y
x = 20
print(x)  # 输出 20
print(y)  # 输出 10,y 仍然是原来的值

当我们讨论引用类型(如列表、字典、对象等)时。在这种情况下,变量存储的不是数据本身,而是数据的引用(即指向数据位置的指针或内存地址)。这意味着,当你将一个变量赋值给另一个变量时,两个变量实际上指向同一个数据对象。

a = [1, 2, 3]  # a 是一个列表
b = a          # b 是对 a 的引用,它指向同一个列表
b.append(4)    # 修改 b,也会影响 a
print(a)       # 输出 [1, 2, 3, 4]
print(b)       # 输出 [1, 2, 3, 4]

 python中变量在内存中的存储方式

python变量的存储原理详解_python变量存储机制-CSDN博客

id比较的是对象的内存地址是否相等
==比较的是对象的值是否相等
is比较的是对象的内存地址和值是否相等

Python中堆里面存放的是具体的对象,在堆中Python会为其分配具体的内存空间,此地址即为此对象在内存中的地址
Python中栈里面存放的是对象的地址,而不是对象本体

需要注意的是在Python中会为匿名列表对象匿名字典对象以及短字符串创建缓存区,,并且Python自带小对象整数池(-5~256)

1. 针对匿名列表和字典对象(即不存在变量名的引用)

 列表和字典这种可变对象当为匿名对象是会向外暴露一个内存地址,不论里面的内容怎么变,该地址不变,所以下面的地址都一样。

id([1,2,3]) == id([4,5,6])  # Trueid([1])==id([1,2,3,4,5,6])  # Trueid({1:1,2:2})==id({3:3,4:4})  # True'''但是如果存在对象的引用,即非匿名对象,上面的结果都是False,如下'''a=[1,2,3];b=[4,5,6];c={1:1,2:};d={3:3,4:4}id(a)==id(b)  # Fasleid(c)==id(d)  # Fasle

2. 针对短字符串

Python会为短字符串创建内存缓存,即针对相同的短字符串,只会创建一个内存空间,不会为相同的短字符串创建多个内存空间,但是长字符串不会如此

3. 针对小整数

Python会为(-5,256)之间的整数分配独立的内存空间,当有对象调用的时候会直接从里面取地址,不论调用多少次,都不会再为这些对象创建新的内存空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/496602.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

arm架构 uos操作系统离线安装k8s

目录 操作系统信息 安装文件准备 主机准备 主机配置 配置hosts(所有节点) 关闭防火墙、selinux、swap、dnsmasq(所有节点) 系统参数设置(所有节点) 配置ipvs功能(所有节点) 安装docker(所有节点) 卸载老版本 安装docke…

Animated Drawings:让纸上的角色动起来

前言 今天介绍的这个工具非常的有意思:它可以让我们在纸上绘画的角色动起来。先一起来看看效果: 准备 首先,我们先准备一张绘画。可以在纸上进行绘制,也可以在电子设备上进行绘制。绘制内容不限,在这里为了方便演示&am…

【WRF模拟】如何得到更佳的WRF模拟效果?

【WRF模拟】如何得到更佳的WRF模拟效果? 模型配置(The Model Configuration)1.1 模拟区域domain设置1.2 分辨率Resolution (horizontal and vertical)案例:The Derecho of 29-30 June 2012 1.3 初始化和spin-up预热过程案例1-有无…

javaweb 04 springmvc

0.1 在上一次的课程中,我们开发了springbootweb的入门程序。 基于SpringBoot的方式开发一个web应用,浏览器发起请求 /hello 后 ,给浏览器返回字符串 “Hello World ~”。 其实呢,是我们在浏览器发起请求,请求了我们…

openGauss与GaussDB系统架构对比

openGauss与GaussDB系统架构对比 系统架构对比openGauss架构GaussDB架构 GaussDB集群管理组件 系统架构对比 openGauss架构 openGauss是集中式数据库系统,业务数据存储在单个物理节点上,数据访问任务被推送到服务节点执行,通过服务器的高并…

JS 设置按钮的loading效果

本文是在其他博主的博客JS学习笔记 | 遮罩层Loading实现_jsp loading-CSDN博客基础上,进行实践的。 目录 一、需求 二、Jspcss实现代码 一、需求 在springboot项目中的原始html5页面中,原本的功能是页面加载时,使用ajax向后端发送请求&…

QT线程 QtConcurrent (深入理解)

QT多线程专栏共有16篇文章,从初识线程到、QMutex锁、QSemaphore信号量、Emit、Sgnals、Slot主线程子线程互相传值同步变量、QWaitCondition、事件循环、QObjects、线程安全、线程同步、线程异步、QThreadPool线程池、ObjectThread多线程操作、 moveToThread等线程操作进行了全…

“游戏信息化”:游戏后台系统的未来发展

3.1可行性分析 开发者在进行开发系统之前,都需要进行可行性分析,保证该系统能够被成功开发出来。 3.1.1技术可行性 开发该游戏后台系统所采用的技术是vue和MYSQL数据库。计算机专业的学生在学校期间已经比较系统的学习了很多编程方面的知识,同…

第十六届“蓝桥杯”全国软件和信息技术专业人才大赛简介及资料大全

蓝桥杯全国软件和信息技术专业人才大赛是由工业和信息化部人才交流中心主办的一项全国性竞赛,面向全国高校大学生,累计参赛院校超过1200余所,参赛人数达40万人,是我国极有影响力的高校IT类赛事。 “第十六届蓝桥杯全国软件和信息…

电子电气架构 --- 什么是自动驾驶技术中的域控制单元(DCU)?

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源&…

yolov5及其算法改进

yolov5及其算法改进 1、YOLOV5目标检测简介2、前处理2.1、自适应 Anchor 计算2.2、自适应计算 Anchor 的流程如下:2.3、图像自适应3、YOLOV4与YOLOV5的架构区别3.1、SiLU激活函数3.2、CSPBlock结构图3.3、yolov5的spp改进4、正负样本匹配与损失函数4.1、坐标表示4.2、正负样本…

WPF 绘制过顶点的圆滑曲线(样条,贝塞尔)

项目中要用到样条曲线,必须过顶点,圆滑后还不能太走样,捣鼓一番,发现里面颇有玄机,于是把我多方抄来改造的方法发出来,方便新手: 如上图,看代码吧: -------------------…

阿里云新用户服务器配置

创建和链接实例 创建实例,点击左侧标签栏总的实例, 找到链接帮助 根据帮助中的ip信息,然后启用vscode的ssh链接 ctrlp选择配置,输入公网的ip即可 passwd修改root密码 安装conda 参考 https://blog.csdn.net/adreammaker/arti…

最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解LRMOP1-LRMOP6及工程应用---盘式制动器设计,提供完整MATLAB代码

一、麋鹿优化算法 麋鹿优化算法(Elephant Herding Optimization,EHO)是2024年提出的一种启发式优化算法,该算法的灵感来源于麋鹿群的繁殖过程,包括发情期和产犊期。在发情期,麋鹿群根据公麋鹿之间的争斗分…

Spring事务回滚

Transactional注解 Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。 Transactional注解:我们一般会在业务层当中来控制…

AT24C02学习笔记

看手册: AT24Cxx xx代表能写入xxK bit(xx K)/8 byte 内部写周期很关键,代表每一次页写或字节写结束后时间要大于5ms(延时5ms确保完成写周期),否则时序会出错。 页写:型不同号每一页可能写入不同大小的…

Vite内网ip访问,两种配置方式和修改端口号教程

目录 问题 两种解决方式 结果 总结 preview.host preview.port 问题 使用vite运行项目的时候,控制台会只出现127.0.0.1(localhost)本地地址访问项目。不可以通过公司内网ip访问,其他团队成员无法访问,这是因为没…

Python基础语法知识——列表、字典、元组与集合

列表(list)、字典(dictionary)、元组(tuple)与集合(set)都可以看成存储数据的容器,但是前两者常用,后两者用得相对较少。 目录 1 列表(list) 1.1列表入门 1 列表(list) 1.1列表入门 class1["李白…

JVM调优实践篇

理论篇 1多功能养鱼塘-JVM内存 大鱼塘O(可分配内存): JVM可以调度使用的总的内存数,这个数量受操作系统进程寻址范围、系统虚拟内存总数、系统物理内存总数、其他系统运行所占用的内存资源等因素的制约。 小池塘A&a…

EKF 自动匹配维度 MATLAB代码

该 M A T L A B MATLAB MATLAB代码实现了扩展卡尔曼滤波( E