前言

  • Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址
  • 这里会讲三个概念:对象赋值、浅拷贝、深拷贝

名词解释

  • 变量:存储对象的引用
  • 对象:会被分配一块内存,存储实际的数据,比如字符串、数字、列表
  • 引用:变量指向对象,可以理解为指针

实际的一种应用场景

  • 有一个变量 a,存储了一个值
  • 此时想用另一个变量 b 暂时存储变量 a 的值,以便后续使用
  • 然后继续修改变量 a 的值,但修改的时候并不想同步更改变量 b 的值
a=1
b=a
a=2

对象赋值

不可变对象的赋值

a = 1
b = a print(a, b) a += 2
print(a, b) print("a id:", id(a))
print("b id:", id(b)) # 输出结果
1 1
2 1
a id: 4564097808
b id: 4564097776
  • 修改变量 a 的值,不会同步修改变量 b 的值
  • 因为赋值操作 a += 2 后,变量 a 存储的对象引用已经改变了
  • 至于具体的原理,可以看看不可变对象、可变对象的详解 https://www.cnblogs.com/poloyy/p/15073168.html

可变对象的赋值

a = [1, 2, 3]
b = a print(a, b) a[1] = 22
print(a, b) print("a id:", id(a))
print("b id:", id(b)) # 输出结果
[1, 2, 3] [1, 2, 3]
[1, 22, 3] [1, 22, 3]
a id: 4567683136
b id: 4567683136
  • 修改 a 变量的值,会同步修改变量 b 的值,这不符合上面的说的实际应用场景
  • 因为变量 a、b 指向的对象是可变对象,所以它们保存的对象引用都是一样的

拷贝的诞生

  • 那如果要让可变对象也能满足上述实际应用场景,要怎么做呢?
  • 当然就是拷贝
  • 而拷贝又分为浅拷贝、深拷贝,接下来会具体聊一聊两种拷贝的区别

第一个重点总结

  • 对于不可变对象来说,赋值操作其实就可以满足上面说的实际应用场景
  • 所以!后面要讲的浅拷贝、深拷贝对于不可变对象来说,和赋值操作是一样的效果!
  • 记住!浅拷贝、深拷贝只针对可变对象,即列表、集合、字典!

copy 模块

Python 提供了 copy 模块,包含了浅拷贝、深拷贝函数

from copy import copy, deepcopy

# 浅拷贝
copy(x) # 深拷贝
deepcopy(x)

浅拷贝

一句话概括:浅拷贝会创建一个新对象,该新对象存储原始元素的引用

浅拷贝后的值是相同的

  • 将列表赋值给变量 old_list
  • 通过 copy() 方法对 old_list 变量指向的对象进行浅拷贝,并赋值给新变量 new_list
  • 因为是对象进行拷贝,所以 new_list 和 old_list 存储的值是相同的
import copy

old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_list = copy.copy(old_list) print("Old list:", old_list)
print("New list:", new_list) # 输出结果
Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

浅拷贝后的会产生一个新的对象

  • 虽然 old_list 和 new_list 存储的值是相同的,但浅拷贝的操作是产生了一个新的对象
  • 所以 old_list 和 new_list 指向的对象并不是同一个
import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list) old_list.append([5, 6]) print("Old list:", old_list, "id is :", id(old_list))
print("New list:", new_list, "id is :", id(new_list)) # 输出结果
Old list: [[1, 2], [3, 4], [5, 6]] id is : 4366240704
New list: [[1, 2], [3, 4]] id is : 4366246720

可以看到内存地址是不同的,所以给 old_list 新增一个元素并不会同步让 new_list 也新增

原理图

  • 浅拷贝生成了一个新对象,然后赋值给 new_list
  • new_list、old_list 指向的列表对象不是同一个,但值相同
  • 重点:对于列表对象中的元素,浅拷贝产生的新对象只存储原始元素的引用(内存地址),所以两个列表对象的元素的引用都指向同一个内存地址

那为什么要深拷贝呢?

修改列表内的不可变对象元素

