0

0

Python对象引用、可变性与列表递归行为深度解析

聖光之護

聖光之護

发布时间:2025-07-14 23:32:01

|

939人浏览过

|

来源于php中文网

原创

Python对象引用、可变性与列表递归行为深度解析

本文深入探讨Python中变量赋值、列表操作背后的内存机制,重点阐述可变与不可变数据类型的概念,以及对象引用(指针)的工作原理。通过实际代码示例,详细解析列表别名(aliasing)现象,特别是当列表相互引用时如何形成递归结构,并解释了这种行为对程序状态的影响,帮助读者理解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)

输出:

STORYD
STORYD

帮你写出让领导满意的精美文稿

下载

立即学习Python免费学习笔记(深入)”;

变量值: 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)

输出:

立即学习Python免费学习笔记(深入)”;

变量值: ['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)

输出:

立即学习Python免费学习笔记(深入)”;

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 的值

输出:

立即学习Python免费学习笔记(深入)”;

[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 行为逐行解析

  1. a = [1,2,3] 和 b = [4,5]:在内存中创建两个独立的列表对象,a 和 b 分别指向它们。
  2. 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。
  3. 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。
  4. 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, [...]]]。
  5. print(a[3][2] is a):
    • a[3] 是 b。
    • a[3][2] 是 b[2]。
    • b[2] 是 a。
    • 所以 a[3][2] 就是 a。is 运算符检查两个变量是否指向内存中的同一个对象,因此结果为 True。
  6. 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())来创建对象的独立副本。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

716

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

626

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

739

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

617

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1236

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

575

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

699

2023.08.11

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号