Python中赋值、引用、深浅拷贝的区别和联系

news2025/1/23 4:04:48

文章目录

  • 一、对象的唯一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 np

a = [1, 2, 3, 4, 5]
def fun(data_in):
    print(data_in, '  >>>id=', id(data_in))

    b = data_in
    print(b, '  >>>id=', id(b))

    b[0] = 99
    print(b, ' >>>id=', id(b))
    print(data_in, ' >>>id=', id(data_in))

    return b


print(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 = 1
    p2.append(2)
    return

a, 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 b

print(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 b

print(fun(0))
print(fun(0))
print(fun(0))

五、深拷贝和浅拷贝

.copy()是浅拷贝,.deepcopy()是深拷贝。

相同点:两个操作都会创建一个新的对象,新对象的id都和原始对象的id不同;

本质区别:拷贝出来的对象的id不同,即内存地址不同。

import copy

a = [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 copy

a = [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]))

运行结果:

【解释】:

  1. b是由a直接赋值得来的,所以是引用,b的所有属性都和a完全一致,所以a和b的id一致,且a[1]和b[1]的id一致;

  2. c和d是由a浅拷贝得来,所以c, d和a的id不同,但c, d的子对象和a的子对象的id相同;

  3. e是由a深拷贝得来,所以e和a已经完全没有关系,对象e和对象a的id不同且两个对象的每个可变子对象的id也不同;

  4. 当a[1]=2时,a[1]是不可变对象,所以不管是深拷贝还是浅拷贝,拷贝得来的对象的第一个元素的id都和a[1]相同;

  5. 当a[1]={}时,a[1]是可变对象,所以深拷贝对象e的子对象e[1]的id和a[1]的id不同。

六、举个栗子

6.1 不可变对象的拷贝

import copy

a = (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 copy

a = [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 copy

a = [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 copy

a = [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]的元素也会对应发生改变。

七、总结

  1. Python中的赋值即引用,进行赋值时不会开辟新的内存空间,也不会产生一个新的变量单独存在,只是在原有数据块上打上了一个新标签。当数据块的任意一个标签发生变化时,本质是这个数据块发生变化,那么指向这个数据块的任意标签都会发生变化。
  2. 浅拷贝常见的形式:切片a=a[:]、工厂函数a=list(a)、copy函数a=a.copy()或a=copy.copy(a)。浅拷贝只拷贝了最外层的对象,子对象只是被拷贝了元素的引用(即对象内的元素没有被拷贝);
  3. 深拷贝只有一种实现形式:a=copy.deepcopy(a)。深拷贝既拷贝了对象,也拷贝了多层的嵌套子元素,深拷贝得到的对象是一个完全全新的对象,和原对象不再有任何关联。

    
    

    (本文完整的pdf请关注“张张学算法”,并回复“015”获取~)

    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

典型回溯题目 - 全排列(一、二)

典型回溯题目 - 全排列(一、二) 46. 全排列 题目链接:46. 全排列状 题目大意: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 注意:(1&#xf…

Linux命令·which·whereis·locate·find

我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索: which 查看可执行文件的位置。whereis 查看文件的位置。 locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。whichwhich命令的作用是&#x…

DJ1-1 操作系统引论

目录 一、操作系统的概念 二、操作系统的目标 三、操作系统的作用 一、操作系统的概念 定义一 操作系统是一组控制和管理计算机软硬件资源、合理地对各类作业进行调度以及方便用户使用的程序集合。 定义二 操作系统是位于硬件层(HAL)之上&#xff…

SQL 基础函数,通配符,BETWEEN ,用法复习

使用 SQL _ 通配符 下面的 SQL 语句选取 name 以一个任意字符开始,然后是 “oogle” 的所有客户: SELECT * FROM Websites WHERE name LIKE _oogle;下面的 SQL 语句选取 name 以 “G” 开始,然后是一个任意字符,然后是 “o”&am…

看完这篇我不信你不会二叉树的层序遍历【C语言】

目录 实现思路 代码实现 之前介绍了二叉树的前、中、后序三种遍历,采用的是递归的方式。今天我们来学习另外一种遍历方式——层序遍历。层序遍历不容小觑,虽然实现方法并不难,但是它所采取的思路是很值得学习的,与前三者不同&am…

学习笔记-架构的演进之容器的封装-3月day06

文章目录前言封装应用的Dockerwhy Docker not LXC?附前言 当文件系统、访问、资源都可以被隔离后,容器就已经具备它降生所需要的全部前置支撑条件了。为了降低普通用户综合使用 namespaces、cgroups 这些低级特性的门槛,2008 年 Linux Kernel 2.6.24 内…

Java中的final和权限修饰符

目录 final 常量 细节: 权限修饰符 Java权限修饰符用于控制类、方法、变量的访问范围。Java中有四种权限修饰符: 权限修饰符的使用场景: final 方法 表明该方法是最终方法,不能被重写。类 表明该类是最终类,不能被继…

Jetpack太香了,让开发效率提升了不少

作者:Jingle_zhang 第三方App使用Jetpack等开源框架非常流行,在Gradle文件简单指定即可。然而ROM内置的系统App在源码环境下进行开发,与第三方App脱节严重,采用开源框架的情况并不常见。但如果系统App也集成了Jetpack或第三方框架…

【UE4 RTS游戏】04-摄像机运动_鼠标移动到视口边缘时移动Pawn

效果可以看到当鼠标移动到视口边缘时,Pawn就会向这个方向移动。步骤打开项目设置,添加两个操作映射打开“CameraPawnController”,在事件图表中添加两个浮点型变量,一个为公有一个为私有。分别命名为“ZoomSensitivity”、“MaxAr…

【Linux】帮助文档查看方法

目录1 Linux帮助文档查看方法1.1 man1.2 内建命令(help)1 Linux帮助文档查看方法 1.1 man man 是 Linux 提供的一个手册,包含了绝大部分的命令、函数使用说明。 该手册分成很多章节(section),使用 man 时可以指定不同的章节来浏…

ubuntu 系统安装docker——使用docker打包python项目,整个流程介绍

目录 1 安装docker和配置镜像源 2 下载基础镜像 3 通过镜像创建容器 4 制作项目所需的容器 5 容器制作好后打包为镜像 6 镜像备份为.tar文件 7 从其他服务器上恢复镜像 8 docker的其他常用指令 首先科普一下镜像、容器和实例; 镜像:相当于安装包&…

怎么用消息队列实现分布式事务?

当消息队列和事务联系在一起时,它指的是消息生产者和消息消费者之间如何保持数据一致性。 什么是分布式事务? 事务是指当我们进行若干项数据更新操作时,为了保证数据的完整性和一致性,我们希望这些更新操作要么都成功&#xff0…

蓝桥杯三月刷题 第五天

文章目录💥前言😉解题报告💥数的分解🤔一、思路:😎二、代码:💥前言 上午没写,下午写了会被朋友拉出去耍,被冷风吹到了,而且被他坑了,根本没有玩骑…

【源码库】在调用 createApp 时,Vue 为我们做了那些工作?

在使用Vue3时,我们需要使用createApp来创建一个应用实例,然后使用mount方法将应用挂载到某个DOM节点上。 那么在调用createApp时,Vue再背后做了些什么事情呢?今天就来扒一扒Vue3的源码,看看调用createApp发生了些什么…

八股文系列:Java虚拟机(JVM)

说一下 JVM 的主要组成部分及其作用? JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、 Execution engine(执行引擎);两个组件为Runtime data area(运行时数据 区)、Native Interface(本地接口)。 Class loader(类装载)&…

IDEA插件开发入门.02

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

【JavaEE初阶】第四节.文件操作 和 IO (上篇)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、文件 1.1 文件的概念 1.2 文件的路径二、 Java中文件系统操作 2.1 File类的属性 2.2 File类的构造方法 2.3 File类的方法 …

ArangoDB

介绍 ArangoDB 是一个原生的多模型开源数据库,具有灵活的文档、图形和键值数据模型。使用方便的类似 SQL 的查询语言或 JavaScript 扩展构建高性能应用程序。主要特点 在集群上安装 ArangoDB —— 安装简单灵活的数据建模:数据建模为键值对、文档或图表的…

企业信息化,电商商品详情API接口,数据返回值说明,商品详情,关键词搜索,价格监控,卖家买家订单等相关数据

都在说API,API到底是什么? 对于很多非IT人士而言,API ≈ 听不懂。 其实日常生活中,我们有很多类似API的场景,比如: 电脑需要调用手机里面的信息,这时候你会拿一根数据线将电脑手机连接起来&a…

[Tomcat]解决IDEA中的Tomcat中文乱码问题

目录 1、IDEA 2、VM options 3、IDEA启动程序的存放目录 4、Tomcat 写在前面:此方法亲测有效!!! 1、IDEA 2、VM options 加上这两行: -Dfile.encodingUTF-8 -Dconsole.encodingUTF-8 3、IDEA启动程序的存放目录…