上面的栗子是直接添加元素,来看看修改元素会怎么样

# 不可变元素
import copy old_list = [1, 2, "string", (1, 2,)]
new_list = copy.copy(old_list) old_list[1] += 22
old_list[2] += "s"
old_list[3] += (3,) print("Old list:", old_list)
print("New list:", new_list) # 输出结果
Old list: [1, 24, 'strings', (1, 2, 3)]
New list: [1, 2, 'string', (1, 2)]

修改 old_list 的三种不可变对象元素,均不会同步给 new_list

修改不可变对象的原理图

修改列表内的可变对象元素

# 可变元素
import copy old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list) old_list[0][0] += 99
old_list[1][0] += 97 print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0])) # 输出结果
Old list: [[100, 2], [100, 4]] old list id: 4430308096 old list[0] id: 4430302400
new list: [[100, 2], [100, 4]] new list id: 4430308416 new list[0] id: 4430302400

从输出结果看到

  • 两个变量保存了不同的对象引用
  • 但是可变对象元素的内存地址仍然是同一个

修改可变对象的原理图

总结

  • 修改可变对象是在原始对象上直接操作的
  • 浅拷贝产生的新对象是存储的仍然是原始对象的内存地址
  • 所以修改可变对象的时候,新对象的值也会被同步修改,因为新旧列表对象的元素的引用是指向同一个内存地址
  • 当修改可变对象的时候,不满足一开始说的实际应用场景,所以诞生了深拷贝

深拷贝

  • 创建一个新对象,且存储的对象引用也是新的
  • 深,意味着会把所有子元素对象也复制生成一个新对象

栗子一

# 深拷贝
old_list = [[1, 2], [3, 4]]
new_list = copy.deepcopy(old_list) old_list[0][0] += 99
old_list[1][0] += 97 print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0])) # 输出结果
Old list: [[100, 2], [100, 4]] old list id: 4430308480 old list[0] id: 4430211392
new list: [[1, 2], [3, 4]] new list id: 4430308096 new list[0] id: 4430308864

从输出结果看到

  • 两个变量保存了不同的对象引用
  • 可变对象元素(子对象)的内存地址也是不同的

栗子二

假设是一个三维列表呢

# 深拷贝-三维数组
old_list = [[1, [10, 9]], [3, 4]]
new_list = copy.deepcopy(old_list) old_list[0][1][0] += 90 print("Old list:", old_list)
print("New list:", new_list) # 输出结果
Old list: [[1, [100, 9]], [3, 4]]
New list: [[1, [10, 9]], [3, 4]]

两个变量依旧是独立的

深拷贝原理图

浅拷贝的多种实现方式

https://www.cnblogs.com/poloyy/p/15086511.html

Python - 对象赋值、浅拷贝、深拷贝的区别的更多相关文章

  1. python中赋值-浅拷贝-深拷贝之间的关系

    赋值: 变量的引用,没有拷贝空间 对象之间赋值本质上 是对象之间的引用传递而已.也就是多个对象指向同一个数据空间. 拷贝的对象分两种类型: . 拷贝可变类型 浅拷贝: 只拷贝第一层数据,不关心里面的第 ...

  2. Python对象赋值、浅拷贝、深拷贝

    Python中,基本数据类型,理解为常见数据类型:布尔型.整型.浮点型.字符串.列表.元组.字典.集合,随语言不同而不同,但是根据在内存中存储方式的不同,区分开原子类型和容器类型. 对象赋值 对象的赋 ...

  3. python的赋值,深拷贝和浅拷贝的区别

    原文地址https://www.cnblogs.com/xueli/p/4952063.html 赋值:a = [1,2,3,["a","b"]]  b=a,那 ...

  4. python中赋值,深拷贝,浅拷贝区别

    这三种 的区别就是 复制的变量 是否是原变量的引用. 赋值:只是原变量的引用. 浅拷贝和深拷贝的区别 需要通过 子元素 区分 浅拷贝:子元素的 引用相同 深拷贝:所以引用都不相同,完全复制一份 这三种 ...

  5. 深入理解Python中赋值、深拷贝(deepcopy)、浅拷贝(copy)

    赋值 python跟java中的变量本质是不一样的,Python的变量实质上是一个指针(int型或str型),而java的变量是一个可操作的存储空间. a = 123b = a print(id(a) ...

  6. python开发_copy(浅拷贝|深拷贝)_博主推荐

    在python中,有着深拷贝和浅拷贝,即copy模块 下面我们就来聊一下: 运行效果: ================================================== 代码部分: ...

  7. python 中的 赋值 浅拷贝 深拷贝

    1.对象的赋值 都是进行对象引用(内存地址)传递,即 b is a ,a 变 b也变 2.浅拷贝 会创建一个新的对象,对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址) 当我们使用下面的操作 ...

  8. 搞不懂JS中赋值·浅拷贝·深拷贝的请看这里

    前言 百科定义:拷贝就是拷贝指向对象的指针,意思就是说:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间,浅拷贝只是一种简单的拷贝,让几个对象公用一个内存,然而当内存销毁的时候,指向这 ...

  9. [Python] 等号赋值, copy, deepcopy的区别

    参考链接: 1. 介绍python中的可变类型与不可变类型:https://blog.csdn.net/answer3lin/article/details/86430074 (也可以参考转载博客 P ...

