一行“优雅”代码踩爆3x3矩阵:Python列表乘法的“共享引用”陷阱

news/2025/11/3 20:45:34/文章来源:https://www.cnblogs.com/wangya216/p/19188281

一行“优雅”代码踩爆3x3矩阵:Python列表乘法的“共享引用”陷阱

很多Python新手初次创建多维列表时,都会被[[0] * 3] * 3这种写法吸引——一行代码搞定3x3矩阵,看起来简洁又聪明。可当你像这样修改一个元素时,却会遭遇“全体叛变”的诡异场景:

# 看似优雅的3x3矩阵创建
matrix = [[0] * 3] * 3
print("初始矩阵:", matrix)  # 输出:[[0, 0, 0], [0, 0, 0], [0, 0, 0]]# 只想修改第一个子列表的第一个元素
matrix[0][0] = 1
print("修改后矩阵:", matrix)  # 输出:[[1, 0, 0], [1, 0, 0], [1, 0, 0]]

看到结果的瞬间,大多数人都会发出和你一样的疑问:“我嘞个去!只改了一个元素,为什么三个子列表全变了?” 这背后藏着Python列表乘法的核心陷阱——可变对象的“共享引用”

一、先搞懂:[[0]*3]*3到底创建了什么?

要解开这个谜题,我们得拆解这行代码的执行逻辑,看看内存里到底生成了什么。列表乘法*的规则很简单:复制元素,生成新列表——但关键在于“复制的是值,还是引用”。

1. 第一步:[0] * 3——安全的不可变元素复制

先看内层的[0] * 3:这里的0不可变对象(整数),乘法会复制0的值,生成一个新列表[0, 0, 0]。此时三个0是独立的,修改其中一个不会影响其他:

lst = [0] * 3
lst[0] = 1
print(lst)  # 输出:[1, 0, 0](只有第一个元素变了,安全)

因为不可变对象无法被修改,复制时只能传递“值的副本”,所以这一步没问题。

2. 第二步:[[0]*3] * 3——危险的可变对象引用复制

真正的坑在第二层乘法:[[0]*3] * 3中,[0]*3生成的子列表[0,0,0]可变对象(列表)。此时乘法不会复制子列表的“内容”,只会复制子列表的“引用”(相当于指向子列表的“指针”)。

换句话说,[[0]*3] * 3生成的不是“三个独立的子列表”,而是“三个指向同一个子列表的引用”。内存结构像这样:

matrix -> [ 引用1 , 引用2 , 引用3 ]↓        ↓        ↓└───→ [0,0,0] ←──┘

三个引用都指向同一个子列表,就像三个人共用同一本笔记本——你在笔记本上改一个字,三个人看到的都会是修改后的内容。这就是为什么修改matrix[0][0],三个子列表的第一个元素都会变:你改的是“共享的子列表”,所有引用都会同步反映这个变化。

二、为什么会踩这个坑?——对“可变对象”的认知盲区

这个问题的本质,是新手容易混淆Python中的“可变对象”和“不可变对象”:

  • 不可变对象:整数、字符串、元组等,创建后内容不能修改,复制时传递“值副本”;
  • 可变对象:列表、字典、集合等,创建后内容可以修改,复制时传递“引用”。

很多人看到[0]*3安全,就想当然地认为[[0]*3]*3也安全,却忽略了“子列表是可变对象”这个关键差异。这种“表面相似,本质不同”的特性,正是Python中最容易踩的坑之一。

我们再用一个直观的例子验证“共享引用”:

# 创建矩阵
matrix = [[0]*3]*3
# 打印三个子列表的内存地址(id相同,证明是同一个对象)
print(id(matrix[0]))  # 输出:140688888888320
print(id(matrix[1]))  # 输出:140688888888320(和第一个子列表id相同)
print(id(matrix[2]))  # 输出:140688888888320(三个子列表完全是同一个对象)

id()函数返回对象的内存地址,三个子列表的id完全相同,实锤了它们是同一个对象的引用。

三、正确的多维列表创建方式:避免共享引用

既然*会导致共享引用,那该怎么创建真正独立的多维列表?核心思路是:每次都新建一个子列表,而不是复制引用。最常用的方法是“列表推导式”。

1. 推荐方案:双层列表推导式

用外层循环控制行数,内层循环控制列数,每次内层循环都新建一个子列表:

# 正确创建3x3矩阵:每次循环都新建子列表
matrix = [[0 for _ in range(3)] for _ in range(3)]
# 修改第一个子列表的第一个元素
matrix[0][0] = 1
print(matrix)  # 输出:[[1, 0, 0], [0, 0, 0], [0, 0, 0]](只改了一个元素,正确!)

