0

0

Python列表乘法与引用:深度解析嵌套结构中的预期与实际行为

心靈之曲

心靈之曲

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

|

796人浏览过

|

来源于php中文网

原创

Python列表乘法与引用:深度解析嵌套结构中的预期与实际行为

本文深入探讨了Python中列表乘法(*运算符)在创建嵌套列表时涉及的引用机制。我们将通过示例代码和id()函数揭示,当使用*复制包含可变对象的列表时,实际上是创建了对同一对象的多个引用,而非独立副本。文章详细解释了这种“浅复制”行为如何影响后续的元素赋值操作,并提供了创建独立嵌套列表的正确方法,以避免常见的引用陷阱。

Python列表乘法与引用机制

python中,使用乘法运算符*来“乘以”列表是一种常见的操作,它可以快速创建一个包含重复元素的列表。然而,当列表中的元素是可变对象时,这种操作会引入一个重要的引用机制,即“浅复制”。

考虑以下代码片段,它尝试创建一个二维矩阵:

# 假设 A 是一个二维列表,例如 A = [[0,0], [0,0], [0,0]]
# len(A[0]) = 2, len(A) = 3

empty_row = [None] * len(A[0])  # 创建一个包含 len(A[0]) 个 None 的列表
empty_matrix = [ empty_row ] * len(A) # 将 empty_row 复制 len(A) 次

print("--- 初始状态下的对象ID ---")
for i in range(len(empty_matrix)):
    print(f"行对象ID: {id(empty_matrix[i])}")
    for j in range(len(empty_matrix[0])):
        print(f"     元素ID[{j}]: {id(empty_matrix[i][j])}", end = ", ")
    print()

运行这段代码,你会观察到类似以下的输出(ID值可能不同):

--- 初始状态下的对象ID ---
行对象ID: 2856577670848
     元素ID[0]: 140733388238040,      元素ID[1]: 140733388238040, 
行对象ID: 2856577670848
     元素ID[0]: 140733388238040,      元素ID[1]: 140733388238040, 
行对象ID: 2856577670848
     元素ID[0]: 140733388238040,      元素ID[1]: 140733388238040, 

从输出中可以清晰地看到:

  1. 所有行的对象ID都是相同的(2856577670848),这意味着empty_matrix中的所有行都引用了同一个列表对象empty_row。
  2. 所有元素的ID也是相同的(140733388238040),这表示empty_row中的所有元素都引用了同一个None对象。

这种行为是Python列表乘法操作的特性:它创建的是对元素的引用,而不是元素的独立副本。对于不可变对象(如数字、字符串、None),这通常不是问题,因为它们的值不能被修改。但对于可变对象(如列表、字典),这会导致意想不到的副作用。

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

理解赋值操作的影响

现在,我们尝试向这个empty_matrix赋值:

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()

你可能会预期得到一个像[[0, 1], [10, 11], [20, 21]]这样的矩阵。然而,实际输出却是:

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

这个结果表明,所有行都变成了[20, 21]。这正是因为所有行都引用了同一个empty_row列表对象。当执行empty_matrix[i][j] = i*10+j时,这是一个赋值操作,它做了以下事情:

  1. empty_matrix[i]首先解析为它所引用的那个唯一的empty_row列表对象。
  2. [j]访问这个empty_row列表的第j个位置。
  3. = i*10+j将一个新的整数对象(例如20或21)赋值给empty_row列表中第j个位置,使其现在引用这个新的整数对象,而不是之前的None。

由于empty_matrix[0]、empty_matrix[1]和empty_matrix[2]都指向同一个empty_row列表,对其中任何一个索引的修改都会体现在所有引用该列表的行上。最终,empty_row列表的元素被最后一次迭代(即i=2)中的赋值操作所覆盖,变成了[2*10+0, 2*10+1],也就是[20, 21]。

为了进一步验证,我们可以在赋值后再次检查对象ID:

GitHub Copilot
GitHub Copilot

GitHub AI编程工具,实时编程建议

下载
print("\n--- 赋值后对象ID的验证 ---")
for i in range(len(empty_matrix)):
    print(f"行对象ID: {id(empty_matrix[i])}")
    for j in range(len(empty_matrix[0])):
        print(f"     元素ID[{j}]: {id(empty_matrix[i][j])}", end = ", ")
    print()

输出会是:

--- 赋值后对象ID的验证 ---
行对象ID: 1782995372160
     元素ID[0]: 1782914902928,      元素ID[1]: 1782914902960,