随机推荐

  1. 【NX二次开发】开发环境搭建

    1.Visual Studio 版本按照下表选择. UG版本 VS版本 NX1847-NX1872版 Visual Studio 2017 Build 19.10.25017 NX12版 Visual ...

  2. Mybatis 中经典的 9 种设计模式!面试可以吹牛了

    虽然我们都知道有23个设计模式,但是大多停留在概念层面,真实开发中很少遇到.Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  3. Windows的静态库与动态库

    Windows的静态库与动态库 1.静态库 1.1 静态库特点 运行不存在 静态库源码被链接到调用程序中 目标程序的归档 1.2 C语言静态库 C静态库的创建 创建一个静态库项目. 添加库程序,源文件 ...

  4. java并发编程JUC第十二篇:AtomicInteger原子整型

    AtomicInteger 类底层存储一个int值,并提供方法对该int值进行原子操作.AtomicInteger 作为java.util.concurrent.atomic包的一部分,从Java 1 ...

  5. 十七、.net core(.NET 6)搭建基于Quartz组件的定时调度任务

     搭建基于Quartz组件的定时调度任务 先在package包项目下,添加Quartz定时器组件: 新建类库项目Wsk.Core.QuartzNet,并且引用包类库项目.然后新建一个中间调度类,叫Qu ...

  6. docker入门详解

    这可能是最为详细的Docker总结 Docker是什么? 在计算机技术日新月异的今天, Docker 在国内发展的如火如荼,特别是在一线互联网公司, Docker 的使用是十分普遍的,甚至成为了一些企 ...

  7. Pandas高级教程之:处理缺失数据

    目录 简介 NaN的例子 整数类型的缺失值 Datetimes 类型的缺失值 None 和 np.nan 的转换 缺失值的计算 使用fillna填充NaN数据 使用dropna删除包含NA的数据 插值 ...

  8. 简单聊聊Ehcache缓存

    最近工作没有那么忙,有时间来写写东西.今年的系统分析师报名已经开始了,面对历年的真题,真的难以入笔,所以突然对未来充满了担忧,还是得抓紧时间学习技术. 同事推了一篇软文,看到了这个Ehcache,感觉 ...

  9. DDoS防护方式以及产品

    导航: 这里将一个案例事项按照流程进行了整合,这样查看起来比较清晰.部分资料来自于Cloudflare 1.DDoS介绍 2.常用DDoS攻击 3.DDoS防护方式以及产品 4.Cloudflare ...

  10. 11、函数(def)

    11.1.函数: 函数即变量 函数的作用域只跟函数声明时定义的作用域有关,跟函数的调用位置无任何关系 1.函数格式: def test(x): ''' 2*x+1 :param x:整形数字 :ret ...