Python对象引用与列表递归解析
本文深入解析Python对象引用与列表递归,助你掌握Python核心数据模型!理解Python中变量赋值的本质在于对象引用而非值传递,是理解其数据模型的基础。文章详细阐述了可变与不可变数据类型的差异,并通过代码示例剖析了列表别名现象。重点讲解列表相互引用时如何形成递归结构,以及这种结构对程序状态的潜在影响。掌握对象引用、可变性、以及列表递归等概念,能有效避免程序中出现难以追踪的Bug,编写出更健壮、可维护的Python代码。此外,文章还对比了`is`和`==`的区别,并提供了避免别名副作用的实用技巧,如浅拷贝和深拷贝,助力Python开发者写出高质量代码。
1. Python数据模型基础:可变性与不可变性
在Python中,所有数据都是对象。每个对象在内存中都有一个唯一的标识符(ID),可以通过内置的 id() 函数获取。理解对象的“可变性”(Mutable)和“不可变性”(Immutable)是理解Python中变量赋值和对象引用的关键。
- 不可变数据类型:一旦创建,其值就不能被修改。如果尝试“修改”一个不可变对象,Python实际上会创建一个新的对象,并让变量指向这个新对象。常见的不可变类型包括:数字(int, float, complex)、字符串(str)、元组(tuple)、冻结集合(frozenset)。
- 可变数据类型:创建后,其值可以在不改变内存地址的情况下被修改。常见的可变类型包括:列表(list)、字典(dict)、集合(set)。
让我们通过 id() 函数来验证这一点:
1.1 字符串(不可变类型)示例
# 初始化一个字符串并打印其ID some_str = "Hello" print("变量值:", some_str) print("变量ID:", id(some_str)) print("-" * 20) # 修改字符串并再次打印其ID some_str += " World" # 看起来是修改,实则创建新对象 print("变量值:", some_str) print("变量ID:", id(some_str)) print("-" * 20)
输出:
变量值: Hello 变量ID: 4457023280 -------------------- 变量值: Hello World 变量ID: 4458388464 --------------------
从输出可以看出,当字符串 some_str 被“修改”后,它的内存ID发生了变化,这证实了字符串的不可变性:原对象未被修改,而是创建了一个新对象。
1.2 列表(可变类型)示例
# 初始化一个列表并打印其ID some_list = ["Hello"] print("变量值:", some_list) print("变量ID:", id(some_list)) print("-" * 20) # 修改列表并再次打印其ID some_list.append("World") # 直接在原对象上修改 print("变量值:", some_list) print("变量ID:", id(some_list)) print("-" * 20)
输出:
变量值: ['Hello'] 变量ID: 4484419200 -------------------- 变量值: ['Hello', 'World'] 变量ID: 4484419200 --------------------
与字符串不同,列表 some_list 在被修改后,其内存ID保持不变。这证明了列表是可变数据类型,其内容可以在不改变对象本身内存地址的情况下进行修改。
2. Python中的对象引用与别名
在Python中,变量并不直接存储值,而是存储对内存中对象的引用(可以理解为指向对象的“指针”)。当一个变量被赋值给另一个变量,或者一个对象被添加到另一个数据结构中时,复制的不是对象的值,而是其引用。这意味着多个变量或数据结构中的元素可能指向同一个内存中的对象。这种现象被称为“别名”(Aliasing)。
2.1 引用传递的实验
# 初始化一个字符串和列表 some_str = "Hello" print("some_str 值:", some_str, "ID:", id(some_str)) print("-" * 10) some_list_1 = ["Hello"] print("some_list_1 值:", some_list_1, "ID:", id(some_list_1)) print("-" * 10) # 创建一个新列表,并追加上述两个变量 some_list_2 = [] some_list_2.append(some_str) # some_list_2[0] 存储 some_str 的引用 some_list_2.append(some_list_1) # some_list_2[1] 存储 some_list_1 的引用 print("some_list_2 的第一个元素:", some_list_2[0], "ID:", id(some_list_2[0])) print("ID of some_list_2[0] == ID of some_str?:", id(some_list_2[0]) == id(some_str)) print("*" * 10) print("some_list_2 的第二个元素:", some_list_2[1], "ID:", id(some_list_2[1])) print("ID of some_list_2[1] == ID of some_list_1?:", id(some_list_2[1]) == id(some_list_1)) print("*" * 10) # 修改原始的 some_str 和 some_list_1 some_str += " World" # some_str 指向新对象 print("修改后 some_str ID:", id(some_str)) some_list_1.append("World") # some_list_1 在原地址修改 print("修改后 some_list_1 ID:", id(some_list_1)) print("-" * 20) # 再次检查 some_list_2 中的元素 print("some_list_2 的第一个元素:", some_list_2[0], "ID:", id(some_list_2[0])) print("ID of some_list_2[0] == ID of some_str?:", id(some_list_2[0]) == id(some_str)) # False,因为some_str现在指向新对象 print("*" * 10) print("some_list_2 的第二个元素:", some_list_2[1], "ID:", id(some_list_2[1])) print("ID of some_list_2[1] == ID of some_list_1?:", id(some_list_2[1]) == id(some_list_1)) # True,因为some_list_1在原地址修改 print("*" * 10)
输出:
some_str 值: Hello ID: 4321089264 ---------- some_list_1 值: ['Hello'] ID: 4322442880 ---------- some_list_2 的第一个元素: Hello ID: 4321089264 ID of some_list_2[0] == ID of some_str?: True ********** some_list_2 的第二个元素: ['Hello'] ID: 4322442880 ID of some_list_2[1] == ID of some_list_1?: True ********** 修改后 some_str ID: 4322509360 修改后 some_list_1 ID: 4322442880 -------------------- some_list_2 的第一个元素: Hello ID: 4321089264 ID of some_list_2[0] == ID of some_str?: False ********** some_list_2 的第二个元素: ['Hello', 'World'] ID: 4322442880 ID of some_list_2[1] == ID of some_list_1?: True **********
这个实验清晰地展示了:
- 当不可变对象(字符串)被“修改”时,原始变量 some_str 指向了一个新的内存地址,但 some_list_2[0] 仍然指向最初的那个“Hello”字符串对象。
- 当可变对象(列表)被修改时,some_list_1 在其原有内存地址上被修改,由于 some_list_2[1] 存储的是 some_list_1 的引用,因此 some_list_2[1] 的内容也随之改变。
3. 列表的递归行为与相互引用解析
现在,我们将上述概念应用于一个更复杂的场景:列表相互引用,从而形成递归结构。
考虑以下代码片段:
a = [1,2,3] b = [4,5] a.append(b) # 1. 将列表 b 的引用追加到列表 a 中 print(a) # 此时 a 是 [1, 2, 3, [4, 5]]。a[3] 指向 b 所指向的同一个列表对象。 print(a[3][1]) # 访问 a[3] (即 b) 的第二个元素 b.append(a) # 2. 将列表 a 的引用追加到列表 b 中 print(b) # 此时 b 是 [4, 5, [1, 2, 3, [...]]]。b[2] 指向 a 所指向的同一个列表对象。 # 并且由于 a 已经包含了 b 的引用,b 现在又包含了 a 的引用,形成了循环引用。 print(b[2][1]) # 访问 b[2] (即 a) 的第二个元素 a[3][1] = 6 # 3. 修改 a[3] (即 b) 的第二个元素 print(a) print(b) # 由于 a[3] 和 b 指向同一个对象,修改 a[3][1] 等同于修改 b[1]。 print(a[3][2] is a) # 4. 检查 a[3][2] 是否就是对象 a print(b[2][3][2] == a) # 5. 检查 b[2][3][2] 的值是否等于 a 的值
输出:
[1, 2, 3, [4, 5]] 5 [4, 5, [1, 2, 3, [...]]] 2 [1, 2, 3, [4, 6, [...]]] [4, 6, [1, 2, 3, [...]]] True True
3.1 行为逐行解析
- a = [1,2,3] 和 b = [4,5]:在内存中创建两个独立的列表对象,a 和 b 分别指向它们。
- a.append(b):Python 不会将 b 的内容复制到 a 中,而是将 b 所指向的列表对象的引用添加到 a 的末尾。现在,a[3] 和 b 都指向内存中同一个 [4,5] 列表对象。
- print(a[3][1]):a[3] 就是 b,所以 a[3][1] 实际上是 b[1],其值为 5。
- b.append(a):类似地,Python 将 a 所指向的列表对象的引用添加到 b 的末尾。现在,b[2] 和 a 都指向内存中同一个 [1,2,3, [4,5]] 列表对象。
- 关键点:此时,a 包含了 b 的引用,而 b 又包含了 a 的引用,形成了一个循环引用(或称递归结构)。当打印这些列表时,Python 的 repr() 函数会检测到这种循环,并用 [...] 来表示递归引用,以避免无限打印。
- print(b[2][1]):b[2] 就是 a,所以 b[2][1] 实际上是 a[1],其值为 2。
- a[3][1] = 6:
- a[3] 指向的是列表 b。
- 因此,a[3][1] = 6 等同于 b[1] = 6。
- 由于列表是可变的,b 的第二个元素被修改为 6。
- 因为 a[3] 和 b 指向同一个对象,所以修改 a[3][1] 会立即反映在 b 中,反之亦然。
- 此时 a 变为 [1, 2, 3, [4, 6, [...]]],b 变为 [4, 6, [1, 2, 3, [...]]]。
- print(a[3][2] is a):
- a[3] 是 b。
- a[3][2] 是 b[2]。
- b[2] 是 a。
- 所以 a[3][2] 就是 a。is 运算符检查两个变量是否指向内存中的同一个对象,因此结果为 True。
- print(b[2][3][2] == a):
- b[2] 是 a。
- b[2][3] 是 a[3],即 b。
- b[2][3][2] 是 a[3][2],即 b[2],也就是 a。
- 所以 b[2][3][2] 指向的对象就是 a。== 运算符检查两个对象的值是否相等,由于它们是同一个对象,其值必然相等,因此结果为 True。
4. 总结与注意事项
- 理解引用而非值:Python 中的变量存储的是对象的引用,而不是对象本身的值。这意味着当你操作一个变量时,你实际上是在操作它所引用的对象。
- 可变性与副作用:可变对象的共享引用可能导致“副作用”。当多个变量或数据结构中的元素引用同一个可变对象时,通过任何一个引用对该对象的修改都会影响所有其他引用。
- 递归结构:列表等可变容器类型可以包含对自身的引用,或者通过中间对象形成循环引用,从而创建递归数据结构。Python 在打印这些结构时会用 [...] 来表示,以避免无限循环。
- is 与 == 的区别:
- is 运算符检查两个变量是否引用内存中的同一个对象(即它们的 id() 是否相同)。
- == 运算符检查两个对象的值是否相等。
- 对于可变对象,理解 is 尤为重要,因为它直接反映了别名现象。
掌握这些核心概念对于编写健壮、可预测的Python代码至关重要,尤其是在处理复杂数据结构时。如果需要避免别名引起的意外修改,可以考虑使用浅拷贝(list.copy() 或 copy.copy())或深拷贝(copy.deepcopy())来创建对象的独立副本。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。