为什么这个方法有效?因为内层的[0 for _ in range(3)]会在每次外层循环时“重新执行”,生成一个新的子列表——三个子列表的id完全不同,彼此独立:

print(id(matrix[0]))  # 输出:140688888889472
print(id(matrix[1]))  # 输出:140688888889600(不同)
print(id(matrix[2]))  # 输出:140688888889728(不同)

2. 其他方案:按需选择

如果需要创建更复杂的多维列表(比如4x4、5x5),或子列表有初始值,还可以用这些方法:

  • 手动创建:适合小规模矩阵,比如[[0,0,0], [0,0,0], [0,0,0]],直观但不灵活;
  • 使用numpy:如果处理数值矩阵,numpy.array更高效,且不会有引用问题:
    import numpy as np
    matrix = np.zeros((3, 3), dtype=int)  # 创建3x3全0矩阵
    matrix[0][0] = 1
    print(matrix)  # 输出:[[1 0 0], [0 0 0], [0 0 0]](正确)
    

四、拓展:还有哪些“共享引用”的坑?

多维列表的坑不是孤例,只要涉及“可变对象的复制”,都可能遇到类似问题。比如下面这两个常见场景:

1. 默认参数是可变对象

# 错误示例:默认参数是列表(可变对象)
def add_item(item, lst=[]):lst.append(item)return lst# 第一次调用:正常
print(add_item(1))  # 输出:[1]
# 第二次调用:意外!默认列表被共享了
print(add_item(2))  # 输出:[1, 2](不是预期的[2])

原因和矩阵坑一样:默认参数lst=[]只在函数定义时创建一次,后续调用共享同一个列表。正确写法是把默认参数设为None,再在函数内新建列表:

def add_item(item, lst=None):if lst is None:lst = []  # 每次调用都新建列表lst.append(item)return lst

2. 字典的值是可变对象

# 错误示例:字典值是列表,复制引用
d = {"a": [1,2], "b": d["a"]}  # "b"的值是"a"列表的引用
d["a"].append(3)
print(d["b"])  # 输出:[1,2,3]("b"的值也变了)

正确写法是用copy()创建列表副本:

d = {"a": [1,2], "b": d["a"].copy()}  # 复制列表内容,不是引用
d["a"].append(3)
print(d["b"])  # 输出:[1,2](正确,不受影响)

五、总结:避开引用陷阱的核心原则

回顾矩阵创建的坑,以及类似的默认参数、字典值问题,本质都是“没搞懂可变对象的引用机制”。记住这两个原则,就能少踩80%的引用坑:

  1. 创建多维列表/可变对象集合时,避免用*乘法*会复制引用,导致共享对象;优先用列表推导式,确保每次都新建独立对象。
  2. 遇到可变对象的“复制”需求,先想清楚是要“引用”还是“副本”
    • 要副本:用list.copy()(列表)、dict.copy()(字典)或切片lst[:]
    • 要引用:明确标注(比如写注释),避免后续修改时不知情。

Python的“优雅”写法很多,但有些看似简洁的代码,背后藏着认知盲区。就像[[0]*3]*3,一行代码的优雅,换来的可能是线上bug的头疼——理解底层原理,比追求表面简洁更重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/954882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

写给创业者新手:什么是MAU指标,什么是ARR、PMF

写给创业者新手:什么是MAU指标,什么是ARR、PMF大家好,我是jobleap.cn的小九。今天谈谈创业者必知必会的基础概念。# MAU MAU(Monthly Active Users)的定义是:**在一个自然月内至少登录或使用过一次产品的独立用户数…

git不小心把本地从未提交过的贮藏的版本删掉了,如何恢复?

