Python中赋值、引用、深浅拷贝的区别和联系
文章目录
- 一、对象的唯一id
- 二、赋值
- 三、可变对象和不可变对象
- 四、函数的参数传递
- 五、深拷贝和浅拷贝
- 六、举个栗子
- 6.1 不可变对象的拷贝
- 6.2 可变对象的拷贝
- 6.3 可变对象改变外层元素
- 6.4 可变对象改变内层元素
- 七、总结
一、对象的唯一id
python中的所有对象都有自己的唯一id,id在创建对象时就已经分配给对象,id是对象的内存地址,并且在每次运行程序时都不相同(除了某些具有恒定唯一id的对象,比如-5~256之间的整数)。
id():返回对象的唯一id,适用于python中的任何对象,如变量、字符串、列表、字典、元胞等。
a = 5
b = 'hello'
c = (1, 2, 3)print('>>> id(a):', id(a))
print('>>> id(b):', id(b))
print('>>> id(c):', id(c))
运行三次的结果:
从上图可以看出,整数5的id一直不变,但另外两个变量id的每次重新运行程序的结果都不一样。
二、赋值
python中的赋值语句总是建立对象的引用值,而不是简单的复制对象。因此python变量更像是指针,而不是数据存储区域。如下例子:
import numpy as npa = [1, 2, 3, 4, 5]
def fun(data_in):print(data_in, ' >>>id=', id(data_in))b = data_inprint(b, ' >>>id=', id(b))b[0] = 99print(b, ' >>>id=', id(b))print(data_in, ' >>>id=', id(data_in))return bprint(a, ' >>>id=', id(a))
b = fun(a)
print(a, ' >>>id=', id(a))
print(b, ' >>>id=', id(b))
运行结果:
【解释】:在fun()函数中直接用等号赋值语句对b进行操作,本质上是将data_in的地址赋给b,因此主函数中的变量a和fun()函数中的变量b同时指向了同一个地址,因此在fun()函数中对变量b的所有操作都影响到了主函数中的变量a!
三、可变对象和不可变对象
-
可变对象包括:列表(list)、集合(set)、字典(dict);
-
不可变对象:整数(int)、浮点型(float)、字符串(str)、布尔型(bool)、元胞(tuple)。
判断一个对象是否是可变对象,关键是看操作对象前后的内存地址是否发生变化!如果对某个变量进行了操作,但操作前后的内存地址不变,说明这个变量是可变对象;否则这个变量是不可变对象。
可变与不可变的关键是对象内容能否被修改,而不是对象的指向能否被修改!如下例子:
a = 1
print(a, '>>>id=', id(a))
a = 2
print(a, '>>>id=', id(a))
运行结果:
【解释】:a首先被赋值一个整数,然后再被赋值为另一个整数,到这里很多人会说了“int是可变对象”,那可就大错特错了!注意看可以发现两次输出的a变量的内存地址是不同的,说明第一次给a赋值为一个整数1,a指向了第一个内存地址address1,第二次再给a赋值为另一个整数2,a指向了第二个内存地址address2,但“address1和address2中存放的内容分别是1和2”这个事实是不变的,所以先后两次赋值并不是改变了对象的内容,只是第二次赋值时创建了一个新对象,a指向了这个新对象而已。也就是说,变量a改变的只是指向,而不是内容,所以a是不可变对象。
四、函数的参数传递
-
值传递:指在调用函数时将实际参数复制一份传递到函数中,在函数中对参数进行修改不会到影响实际参数;
-
引用传递:只在调用函数时将实际参数的地址传递到函数中,在函数中对参数进行的修改将影响到实际参数。
python既支持值传递,也支持引用传递。解释器会查看对象引用(即对象的内存地址)指向的变量的类型,如果变量是不可变对象,那么函数参数作为值传递;如果变量是可变对象,那么函数参数作为引用传递。对于值传递的传参方式,函数结束之后主函数中该变量值不发生变化;对于引用传递的传参方式,函数之后主函数中该变量值会发生变化。如下例子:
def fun(p1, p2):p1 = 1p2.append(2)returna, b = 0, [1]
print(a, b)
fun(a, b)
print(a, b)
运行结果:
【解释】:fun()函数的参数p1的传入为a(是整数),是不可变对象,所以p1是值传递,函数fun()运行结束后主函数中的a不发生变化;参数p2的传入为b(是列表),是可变对象,所以p2是引用传递,p2指向变量b指向的内存地址,所以当p2发生变化时同时会改变变量b的取值,函数fun()运行结束后b发生改变。
不要使用可变对象作为函数默认参数!!!比如,下面例子:
def fun(a, b=[]):b.append(a)return bprint(fun(0))
print(fun(0))
print(fun(0))
运行结果:
【解释】:因为函数fun()的第二个参数是可变对象,所以并不是每次调用fun()函数时参数b的传入都是空列表。
为了避免此类问题,可以使用如下代码代替:
def fun(a, b=None):if b is None:b = []b.append(a)return bprint(fun(0))
print(fun(0))
print(fun(0))
五、深拷贝和浅拷贝
.copy()是浅拷贝,.deepcopy()是深拷贝。
相同点:两个操作都会创建一个新的对象,新对象的id都和原始对象的id不同;
本质区别:拷贝出来的对象的id不同,即内存地址不同。
import copya = [1, 2, 3, 4, 5]
b = a # 直接用等号进行复制,相当于引用
c = a.copy()
d = copy.copy(a)
e = copy.deepcopy(a)print(a, '>>>id=', id(a))
print(b, '>>>id=', id(b))
print(c, '>>>id=', id(c))
print(d, '>>>id=', id(d))
print(e, '>>>id=', id(e))
print()print(a[1], '>>>id=', id(a[1]))
print(b[1], '>>>id=', id(b[1]))
print(c[1], '>>>id=', id(c[1]))
print(d[1], '>>>id=', id(d[1]))
print(e[1], '>>>id=', id(e[1]))
运行结果:
import copya = [1, {}, 3, 4, 5]
b = a # 直接用等号进行复制,相当于引用
c = a.copy()
d = copy.copy(a)
e = copy.deepcopy(a)print(a, '>>>id=', id(a))
print(b, '>>>id=', id(b))
print(c, '>>>id=', id(c))
print(d, '>>>id=', id(d))
print(e, '>>>id=', id(e))
print()print(a[1], '>>>id=', id(a[1]))
print(b[1], '>>>id=', id(b[1]))
print(c[1], '>>>id=', id(c[1]))
print(d[1], '>>>id=', id(d[1]))
print(e[1], '>>>id=', id(e[1]))
运行结果:
【解释】:
-
b是由a直接赋值得来的,所以是引用,b的所有属性都和a完全一致,所以a和b的id一致,且a[1]和b[1]的id一致;
-
c和d是由a浅拷贝得来,所以c, d和a的id不同,但c, d的子对象和a的子对象的id相同;
-
e是由a深拷贝得来,所以e和a已经完全没有关系,对象e和对象a的id不同且两个对象的每个可变子对象的id也不同;
-
当a[1]=2时,a[1]是不可变对象,所以不管是深拷贝还是浅拷贝,拷贝得来的对象的第一个元素的id都和a[1]相同;
-
当a[1]={}时,a[1]是可变对象,所以深拷贝对象e的子对象e[1]的id和a[1]的id不同。
六、举个栗子
6.1 不可变对象的拷贝
import copya = (1, 2, 3)
b = a
c = copy.copy(a)
d = copy.deepcopy(a)print(a, '>>>id(a)=', id(a))
print()
print('-------赋值/引用-------')
print(b, '>>>id(a)=', id(b))
print()
print('-------浅拷贝-------')
print(c, '>>>id(a)=', id(c))
print()
print('-------深拷贝-------')
print(d, '>>>id(a)=', id(d))
运行结果:
由于a是不可变对象,那么赋值、深浅拷贝之后的内存地址都和原对象相同,即使是被重新赋值,也只是新开辟了一块内存并让a对象指向了新赋值元素,并不改变原有地址内的内容。
6.2 可变对象的拷贝
import copya = [1, 2, 3]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)print(a, '>>>id(a)=', id(a))
print()
print('-------赋值/引用-------')
print(b, '>>>id(b)=', id(b))
print()
print('-------浅拷贝-------')
print(c, '>>>id(c)=', id(c))
print()
print('-------深拷贝-------')
print(d, '>>>id(d)=', id(d))
运行结果:
Python任何时候的赋值都相当于引用,所以b和a的id相同;由于a是可变对象,所以深浅拷贝得到的新对象c和d的id和a不同。
6.3 可变对象改变外层元素
import copya = [1, 2, [3, 4]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(5)print(a, '>>>id(a)=', id(a))
print()
print('-------赋值/引用-------')
print(b, '>>>id(b)=', id(b))
print()
print('-------浅拷贝-------')
print(c, '>>>id(c)=', id(c))
print()
print('-------深拷贝-------')
print(d, '>>>id(d)=', id(d))
运行结果:
Python任何时候的赋值都相当于引用,所以b和a的元素和id完全相同;由于a是可变对象,因此深浅拷贝之后id都发生变化;由于改变的是a外层元素,而深浅拷贝都拷贝了外层对象,所以改变a的外层元素不影响c和d的id。
6.4 可变对象改变内层元素
import copya = [1, 2, [3, 4]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a[2].append(5)print(a, '>>>id(a)=', id(a))
print()
print('-------赋值/引用-------')
print(b, '>>>id(b)=', id(b))
print()
print('-------浅拷贝-------')
print(c, '>>>id(c)=', id(c))
print()
print('-------深拷贝-------')
print(d, '>>>id(d)=', id(d))
运行结果:
Python任何时候的赋值都相当于引用,所以b和a的元素和id完全相同;由于a是可变对象,所以c和d的id和a不相同;由于浅拷贝只拷贝外部对象,对于内部对象只拷贝了元素引用,所以当a的内部对象a[2]发生改变时,c[2]的元素也会对应发生改变。
七、总结
- Python中的赋值即引用,进行赋值时不会开辟新的内存空间,也不会产生一个新的变量单独存在,只是在原有数据块上打上了一个新标签。当数据块的任意一个标签发生变化时,本质是这个数据块发生变化,那么指向这个数据块的任意标签都会发生变化。
- 浅拷贝常见的形式:切片a=a[:]、工厂函数a=list(a)、copy函数a=a.copy()或a=copy.copy(a)。浅拷贝只拷贝了最外层的对象,子对象只是被拷贝了元素的引用(即对象内的元素没有被拷贝);
- 深拷贝只有一种实现形式:a=copy.deepcopy(a)。深拷贝既拷贝了对象,也拷贝了多层的嵌套子元素,深拷贝得到的对象是一个完全全新的对象,和原对象不再有任何关联。
(本文完整的pdf请关注“张张学算法”,并回复“015”获取~)
相关文章:

Python中赋值、引用、深浅拷贝的区别和联系
文章目录一、对象的唯一id二、赋值三、可变对象和不可变对象四、函数的参数传递五、深拷贝和浅拷贝六、举个栗子6.1 不可变对象的拷贝6.2 可变对象的拷贝6.3 可变对象改变外层元素6.4 可变对象改变内层元素七、总结一、对象的唯一id python中的所有对象都有自己的唯一id&#…...

春招冲刺(十一):前端面试之网络总结
网络总结 Q1: GET和POST的请求的区别 应用场景:Get是一个幂等请求,一般用于请求资源。post不是幂等请求,一般用于修改资源。缓存:Get请求一般缓存,Post一般不缓存报文格式:Get请求体一般为空,…...

Mybatis插件
插件使用 动手实现plugin 首先我们需要实现一下这个Interceptor,其中plugin和setProperties方法可以不实现,plugin是因为已经有了完善的逻辑,而setProperties,如果不需要在intercept()中使用属性,也可以不设置。然后…...

计算机学科专业基础综合科目(408)
文章目录408 第一章 数据结构数据是客观事物的符号表示,是对现实世界的事物采用计算机能够识别,存储和处理的形式进行描述的符号的集合。 数据元素是数据的基本单位。一个数据元素由若干个数据项组成。数据项又成为简单数据项及复合数据项两种。简单数据…...

centos7安装教程
1.点击文件–新建虚拟机 2.根据图片一直下一步或者做一些改动 3. 点击自定义硬件,点击浏览选中下载好的ISO文件 4.配置完成后启动虚拟机 5.选择语言,中英文都可,按需求选择 6.进行设置目标位置,配置分区 7.选择网络和主机名 8.配置…...

Kafka 重平衡
Kafka 重平衡协调者RebalanceRebalance 条件Rebalance 避免Rebalance : 让单 Group 下所有的 Consumer 怎么消费订阅主题的所有分区Rebalance 时 , 所有 Consumer 要共同参与 (无法消费),在协调者 (Coordinator) 协调下,完成订阅主题分区的分配 协调者…...

PTA:L1-022 奇偶分家、L1-023 输出GPLT、L1-024 后天(C++)
目录 L1-022 奇偶分家 问题描述: L1-023 输出GPLT 问题描述: 实现代码: L1-024 后天 问题描述: 实现代码: 简单题,没写题解,看代码就能看懂 L1-022 奇偶分家 问题描述: 给…...

IDEA插件开发入门.02
前言许久没更新IDEA插件开发系列了。最近刚好在汇总日常开发中常见的代码“异味”,共享文档复制黏贴略显麻烦,所以想着是否可以搞一个IDEA插件来帮忙收集常见代码,毕竟IDEA作为后端程序员必备的开发工具,显然会方便很多。于是&…...

如何用 23 种编程语言说“Hello World”
在编程的世界里," Hello World " 往往是开发者开始学习一种新语言时写的第一个程序。这个简单的程序会将 “Hello World“ 输出在我们的屏幕上。看似很简单的行为,实际上对于每一个新学习编程语言的人来说,它代表着新的起点。那么&…...

【Linux快速入门】文件目录操作
文章目录概念1. Linux文件系统概述2. Linux文件目录结构3. Linux文件和目录操作3.1 文件操作3.1.1 创建文件3.1.2 复制文件3.1.3 移动文件3.1.4 删除文件3.1.5 查看文件3.1.6 输出指令3.1.7 >和>>指令3.2 目录操作3.2.1 创建目录3.2.2 复制目录3.2.3 移动目录3.2.4 删…...

字体反爬慢慢总结破解方式
什么是字体反爬 网页开发者自己创造一种字体,因为在字体中每个汉字都有其代号,那么以后再网页中不会直接显示这个文字的效果。而是显示其代号,因此即使获取了网页的文本内容。也只是获取到文字的代号,而不是文字本身。 简单来说&…...

Kafka 位移提交
Kafka 位移提交自动提交手动提交Consumer 的消费位移 : 记录 Consumer 下一条消息的消费位移 如 : Consumer 已消费 5 条消息 (位移: 0 - 4) , 此时 Consumer 位移 5 : 指向下一条消息的位移 提交位移 (Committing Offsets) : Consumer 向 Kafka 汇报位移数据 Consumer 能同…...

kubernetes--监控容器运行时:Falco
目录 Falco介绍 Falco架构 Falco的安装 告警规则示列 威胁场景测试: 监控容器创建的不可信任进程(自定义规则) Falco支持五种输出告警方式falco.yaml: Falco告警集中化展示: Falco介绍 Falco是一个Linux安全工具…...

HTTP协议详解(上)
目录 前言: 认识URL HTTP协议方法 通过Fiddler抓包 GET和POST之间典型区别 header详解 HTTP响应状态码 常见状态码解释 状态码分类 HTTP协议报文格式 小结: 前言: HTTP协议属于应用层协议,称为超文本传输协议ÿ…...

java性能-原生内存-内存分析
原生内存最佳实践 内存占用 jVM使用的原生内存和堆内存总和就是一个应用程序的总内存——操作系统角度 jvm启动时候加载的类路径下的jar文件相关的内存和系统其他进程共享资源的可能 测量内存占用 线程是个例外——每当创建一个线程操作系统都会分配一些原生内存存储线程栈…...

c++类与对象
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章 🔥座右铭:“不要等到什么都没有了,才下定决心去做” …...

Java并发编程与API详解
文章目录前言操作系统——进程和线程进程进程组成进程状态进程控制进程创建进程终止进程阻塞和唤醒进程通信线程线程组成线程状态线程控制线程的实现方式用户线程内核线程混合方式CPU调度调度的层次调度的实现调度器调度的时机、切换与过程进程调度的方式闲逛进程两种线程的调度…...

【冲刺蓝桥杯的最后30天】day5
大家好😃,我是想要慢慢变得优秀的向阳🌞同学👨💻,断更了整整一年,又开始恢复CSDN更新,从今天开始更新备战蓝桥30天系列,一共30天,如果对你有帮助或者正在备…...

大厂与小厂招人的区别,看完多少有点不敢相信
前两天在头条发了一条招人的感慨,关于大厂招人和小公司招人的区别。 大厂:有影响力,有钱,能够吸引了大量的应聘者。因此,也就有了筛选的资格,比如必须985名校毕业,必须35岁以下,不能…...

前端ES5对象特性
ES5对象特性 对象和函数的原型 JS中每一个对象都有一个特殊的内置属性,这个特殊的对象可以指向其他的对象 我们通过引用对象的属性key来获取一个value时,它会触发 Get 的操作首先检查该对象是否有对应的属性,如果有的话就使用对象内的如果…...

Linux入门介绍及Linux文件与目录结构
前言 本文小新为大家带来 Linux 入门介绍及Linux 文件与目录结构 相关知识,具体内容包括Linux入门介绍(包括:Linux概述,Linux与Windows区别,CentOS 下载地址),Linux文件与目录结构等进行详尽介绍…...

超赞,用python实现流媒体服务器功能,寥寥几句搞定。
步骤: 要使用Python将实时摄像机传送流写入H5页面,可以使用以下步骤。 1、安装必要的软件包。您需要安装OpenCV和Flask以及gunicorn 与 gevent 。您可以通过在终端中运行以下命令来执行此操作。 pip install opencv-python pip install Flask pip ins…...

冥想第七百二十一天
1.3.3周五,又是周五了。今天又运动了5公里,很舒服轻松。 2.还是往常的生活,休息的也很好,开春后跑的一直很好。 3.早上30分钟健康操。中午转了圈, 给大哥说下周去上海。 4.感谢父母,感谢朋友,感…...

06-Oracle表空间与用户管理
本讲主要内容: 1.表空间管理:表空间的作用,创建,修改,删除及管理; 2.用户管理:创建用户,修改用户,删除用户,修改密码,解锁; 3.用户…...

Mysql 索引特点
承接上文Mysql Server原理简介聚簇索引、二级索引、联合索引分别具备什么样的特点?聚簇索引数据跟索引放在一起的叫聚簇索引;数据和索引分开存储的叫非聚簇索引;innodb存储引擎,数据和文件都放在ibd文件中,实际的数据是…...

读书笔记-终身学习
前言人需要终身成长,也需要终身学习,以下是记录个人读书学习的笔记总结,希望能给大家一点借鉴,仅供参考。笔记1、《匠人精神》秋山利辉是日本神奈川县横滨市都筑区“秋山木工”的经营者,从事订制家具制作业务。是一家小…...

了解栈Stack一篇文章就够了
什么是栈栈是一种特殊的线性表,只允许一端进行数据的插入和删除,即先进后出原则。类似于弹夹先装进去的子弹后面出,后放入的子弹先出。栈的底层原理栈是一种线性结构,所以既能使用数组实现,也能使用链表实现࿰…...

CNStack 助推龙源电力扛起“双碳”大旗
作者:CNStack 容器平台、龙源电力:张悦超 、党旗 龙源电力容器云项目背景 龙源电力集团是世界第一大风电运营商, 随着国家西部大开发战略推进,龙源电力已经把风力发电场铺设到全国各地,甚至是交通极不便利的偏远地区&…...

ruoyi-vue-plus1(控制台相关的输出日志)(p6spy插件)(jackson全局配置)(StopWatch)
Jackson配置在启动项目时,我们发现日志打印出这样几行字,初始化了jacdson配置,我们去查看一下来源找。我们找到了一个全局序列化配置类,其中重写了BigNumberSerializer.INSTANCE进去查看发现了这里对于部分范围的数字进行了转为为…...

【Mybatis】| 如何创建MyBatis的工具类
目录🌟更多专栏请点击👇一、前言二、实现过程1. 创建一个ThreadLocal对象2. 初始化SqlSessionFactory3. 获取并存储sqlSession对象4. 关闭sqlSession对象三、 总代码🌟更多专栏请点击👇 专栏名字🔥Elasticsearch专栏e…...