0

0

Python列表乘法与引用机制深度解析

心靈之曲

心靈之曲

发布时间:2025-10-03 12:21:01

|

915人浏览过

|

来源于php中文网

原创

python列表乘法与引用机制深度解析

本文深入探讨了Python中列表乘法(*运算符)在创建嵌套列表时的引用行为,特别是当内部列表为可变对象时。通过具体代码示例,揭示了列表乘法产生的浅拷贝现象,即所有内部列表引用的是同一个对象。文章详细解释了对共享内部列表元素的赋值操作如何改变其内容,而非创建独立的副本,并提供了正确创建独立嵌套列表的方法,以避免常见的引用陷阱。

Python列表乘法 (*) 的工作原理

在Python中,使用乘法运算符 * 来“乘以”一个列表,会创建一个新列表,其中包含对原列表元素的重复引用。理解这一点对于处理可变对象(如列表自身)至关重要。

考虑以下两种情况:

  1. 复制不可变对象列表:

    >>> a = [0] * 3
    >>> a
    [0, 0, 0]
    >>> id(a[0]), id(a[1]), id(a[2])
    (140733388238040, 140733388238040, 140733388238040) # 示例ID,实际值可能不同

    这里,列表 a 的所有元素都引用同一个整数对象 0。由于整数是不可变的,这通常不会引起问题。当你尝试修改 a[0] = 1 时,实际上是将 a[0] 指向了一个新的整数对象 1,而其他元素仍然指向 0。

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

  2. 复制可变对象列表(浅拷贝):

    >>> b = [[]] * 3
    >>> b
    [[], [], []]
    >>> id(b[0]), id(b[1]), id(b[2])
    (2856577670848, 2856577670848, 2856577670848) # 示例ID,实际值可能不同

    在这种情况下,列表 b 的所有元素都引用同一个空列表对象 []。这意味着 b[0], b[1], b[2] 实际上是同一个列表的三个不同名称。如果修改其中一个:

    >>> b[0].append(1)
    >>> b
    [[1], [1], [1]]

    你会发现所有嵌套列表都受到了影响,因为它们指向的是同一个底层列表对象。这就是所谓的“浅拷贝”:只复制了顶层列表的引用,而没有复制其内部对象。

嵌套列表中的引用陷阱

当创建嵌套列表时,如果不注意 * 运算符的浅拷贝特性,很容易遇到意料之外的行为。让我们以一个具体的矩阵初始化为例:

假设我们有一个用于确定维度的辅助列表 A,例如 A = [[0,0],[0,0],[0,0]],这意味着我们需要一个3行2列的矩阵。

# 假设 A 的维度为 3x2
# len(A) = 3, len(A[0]) = 2

# 步骤1: 创建一个包含 None 的行
empty_row = [None] * len(A[0])
# 此时 empty_row = [None, None]
# 并且 empty_row[0] 和 empty_row[1] 都指向同一个 None 对象

# 步骤2: 使用 empty_row 来创建矩阵
empty_matrix = [ empty_row ] * len(A)
# 此时 empty_matrix = [[None, None], [None, None], [None, None]]
# 关键在于 empty_matrix[0]、empty_matrix[1]、empty_matrix[2] 都指向了同一个 empty_row 列表对象

为了验证这一点,我们可以打印它们的内存地址(ID):

import sys

# 假设 A = [[0,0],[0,0],[0,0]]
A = [[0,0],[0,0],[0,0]]

empty_row = [None] * len(A[0])
empty_matrix = [ empty_row ] * len(A)

print("--- 初始化时的ID ---")
for i in range(len(empty_matrix)):
    print(f"Row ID: {id(empty_matrix[i])}")
    for j in range(len(empty_matrix[0])):
        print(f"     Element ID: {id(empty_matrix[i][j])}", end = ", ")
    print()

示例输出(ID值可能不同):

--- 初始化时的ID ---
Row ID: 2856577670848
     Element ID: 140733388238040,      Element ID: 140733388238040, 
Row ID: 2856577670848
     Element ID: 140733388238040,      Element ID: 140733388238040, 
Row ID: 2856577670848
     Element ID: 140733388238040,      Element ID: 140733388238040, 

从输出可以看出,所有行的ID都是相同的(2856577670848),这证明 empty_matrix 中的所有行都指向了同一个 empty_row 对象。同时,所有元素的ID也都是相同的(140733388238040),因为它们都指向同一个 None 对象。

赋值操作与引用断裂

现在,让我们对这个 empty_matrix 进行赋值操作:

Sesame AI
Sesame AI

一款开创性的语音AI伴侣,具备先进的自然对话能力和独特个性。

下载
print("\n--- 赋值操作 ---")
for i in range(len(A)):
    for j in range(len(A[0])):
        empty_matrix[i][j] = i*10+j

print("\n--- 赋值后的矩阵内容 ---")
for r in empty_matrix:
    for c in r:
        print(c, end = ", ")
    print()

示例输出:

--- 赋值操作 ---

--- 赋值后的矩阵内容 ---
20, 21, 
20, 21, 
20, 21, 

这个输出可能与初学者的预期不符。许多人可能期望得到一个像 0, 1, \n 10, 11, \n 20, 21, 这样的矩阵。然而,实际输出显示所有行都是 20, 21。

