Python嵌套列表的引用陷阱与正确初始化方法

一只小鹿
一只小鹿
发布时间:2026-10-14 | 5人浏览过
原创

本文深入解析Python中嵌套列表的常见陷阱,特别是使用[[0]*3]*5初始化二维列表时导致的引用共享问题。通过区分对象与引用、理解栈与堆内存机制,揭示为何修改一个元素会影响所有行。文章提供了正确的列表推导式和逐行初始化方案,帮助开发者避免此类尴尬错误,写出更健壮的Python代码。

嵌套列表基础

1. 嵌套列表

Python内置了列表(list)这一数据类型,它作为一种容器可以承载其他对象(确切地说是其他对象的引用)。列表中的元素可以是任意类型的对象,包括另一个列表,这种结构被称为嵌套列表。

嵌套列表在实际应用中非常有用,常用于模拟现实世界中的表格、矩阵,或是2D游戏地图(例如《植物大战僵尸》的花园布局)以及棋盘游戏(如国际象棋、黑白棋)的数据结构。

嵌套列表的常见陷阱

2. 识别坑点

使用嵌套列表时需谨慎,否则容易遇到令人尴尬的bug。以下是一个典型示例。


def main():  names = ['关羽', '张飞', '赵云', '马超', '黄忠']  subjs = ['语文', '数学', '英语']  scores = [[0] * 3] * 5  for row, name in enumerate(names):    print('请输入%s的成绩' % name)    for col, subj in enumerate(subjs):      scores[row][col] = float(input(subj + ': '))  print(scores)if __name__ == '__main__':  main()
  names = ['关羽', '张飞', '赵云', '马超', '黄忠']
  subjs = ['语文', '数学', '英语']
  scores = [[0] * 3] * 5
  for row, name in enumerate(names):
    print('请输入%s的成绩' % name)
    for col, subj in enumerate(subjs):
      scores[row][col] = float(input(subj + ': '))
  print(scores)
if __name__ == '__main__':
  main()

假设我们需要记录5名学生3门课程的成绩,可以定义一个包含5个元素的列表,每个元素又是一个包含3个元素的子列表,这正好对应一个5行3列的表格结构。

随后,我们通过嵌套的for-in循环录入每位学生的3门课程成绩。然而程序运行后发现,所有学生的成绩竟然完全相同,且都等于最后录入的那位学生的成绩。

对象与引用的内存机制

3. 区分两个概念

要解决这个坑,首先必须区分“对象”和“对象的引用”这两个概念。而理解这一区别,需要先从内存中的栈(stack)和堆(heap)说起。

人们常混用“堆栈”一词,但实际上“堆”和“栈”是两个截然不同的概念。程序运行时占用内存空间存储数据和代码,从逻辑上可将这些内存划分为不同区域。

了解底层语言(如C语言)的程序员知道,程序可用的内存从逻辑上可分为五个部分,按地址从高到低依次为:栈(stack)、堆(heap)、数据段(data segment)、只读数据段(static area)和代码段(code segment)。

栈用于存储局部变量、临时变量以及函数调用时保存和恢复现场所需的数据。这部分内存在代码块执行开始时自动分配,在代码块结束时自动释放,通常由编译器自动管理。

堆的大小不固定,支持动态分配和回收。若程序需处理大量数据,通常将其存放在堆上。如果堆空间未被正确释放,可能引发内存泄露。Python、Java等语言采用垃圾回收机制自动管理堆空间,回收不再使用的对象。

引用共享示例分析

4. 小例子

因此,在下面的代码中,变量a并非对象本身,而是对象的引用,它记录了对象在堆空间中的地址,通过该地址即可访问对应对象。

a = object() b = ['apple', 'pitaya', 'grape'] b = ['apple', 'pitaya', 'grape']

同理,变量b是列表容器的引用,指向堆空间中的列表容器。列表容器内部并未保存真正的对象,而是保存了这些对象的引用。

基于此,回顾之前的程序,当我们对列表执行[[0]* 3] * 5操作时,仅仅是复制了[0, 0, 0]这个列表对象的地址,并未创建新的列表对象。

因此,虽然容器中有5个元素,但这5个元素都引用了同一个列表对象。这一点可以通过id函数检查scores[0]和scores[1]的地址得到证实。读者可以运行以下小例子加深印象。

a = [[0]*3]*5 id(a[0]) id(a[1]) # id相等 id(a[1]) # id相等

正确的初始化方法

5. 正确代码

因此,正确的代码应按照如下方式修改。


def main():  names = ['关羽', '张飞', '赵云', '马超', '黄忠']  subjs = ['语文', '数学', '英语']  scores = [[]] * 5  for row, name in enumerate(names):    print('请输入%s的成绩' % name)    scores[row] = [0] * 3 #变为不再嵌套    for col, subj in enumerate(subjs):      scores[row][col] = float(input(subj + ': '))  print(scores)if __name__ == '__main__':  main()'关羽', '张飞', '赵云', '马超', '黄忠']
  subjs = ['语文', '数学', '英语']
  scores = [[]] * 5
  for row, name in enumerate(names):
    print('请输入%s的成绩' % name)
    scores[row] = [0] * 3 #变为不再嵌套
    for col, subj in enumerate(subjs):
      scores[row][col] = float(input(subj + ': '))
  print(scores)
if __name__ == '__main__':
  main()

或者采用另一种方式。


def main():  names = ['关羽', '张飞', '赵云', '马超', '黄忠']  subjs = ['语文', '数学', '英语']  scores = [[0] * 3 for _ in range(5)]  for row, name in enumerate(names):    print('请输入%s的成绩' % name)    scores[row] = [0] * 3    for col, subj in enumerate(subjs):      scores[row][col] = float(input(subj + ': '))  print(scores)if __name__ == '__main__':  main()
  names = ['关羽', '张飞', '赵云', '马超', '黄忠']
  subjs = ['语文', '数学', '英语']
  scores = [[0] * 3 for _ in range(5)]
  for row, name in enumerate(names):
    print('请输入%s的成绩' % name)
    scores[row] = [0] * 3
    for col, subj in enumerate(subjs):
      scores[row][col] = float(input(subj + ': '))
  print(scores)
if __name__ == '__main__':
  main()

以上介绍了使用嵌套列表时需要注意的问题及解决措施,希望大家多多总结,从而避免在使用嵌套列表或复制对象时遇到类似的坑。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表