行对象ID: 1782995372160
     元素ID[0]: 1782914902928,      元素ID[1]: 1782914902960,
行对象ID: 1782995372160
     元素ID[0]: 1782914902928,      元素ID[1]: 1782914902960,

可以看到,所有行的对象ID仍然相同,这再次确认了它们引用的是同一个列表对象。但现在,该列表中的元素ID已变为1782914902928(对应20)和1782914902960(对应21),它们是不同的整数对象。

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

要创建包含独立子列表的嵌套列表(即“深复制”效果),应确保每个子列表都是一个全新的对象。以下是两种常用的方法:

1. 使用列表推导式 (List Comprehension)

列表推导式是Python中创建列表的简洁且高效的方式。通过嵌套使用列表推导式,可以确保每个内部列表都是一个独立的新对象。

# 假设 rows = 3, cols = 2
rows = len(A)
cols = len(A[0])

# 创建一个包含独立子列表的矩阵
independent_matrix = [[None for _ in range(cols)] for _ in range(rows)]

print("\n--- 使用列表推导式创建的矩阵 ---")
for i in range(rows):
    print(f"行对象ID: {id(independent_matrix[i])}")
    for j in range(cols):
        print(f"     元素ID[{j}]: {id(independent_matrix[i][j])}", end = ", ")
    print()

# 赋值测试
for i in range(rows):
    for j in range(cols):
        independent_matrix[i][j] = i*10+j

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

输出将是:

--- 使用列表推导式创建的矩阵 ---
行对象ID: 1782995372224
     元素ID[0]: 140733388238040,      元素ID[1]: 140733388238040, 
行对象ID: 1782995372352
     元素ID[0]: 140733388238040,      元素ID[1]: 140733388238040, 
行对象ID: 1782995372480
     元素ID[0]: 140733388238040,      元素ID[1]: 140733388238040, 

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

可以看到,现在每行的对象ID都是不同的,并且赋值操作按预期工作,每行都保持了其独立的数值。

2. 使用循环和append

另一种方法是使用传统的for循环,在每次迭代中显式地创建一个新的子列表并添加到主列表中。

# 假设 rows = 3, cols = 2
rows = len(A)
cols = len(A[0])

# 创建一个包含独立子列表的矩阵
independent_matrix_loop = []
for _ in range(rows):
    independent_matrix_loop.append([None for _ in range(cols)])

print("\n--- 使用循环创建的矩阵 ---")
for i in range(rows):
    print(f"行对象ID: {id(independent_matrix_loop[i])}")
    for j in range(cols):
        print(f"     元素ID[{j}]: {id(independent_matrix_loop[i][j])}", end = ", ")
    print()

# 赋值测试
for i in range(rows):
    for j in range(cols):
        independent_matrix_loop[i][j] = i*10+j

print("\n--- 赋值后的独立矩阵内容 (循环创建) ---")
for r in independent_matrix_loop:
    for c in r:
        print(c, end = ", ")
    print()

这种方法也会产生与列表推导式相同的结果,因为每次append操作都添加了一个新创建的列表对象。

注意事项与总结

  • 理解引用与赋值: Python中的变量是对象的引用。a = b意味着a引用了b所引用的对象。对于列表元素my_list[index] = value,这表示my_list中index位置的引用现在指向了value所引用的对象。这与修改对象本身(如my_list.append(value)或my_list[index].method())是不同的。
  • 列表乘法(*)的“浅复制”:* 当使用`[mutable_object] N时,mutable_object只被创建一次,然后列表N`次引用这个同一个**对象。如果mutable_object是可变的(如另一个列表),修改其中一个引用会影响所有引用。
  • 创建独立嵌套结构: 始终使用列表推导式[[... for _ in range(cols)] for _ in range(rows)]或循环显式创建每个内部列表,以确保每个子列表都是一个独立的内存对象。
  • 避免常见陷阱: 在处理涉及可变对象的嵌套数据结构时,务必注意其初始化方式,以避免因共享引用而导致的意外行为。

通过深入理解Python的引用机制和赋值操作的本质,开发者可以更有效地管理数据结构,编写出健壮且可预测的代码。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

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

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

1498

2023.10.24

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

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

231

2024.02.23

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

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

87

2025.10.17

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

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

1498

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

623

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

612

2024.03.22

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

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

精品课程

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

共4课时 | 22.2万人学习

Django 教程
Django 教程

共28课时 | 3.5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

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

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