Python的变量存储机制:浅拷贝与深拷贝详解

简介: 不可变数据类型在内存中存储的值仅存储一份,后续定义的变量如果值相等都指向用一个对象,为什么是这样呢?

这是机器未来的第8篇文章



1. 变量的存储机制

Python中的一切都是对象,变量是对象的引用!对象存于堆中,变量存于栈中。

1.1 什么是堆、栈?

堆栈都存在与内存中,在运行时分配的内存空间。对象存于堆中,变量存于栈中, 堆区存变量值, 栈区存变量名。栈区存放变量名和其变量值的内存地址, 通过这个内存地址, 变量名可以找到变量值。

1.2 直接引用和间接引用

1.2.1 直接引用:变量名直接关联变量值

直接引用常见于整数类型和字符串类型,修改它们的值,其实已经指向了其它的对象。直接引用的数据类型也被称为不可变数据类型, *不可变数据类型在内存中存储的值仅存储一份,后续定义的变量如果值相等都指向用一个对象,即 x1 is x2 and x1 == x2为True*.

变量名(变量值的地址)存于内存栈区,变量值存于堆区, 变量名直接关联变量值。

x = 10

y = 20

print(hex(id(x)), hex(id(y)))

0x7ff97c90f020 0x7ff97c90f160    # 从输出中可知,x,y指向两个不一样的对象

当执行x = y 时,你会发现x已经指向了一个新的对象,和原来的对象链路已经断开了。

x = 10

y = 20

print(hex(id(x)), hex(id(y)))

x = y

print(hex(id(x)), hex(id(y)))

0x7ff97c90f020 0x7ff97c90f1600x7ff97c90f160 0x7ff97c90f160    # 从输出中可知,x,y已经指向了同一个对象

注意:字符串的内容是不可以更改的,修改会直接报错!!!

name = "David"

name[2] = 'a'

TypeError                 Traceback (most recent call last)C:\Users\ZHOUSH~1\AppData\Local\Temp/ipykernel_9084/4053774855.py in <module>1 name = "David"

2 name[2] = 'a'

TypeError: 'str' object does not support item assignment

1.2.2 间接引用:变量名通过列表对象间接访问变量值

间接引用出现在容器类型里,如列表、元组、字典等。

定义列表变量l,其存储结构如图:变量l存储于栈区,变量l通过存储在堆区中列表对象内存地址访问列表对象,然后列表对象再通过其列表中存储的元素地址访问具体的变量值'a' 和 'b'。

l = ['a', 'b']

如果修改变量l[1]的值,例如l[1] = 'c',则列表的存储地址不会发生变化,但是列表对象第0个元素的地址会发生变化,其为字符'c'的内存地址,并指向字符对象'c',但是整个过程变量l的地址不会发生变化.

特别注意:间接引用变量既是值相等,这两个变量也不一定是同一个变量;而对于不可变数据类型,只要值相等,那么这两个变量一定是同一个变量.

x1 = [1, 2, 3, 4, 5, 3, 6, 7]

x2 = [1, 2, 3, 4, 5, 3, 6, 7]

x1 is x2

print(hex(id(x1)), hex(id(x2)))

0x2913b25ab08 0x2913cdf35c8

2. 浅拷贝与深拷贝

  • 直接赋值:其实就是对象的引用(别名)。
  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

2.1 直接赋值

其实就是对象的引用(别名)

l1 = [1, 'abc', [2, 3]]

l2 = l1

print(id(l1), id(l2))

2822815412936 2822815412936

l2直接指向l1引用的对象,l1和l2的内存地址是一样的, 存储结构如图.

2.2 浅拷贝

拷贝父对象,不会拷贝对象的内部的子对象。

import copy

l1 = [1, 'abc', [2, 3]]

l2 = copy.copy(l1)

print(id(l1), id(l2))

print('l1:', id(l1[0]), id(l1[1]), id(l1[2]))

print('l2:', id(l2[0]), id(l2[1]), id(l2[2]))

2822786373512 2822786371656       # l1和l2的内存地址不一样,是两个不一样的变量l1: 140709513457408 2822710032120 2822785142088 l2: 140709513457408 2822710032120 2822785142088 # l1和l2的元素的内存地址是一样的, 包括元素中的列表变量(是直接指向过去的), 验证了仅拷贝父对象的描述.

2.3 深拷贝

copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

import copy

l1 = [1, 'abc', [2, 3]]

l2 = copy.deepcopy(l1)

print(id(l1), id(l2))

print('l1:', id(l1[0]), id(l1[1]), id(l1[2]), id(l1[2][0]), id(l1[2][1]))

print('l2:', id(l2[0]), id(l2[1]), id(l2[2]), id(l2[2][0]), id(l2[2][1]))

2822786372360 2822815414472   # l1和l2的内存地址不一样,是不同的变量l1: 140709513457408 2822710032120 2822786363528 140709513457440 140709513457472l2: 140709513457408 2822710032120 2822786366024 140709513457440 140709513457472

l2对l1进行了深拷贝,直至数据类型为不可变类型为止.