- 上一篇
- Java数组组合凑出目标金额技巧

- 下一篇
- JS节流函数怎么用?throttle原理与应用
-
- 文章 · python教程 | 3分钟前 |
- 按字典值划分Pandas列的技巧
- 426浏览 收藏
-
- 文章 · python教程 | 3分钟前 |
- PythonPyQt计算器开发教程实战
- 432浏览 收藏
-
- 文章 · python教程 | 5分钟前 |
- Python大数据处理:Dask并行计算全解析
- 157浏览 收藏
-
- 文章 · python教程 | 15分钟前 |
- Python项目打包发布指南
- 338浏览 收藏
-
- 文章 · python教程 | 18分钟前 |
- Pandas组内最小值排序技巧分享
- 474浏览 收藏
-
- 文章 · python教程 | 22分钟前 |
- Python中ans是什么意思及使用建议
- 328浏览 收藏
-
- 文章 · python教程 | 23分钟前 |
- PythonOpenCV图像识别入门教程
- 109浏览 收藏
-
- 文章 · python教程 | 53分钟前 |
- Python爬虫教程:requests+BeautifulSoup实战指南
- 330浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- PythonLabelEncoder使用详解
- 370浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python语音识别教程:SpeechRecognition库使用详解
- 210浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 千音漫语
- 千音漫语,北京熠声科技倾力打造的智能声音创作助手,提供AI配音、音视频翻译、语音识别、声音克隆等强大功能,助力有声书制作、视频创作、教育培训等领域,官网:https://qianyin123.com
- 100次使用
-
- MiniWork
- MiniWork是一款智能高效的AI工具平台,专为提升工作与学习效率而设计。整合文本处理、图像生成、营销策划及运营管理等多元AI工具,提供精准智能解决方案,让复杂工作简单高效。
- 94次使用
-
- NoCode
- NoCode (nocode.cn)是领先的无代码开发平台,通过拖放、AI对话等简单操作,助您快速创建各类应用、网站与管理系统。无需编程知识,轻松实现个人生活、商业经营、企业管理多场景需求,大幅降低开发门槛,高效低成本。
- 112次使用
-
- 达医智影
- 达医智影,阿里巴巴达摩院医疗AI创新力作。全球率先利用平扫CT实现“一扫多筛”,仅一次CT扫描即可高效识别多种癌症、急症及慢病,为疾病早期发现提供智能、精准的AI影像早筛解决方案。
- 104次使用
-
- 智慧芽Eureka
- 智慧芽Eureka,专为技术创新打造的AI Agent平台。深度理解专利、研发、生物医药、材料、科创等复杂场景,通过专家级AI Agent精准执行任务,智能化工作流解放70%生产力,让您专注核心创新。
- 105次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览