事故发生背景:晚上加班不小心把未提交过的本地贮藏删掉了!!! 我是刚删完里面就反应过来,卧槽!!!误删本地贮藏了(因为我本地同时在改好几个需求,可能A需求改一半,还不能提交,所以就暂时贮藏在本地,然后就去…

ffmpeg安装配置

一、下载ffmpeg安装包打开Download FFmpeg 官网,选择 Windows builds for gyan.dev2.下滑选择 release builds 部分,选择 ffmpeg-7.1.1-essentials_build.zip3.下载完后,解压到本地二、配置本地环境变量开始菜单输入…

【C】 static用法

static用法 1. 在 C语言 中,static 关键字用于控制变量或函数的作用域和生命周期。当它修饰函数时,含义如下: static int add(int a, int b) {return a + b; } 上面的函数前加了 static,表示这是一个 静态函数(st…

Python线程锁

多线程用于IO、网络请求等地方,只要不是CPU密集型,都可以直接使用多线程。 线程锁在代码中发挥着关键的保护作用,让我详细解释它的工作原理: 锁的作用机制 1. 基本工作原理 with lock:if ensp_id in ensp_to_seq:r…

若依前端验证码的实现

由于之前没时间,现在重新写一份 好吵啊,游戏人的吼叫声,嗓门太大了 有钱了买个好的耳机 详细视频讲解:https://www.bilibili.com/video/BV1HT4y1d7oA?spm_id_from=333.788.player.switch&vd_source=886219f6fb…

从O(n)到O(n):Python字符串拼接的效率陷阱与最佳实践

从O(n)到O(n):Python字符串拼接的效率陷阱与最佳实践 在Python开发中,字符串拼接是最常见的操作之一。但看似简单的+号拼接,在循环场景下可能埋下严重的性能隐患。本文通过两段代码的对比,拆解字符串拼接的效率差异…

实验4:MobileNet ShuffleNet - OUC

实验4:MobileNet & ShuffleNet 姓名: 学号:姓名和学号?本实验属于哪门课程? 中国海洋大学25秋《软件工程原理与实践》实验名称? 实验4:MobileNet & ShuffleNet博客链接:学习要求CNN的基本结构:卷积、…

模拟赛 31

T1简单题,排序后直接枚举两个有画面格子之间的间隔数,即为可能节省的行数或列数,移动次数也易求。点击查看代码 #include<bits/stdc++.h> #define MAXN 500005 #define int long long const int inf=1e18; us…

CSP-S 2025 T3 小结

这个主要是写给自己看的。 就是观察到 b 性质是个扫描线。 考虑加强,会发现把 trie 树套上去就没了。 前面的思路不难想,主要是最后一步。 代码: #include<bits/stdc++.h> #include<bits/extc++.h> usi…

第三十二篇

今天是11月3号,进行期中考试了,难

2025年苏州AIGEO 优化服务商深度测评:TOP5 企业核心优势与实战案例对比

这份 GEO 优化服务商榜单与实践指南,既提供了可直接对标选择的优质服务商,也拆解了不同行业的定制化策略与落地路径。对企业而言,GEO 优化不再是单一的技术操作,而是 AI 时代品牌抢占流量入口、传递核心价值的关键…

使用 Docker Compose 轻松实现 INFINI Console 离线部署与持久化管理

本文是 INFINI Console 环境搭建系列的第四篇,专为需要在离线或内网环境中容器化部署 INFINI Console 与 Easysearch 的用户设计。系列回顾与引言 在我们的 INFINI 本地环境搭建系列博客中:第一篇《搭建持久化的 INF…

第6章 语句

6.5 if语句悬垂else (dangling-else) 问题:当一个if子句多于else子句时,对于每一个else,究竟属于哪个if。 C++中会将else匹配给最后出现的尚未匹配的if子句。

十一月杂题

十一月杂题1. CF1067D Computer Game 考虑有了一次升级机会之后一定只会对着期望收益最高的做。于是只需要决策升级之前的决策。设 \(f_t\) 为还剩 \(t\) 秒的最大收益,\(x\) 为期望收益最大的任务的期望收益,则有 \…

Modbus RTU 通信格式详解学习笔记

看似与人为善、心肠柔软之人,必然有一块坚硬如铁的心境土壤,在苦难人生中,死死支撑着那份看似愚蠢的善意1️⃣ 核心思想:Modbus RTU 报文 = 一封结构化的“电报” 想象一下,你要给朋友发一封电报:你得先告诉邮局…

Selenium3+Python3 自动化项目项目实战day1

HTML CSS JavaScript HTMl总体脉络 CSS皮肤组织 JavaScript二者神经组织 HTML 超文本标记语言 HTMl元素 HTML表单 CSS JaveScript

P1.python环境的配置和安装

P1.python环境的配置和安装1.1CUDA安装: 1.查看CUDA Version 在终端输入nvidia-smi2.下载安装配置CUDA1.2python3.8安装 C:\Users\ASUS\AppData\Local\Programs\Python\Python381.3Anaconda安装 1.软件下载安装测试一…

Python 中可变对象的“引用赋值”特性——可变对象的“引用传递”

一、踩坑代码 某程序老鸟讲了一个故事: “2019年夏天,我在做一个推荐系统的用户画像模块。当时写了这样的代码: # 当时的蠢代码,现在想起来都脸红 default_preferences = [] # 想着所有用户共享一个默认偏好 user…

CSP-S 2025 游寄喵

使用了新的文风喵……希望能缓解气氛喵……poi 酱写下这篇游记的时候大概心态很炸喵……不过还是尽量收住了我的垃圾情绪喵,如果不慎伤到您的话欢迎指出喵……poi 酱谢罪喵……(跪着) 如果您有帮到 poi 酱没有 ack …