从输出中可以看到:

  • l1和l2的内存地址不一样了,是不同的变量;
  • l1和l2的前2个成员变量的内存是一样的, 因为它们直接引用的不可变数据类型;
  • 第3个成员变量为一个列表,深拷贝时创建了一个新的列表变量,从输出可知l1[2]的内存地址为2822786363528, l2[2]的内存地址为2822786366024.
  • 但l1[2]和l[3]列表中的元素的内存地址又变为一样的了,因为它们都是不可变数据类型,指向同一个对象(注:不可变数据类型仅在内存中存储一份).
相关文章
|
4月前
|
存储 JSON API
Python与JSON:结构化数据的存储艺术
Python字典与JSON格式结合,为数据持久化提供了便捷方式。通过json模块,可轻松实现数据序列化与反序列化,支持跨平台数据交换。适用于配置管理、API通信等场景,兼具可读性与高效性,是Python开发中不可或缺的数据处理工具。
179 0
|
7月前
|
索引 Python
Python的变量和简单类型
本文介绍了Python中变量命名规则、常用变量类型及字符串操作。变量命名需遵循字母、数字和下划线组合,不能以数字开头且不可与关键字冲突。字符串支持单引号、双引号或三引号定义,涵盖基本输出、转义字符、索引、拼接等操作。此外,还详细解析了字符串方法如`islower()`、`upper()`、`count()`等,帮助理解字符串处理技巧。
203 15
|
7月前
|
人工智能 Python
[oeasy]python082_变量部分总结_variable_summary
本文介绍了变量的定义、声明、赋值及删除操作,以及Python中的命名规则和常见数据类型。通过示例讲解了字符串与整型的基本用法、类型转换方法和加法运算的区别。此外,还涉及异常处理(try-except)、模块导入(如math和random)及随机数生成等内容。最后总结了实验要点,包括捕获异常、进制转化、变量类型及其相互转换,并简述了编程中AI辅助的应用策略,强调明确目标、分步实施和逐步巩固的重要性。更多资源可在蓝桥、GitHub和Gitee获取。
273 97
|
5月前
|
测试技术 Python
Python测试报告生成:整合错误截图,重复用例执行策略,调整测试顺序及多断言机制。
如何组织这一切呢?你可以写一本名为“Python测试之道”的动作指南手册,或者创建一个包含测试策略、测试顺序、多断言机制的脚本库。只要你的测试剧本编写得足够独到,你的框架就会像一位执行任务的超级英雄,将任何潜伏于代码深处的错误无情地揪出来展现在光天化日之下。这些整理好的测试结果,不仅有利于团队协作,更像冒险故事中的精彩篇章,带给读者无尽的探索乐趣和深刻的思考。
147 10
|
6月前
|
数据采集 存储 NoSQL
Python爬虫Cookie管理最佳实践:存储、清理与轮换
Python爬虫Cookie管理最佳实践:存储、清理与轮换
|
7月前
|
存储 程序员 Python
Python 变量和简单数据类型
本文介绍了 Python 编程的基础知识,从创建第一个 Python 文件 `hello_world.py` 开始,讲解了 Python 文件的运行机制及解释器的作用。接着深入探讨了变量的定义、命名规则和使用方法,并通过示例说明如何修改变量值。同时,文章详细解析了字符串的操作,包括大小写转换、变量插入及空白字符处理等技巧。此外,还涵盖了数字运算(整数与浮点数)、常量定义以及注释的使用。最后引用了《Python 之禅》,强调代码设计的美学原则和哲学思想。适合初学者快速掌握 Python 基础语法和编程理念。
150 5
|
10月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
756 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
9月前
|
存储 Linux iOS开发
Python入门:2.注释与变量的全面解析
在学习Python编程的过程中,注释和变量是必须掌握的两个基础概念。注释帮助我们理解代码的意图,而变量则是用于存储和操作数据的核心工具。熟练掌握这两者,不仅能提高代码的可读性和维护性,还能为后续学习复杂编程概念打下坚实的基础。
Python入门:2.注释与变量的全面解析
|
11月前
|
Python
[oeasy]python050_如何删除变量_del_delete_variable
本文介绍了Python中如何删除变量,通过`del`关键字实现。首先回顾了变量的声明与赋值,说明变量在声明前是不存在的,通过声明赋予其生命和初始值。使用`locals()`函数可查看当前作用域内的所有本地变量。进一步探讨了变量的生命周期,包括自然死亡(程序结束时自动释放)和手动删除(使用`del`关键字)。最后指出,删除后的变量将无法在当前作用域中被访问,并提供了相关示例代码及图像辅助理解。
234 68
|
8月前
|
Python
[oeasy]python073_下划线在python里是什么含义_内部变量_私有变量_系统变量
本文回顾了Python中从模块导入变量和函数的方式,重点讨论了避免本地变量名冲突(local name clashes)的方法。通过`from module import variable as alias`可以为导入的变量重命名,防止冲突。根据PEP8规范,建议避免使用`from module import *`,因为它会导入模块中所有非下划线开头的变量,容易引发冲突。下划线在变量命名中有特殊含义:单个前导下划线表示内部变量,后置下划线用于避免与关键字冲突,双下划线前后包围表示系统变量。总结了下划线的不同用法及其作用。下次将继续探讨更实用的编程技巧。
167 3

推荐镜像

更多