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:

HTTPie AI
HTTPie AI

AI API开发工具

下载
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的引用机制和赋值操作的本质,开发者可以更有效地管理数据结构,编写出健壮且可预测的代码。

相关专题

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

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

715

2023.06.15

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

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

625

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教程的相关文章,大家可以免费体验学习。

1235

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相关的文章、下载、课程内容,供大家免费下载体验。

698

2023.08.11

小游戏4399大全
小游戏4399大全

4399小游戏免费秒玩大全来了!无需下载、即点即玩,涵盖动作、冒险、益智、射击、体育、双人等全品类热门小游戏。经典如《黄金矿工》《森林冰火人》《狂扁小朋友》一应俱全,每日更新最新H5游戏,支持电脑与手机跨端畅玩。访问4399小游戏中心,重温童年回忆,畅享轻松娱乐时光!官方入口安全绿色,无插件、无广告干扰,打开即玩,快乐秒达!

30

2025.12.31

热门下载

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

精品课程

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

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.0万人学习

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

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