这是因为 empty_matrix[i][j] = i*10+j 语句执行的是赋值操作,而不是对原始 None 对象的原地修改。当执行 empty_matrix[i][j] = value 时:

  1. Python首先找到 empty_matrix[i] 所引用的列表对象(即那个唯一的 empty_row)。
  2. 然后,在这个 empty_row 列表的索引 j 处,将它指向一个新的整数对象 value。

由于 empty_matrix 中的所有行都指向同一个 empty_row 对象,对 empty_matrix[i][j] 的任何赋值操作,实际上都是在修改这个唯一的共享 empty_row。因此,当循环结束后,empty_row 的内容将是循环中最后一次对 empty_row 元素进行的赋值结果,即 i=2, j=0 时的 20 和 i=2, j=1 时的 21。

为了进一步验证,我们可以在赋值后再次打印ID:

print("\n--- 赋值后的ID ---")
for i in range(len(empty_matrix)):
    print(f"Row ID: {id(empty_matrix[i])}") # 行ID保持不变
    for j in range(len(empty_matrix[0])):
        print(f"     Element ID: {id(empty_matrix[i][j])}", end = ", ") # 元素ID已改变
    print()

示例输出(ID值可能不同):

--- 赋值后的ID ---
Row ID: 2856577670848
     Element ID: 1782914902928,      Element ID: 1782914902960, 
Row ID: 2856577670848
     Element ID: 1782914902928,      Element ID: 1782914902960, 
Row ID: 2856577670848
     Element ID: 1782914902928,      Element ID: 1782914902960, 

可以看到,所有行的ID仍然是相同的(2856577670848),这再次确认了 empty_matrix 中的所有行依然指向同一个列表对象。然而,元素ID已经改变(1782914902928 和 1782914902960),这表明在 empty_row 中,原来的 None 对象已经被新的整数对象 20 和 21 替换了。

正确创建独立嵌套列表的方法

为了避免这种浅拷贝带来的引用问题,尤其是在需要独立操作每个嵌套列表时,应该使用列表推导式来创建独立的内部列表:

# 假设 A = [[0,0],[0,0],[0,0]]
A = [[0,0],[0,0],[0,0]]

# 使用列表推导式创建独立的嵌套列表
# 外层循环创建 len(A) 个独立的行列表
# 内层循环为每个行列表创建 len(A[0]) 个独立的 None 元素
correct_matrix = [[None for _ in range(len(A[0]))] for _ in range(len(A))]

print("\n--- 正确创建的矩阵的ID ---")
for i in range(len(correct_matrix)):
    print(f"Row ID: {id(correct_matrix[i])}")
    for j in range(len(correct_matrix[0])):
        print(f"     Element ID: {id(correct_matrix[i][j])}", end = ", ")
    print()

示例输出(ID值可能不同):

--- 正确创建的矩阵的ID ---
Row ID: 2856577670848
     Element ID: 140733388238040,      Element ID: 140733388238040, 
Row ID: 2856577670928
     Element ID: 140733388238040,      Element ID: 140733388238040, 
Row ID: 2856577671008
     Element ID: 140733388238040,      Element ID: 140733388238040, 

现在,correct_matrix 中的每一行都有一个独立的ID,这意味着它们是不同的列表对象。对其中一行的修改不会影响其他行。

print("\n--- 对正确创建的矩阵进行赋值操作 ---")
for i in range(len(A)):
    for j in range(len(A[0])):
        correct_matrix[i][j] = i*10+j

print("\n--- 赋值后的正确矩阵内容 ---")
for r in correct_matrix:
    for c in r:
        print(c, end = ", ")
    print()

示例输出:

--- 对正确创建的矩阵进行赋值操作 ---

--- 赋值后的正确矩阵内容 ---
0, 1, 
10, 11, 
20, 21, 

这正是我们期望的结果。

总结与注意事项

  • *列表乘法 (``) 创建的是浅拷贝。** 当用于复制包含可变对象的列表时,所有副本都会引用同一个内部可变对象。
  • 赋值操作 (=) 会改变引用。 当你对列表的某个索引进行赋值时 (my_list[index] = new_value),你是在让该索引指向一个新的对象,而不是修改原有对象的内容。
  • 列表方法(如 append()、extend())会原地修改对象。 如果多个变量引用同一个列表,通过这些方法修改其中一个变量,所有引用都会看到这个变化。
  • 创建独立的嵌套列表应使用列表推导式。 例如 [[value for _ in range(cols)] for _ in range(rows)]。
  • 对于更复杂的嵌套结构,如果需要完全独立的副本(包括所有嵌套层次),可以使用 copy 模块中的 copy.deepcopy() 函数。

理解Python中变量、对象和引用之间的关系是编写健壮代码的关键。特别是在处理可变数据结构时,对浅拷贝和深拷贝的认识能够有效避免许多潜在的逻辑错误。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1503

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

233

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

87

2025.10.17

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

31

2026.01.06

append用法
append用法

append是一个常用的命令行工具,用于将一个文件的内容追加到另一个文件的末尾。想了解更多append用法相关内容,可以阅读本专题下面的文章。

344

2023.10.25

python中append的用法
python中append的用法

在Python中,append()是列表对象的一个方法,用于向列表末尾添加一个元素。想了解更多append的更多内容,可以阅读本专题下面的文章。

1074

2023.11.14

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

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

精品课程

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

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.7万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

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

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