当前位置: 首页 > news >正文

【Python】列表的创建:[[] for _ in range(2)] 与 [[]] * 2有什么区别?

[[] for _ in range(2)] 与 [[]] * 2有什么区别

目录

  • 写在前面
    • 一、`[[]] * 2 `与`[[] for _ in range(2)] `的区别与联系
      • **1. 示例代码**
      • **2. 核心区别**
      • **3. 验证实验**
        • **(1) 初始状态**
        • **(2) 修改子列表**
      • **4. 内存结构图解**
        • **(1) `mean_values_1 = [[]] * 2`**
        • **(2) `mean_values_2 = [[] for _ in range(2)]`**
      • **5. 实际场景分析**
        • **错误示例:使用乘法运算符**
        • **正确示例:使用列表推导式**
      • **6. 总结**
    • 二、 Python 列表(List)的创建方式总结
    • Python 中列表(`list`)的创建方式灵活多样,以下是常见方法及其区别:
      • **1. 直接使用方括号 `[]` 创建**
      • **2. 使用 `list()` 构造函数**
      • **`3. 列表推导式(List Comprehension)`**
      • **`4. 使用 `*` 运算符重复元素`**
      • **5. 通过循环动态构建**
      • **6. 使用生成器表达式**
      • **7. 解包操作(Python 3.5+)**
      • **8. 使用 `copy` 模块(深拷贝)**
      • **总结:各方法对比**
      • **注意事项**
    • 三、 Python 列表创建过程中的深复制与浅复制总结
      • **1. 浅复制(Shallow Copy)**
        • **(1) 使用 `*` 运算符**
        • **(2) 列表推导式(引用现有对象)**
        • **(3) `list()` 构造函数或切片**
        • **(4) `copy.copy()`**
      • **2. 深复制(Deep Copy)**
        • **(1) `copy.deepcopy()`**
      • **3. 无复制的场景**
        • **(1) 直接赋值**
        • **(2) 列表推导式(新建对象)**
      • **4. 总结对比**
      • **5. 常见陷阱与解决方案**
        • **陷阱1:通过 `*` 初始化嵌套列表**
        • **陷阱2:浅复制后意外修改嵌套对象**
      • **6. 最佳实践**

写在前面

小编最近在写代码的时候遇到了一个问题,具体展示如下:

import numpy as npif __name__ == '__main__':mean_value = [[]] * 2data1 = np.random.randn(3, 4)data2 = np.random.randn(3, 4)data3 = [data1, data2]for i, data in enumerate(data3):value = np.mean(data, axis=1, keepdims=True)mean_value[i].append(value)mean_value = np.array(mean_value)print(mean_value.shape)print(len(mean_value[0]))

输出结果如下:

(2, 2, 3, 1)
2Process finished with exit code 0

Why?????

我想要的效果是mean_value存储两个数据的平均值,理论上来说shape应该是(2,1,3,1),但是这里为什么是(2,2,3,1)??? 很不理解,查看代码逻辑没有问题,觉得应该是列表的创建出现了问题,于是小编换了一种列表的创建方式:
mean_value = [[] for _ in range(2)]
输出:(2, 1, 3, 1)
问题成功解决了!那么这两种创建列表的方式有什么不同呢?于是小编做了如下总结!

一、[[]] * 2 [[] for _ in range(2)] 的区别与联系

在 Python 中,mean_values = [[]] * 2mean_values = [[] for _ in range(2)] 都可以创建一个包含两个空列表的列表,但它们在 内存结构 和行为上有本质区别。


1. 示例代码

# 方法1:使用乘法运算符
mean_values_1 = [[]] * 2# 方法2:使用列表推导式
mean_values_2 = [[] for _ in range(2)]

2. 核心区别

特性mean_values_1 = [[]] * 2mean_values_2 = [[] for _ in range(2)]
内存分配两个子列表是同一个对象的引用每个子列表是独立的对象
修改子列表修改一个子列表会影响另一个修改一个子列表不影响另一个
适用场景需要共享子列表的引用(极少使用)需要独立子列表(绝大多数场景)

3. 验证实验

(1) 初始状态
print("初始状态:")
print("mean_values_1:", mean_values_1)  # [[], []]
print("mean_values_2:", mean_values_2)  # [[], []]

两个列表在初始状态下看起来相同。


(2) 修改子列表
# 向 mean_values_1 的第一个子列表添加元素
mean_values_1[0].append(1)# 向 mean_values_2 的第一个子列表添加元素
mean_values_2[0].append(1)print("修改后:")
print("mean_values_1:", mean_values_1)  # [[1], [1]]
print("mean_values_2:", mean_values_2)  # [[1], []]
  • 关键区别
    • mean_values_1 的两个子列表是同一个对象的引用,修改其中一个会影响另一个。
    • mean_values_2 的两个子列表是独立的,修改互不影响。

4. 内存结构图解

(1) mean_values_1 = [[]] * 2
内存地址:
mean_values_1 --> [子列表A, 子列表A]
子列表A --> []
  • 两个子列表指向同一内存地址。

(2) mean_values_2 = [[] for _ in range(2)]
内存地址:
mean_values_2 --> [子列表B, 子列表C]
子列表B --> []
子列表C --> []
  • 每个子列表是独立的对象。

5. 实际场景分析

错误示例:使用乘法运算符
# 初始化一个 2x2 的零矩阵(错误做法)
matrix = [[0] * 2] * 2
matrix[0][0] = 1  # 修改一个元素会影响整列
print(matrix)      # [[1, 0], [1, 0]]
  • 问题:所有子列表是同一对象的引用,导致修改整列数据。

正确示例:使用列表推导式
# 初始化一个 2x2 的零矩阵(正确做法)
matrix = [[0 for _ in range(2)] for _ in range(2)]
matrix[0][0] = 1
print(matrix)      # [[1, 0], [0, 0]]
  • 解决:每个子列表独立,修改互不影响。

6. 总结

维度[[]] * 2[[] for _ in range(2)]
内存效率高(共享子列表)低(独立子列表)
数据安全性低(可能引发意外修改)高(子列表独立)
典型用途需要共享引用的特殊场景99% 的实际场景(如多维数组初始化)

使用列表推导式: ([[] for _ in range(n)]) 创建包含可变对象的嵌套列表,避免因共享引用导致的数据污染。
* 运算符重复元素: 其实就是对某个元素/列表的复制,本质上来说还是同一个元素/列表,属于欧浅复制,所以是共享内存的。

二、 Python 列表(List)的创建方式总结

Python 中列表(list)的创建方式灵活多样,以下是常见方法及其区别:

1. 直接使用方括号 [] 创建

a = [1, 2, 3]              # 显式定义元素
b = []                     # 创建空列表
  • 特点
    • 最直观的方式,适合已知元素的情况。
    • 支持混合数据类型(如 [1, "a", True])。
  • 适用场景:静态初始化、简单列表。

2. 使用 list() 构造函数

a = list()                 # 空列表
b = list((1, 2, 3))        # 从元组转换
c = list("hello")          # 从字符串转换 → ['h', 'e', 'l', 'l', 'o']
d = list(range(5))         # 从 range 对象转换 → [0, 1, 2, 3, 4]
  • 特点
    • 可将任何可迭代对象(字符串、元组、生成器等)转换为列表。
    • 若参数是字典,默认提取键(list(dict) → 键的列表)。
  • 适用场景:动态转换其他可迭代对象为列表。

3. 列表推导式(List Comprehension)

a = [x for x in range(5)]            # [0, 1, 2, 3, 4]
b = [x**2 for x in range(5) if x%2==0]  # 带条件筛选 → [0, 4, 16]
  • 特点
    • 语法简洁,支持条件筛选和复杂表达式。
    • 性能通常优于显式循环(底层优化)。
  • 适用场景:动态生成复杂列表(如过滤、映射操作)。

4. 使用 * 运算符重复元素

a = [0] * 5                # [0, 0, 0, 0, 0]
b = [[0]] * 3              # [[0], [0], [0]](注意元素引用问题)
  • 特点
    • 快速生成重复元素的列表。
    • 注意:若元素是可变对象(如子列表),所有元素指向同一内存地址。
    • 示例陷阱:
      c = [[0]] * 3
      c[0][0] = 1            # [[1], [1], [1]](所有子列表被修改)
      
  • 适用场景:初始化不可变元素的重复列表(如数值、字符串)。

5. 通过循环动态构建

a = []
for i in range(5):a.append(i)            # [0, 1, 2, 3, 4]
  • 特点
    • 灵活性强,适合动态添加元素的场景。
    • 性能略低于列表推导式(每次循环调用 append() 方法)。
  • 适用场景:元素生成逻辑复杂,无法用推导式简化时。

6. 使用生成器表达式

a = list(x**2 for x in range(5))    # [0, 1, 4, 9, 16]
  • 特点
    • 生成器表达式(())惰性求值,节省内存。
    • 通过 list() 转换为列表。
  • 适用场景:处理大数据量时减少内存占用。

7. 解包操作(Python 3.5+)

a = [*range(5)]            # [0, 1, 2, 3, 4]
b = [*"hello", 10]         # ['h', 'e', 'l', 'l', 'o', 10]
  • 特点
    • 使用 * 解包可迭代对象并合并到列表中。
    • 类似 list(),但更灵活(可与其他元素混合)。
  • 适用场景:合并多个可迭代对象或添加额外元素。

8. 使用 copy 模块(深拷贝)

import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)       # 深拷贝,避免引用问题
  • 特点
    • 深拷贝(deepcopy)完全复制嵌套结构,与原列表独立。
    • 浅拷贝(a.copy()a[:])仅复制外层,嵌套对象仍共享引用。
  • 适用场景:需要完全独立的嵌套列表。

总结:各方法对比

方法语法简洁性性能内存效率适用场景
直接方括号 []★★★★★静态初始化已知元素
list() 构造函数★★★★☆转换其他可迭代对象为列表
列表推导式★★★★★动态生成复杂列表
* 运算符重复★★★★☆初始化不可变元素的重复列表
循环动态构建★★★☆☆复杂逻辑的动态生成
生成器表达式 + list()★★★★☆大数据量的惰性转换
解包操作★★★★☆合并可迭代对象或混合元素
深拷贝★★☆☆☆嵌套结构的完全独立复制

注意事项

  1. 可变对象的引用问题
    使用 * 运算符或浅拷贝时,若元素是可变对象(如子列表),修改一个元素会影响其他引用。
  2. 性能优化
    列表推导式通常比显式循环快,优先用于简单逻辑。
  3. 内存管理
    处理大数据时,生成器表达式(惰性求值)比列表推导式更节省内存。

三、 Python 列表创建过程中的深复制与浅复制总结

在 Python 中,列表的创建和复制行为分为 浅复制(Shallow Copy)深复制(Deep Copy)。它们的核心区别在于对嵌套对象(如子列表、字典等)的处理方式。以下是不同列表创建方式的分类及详细说明:


1. 浅复制(Shallow Copy)

浅复制仅复制外层对象的引用,不递归复制嵌套对象。如果原对象包含可变元素(如子列表),修改这些元素会影响所有引用它们的对象。

(1) 使用 * 运算符
a = [[1, 2]] * 3  # 三个子列表指向同一个对象
a[0].append(3)
print(a)  # [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
  • 特点:所有子列表共享同一内存地址。
  • 陷阱:修改任意子列表会影响所有其他子列表。
(2) 列表推导式(引用现有对象)
original = [[1, 2]]
b = [original[0] for _ in range(3)]  # 三个子列表引用同一个对象
b[0].append(3)
print(b)  # [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
  • 特点:显式引用现有对象时,仍属于浅复制。
(3) list() 构造函数或切片
original = [[1, 2]]
c = list(original)    # 浅复制外层列表
d = original[:]       # 浅复制(等同于切片)
c[0].append(3)
print(original)       # [[1, 2, 3]]
  • 特点:外层列表是新的,但嵌套对象仍共享引用。
(4) copy.copy()
import copy
original = [[1, 2]]
e = copy.copy(original)
e[0].append(3)
print(original)  # [[1, 2, 3]]
  • 特点:与切片和 list() 行为一致,仅浅复制。

2. 深复制(Deep Copy)

深复制会递归复制所有嵌套对象,完全独立于原对象。修改深复制后的对象不会影响原对象。

(1) copy.deepcopy()
import copy
original = [[1, 2]]
f = copy.deepcopy(original)
f[0].append(3)
print(original)  # [[1, 2]](原对象不受影响)
  • 特点:完全独立的副本,适用于多层嵌套结构。

3. 无复制的场景

(1) 直接赋值
a = [1, 2]
b = a  # 无复制,b 和 a 指向同一对象
b.append(3)
print(a)  # [1, 2, 3]
  • 特点:变量名是引用,不涉及任何复制操作。
(2) 列表推导式(新建对象)
c = [[] for _ in range(3)]  # 每个子列表是独立对象
c[0].append(1)
print(c)  # [[1], [], []]
  • 特点:通过新建对象避免共享引用,但若元素本身是复杂对象且未深拷贝,仍可能浅复制。

4. 总结对比

方法复制类型嵌套对象独立性适用场景
* 运算符浅复制❌(共享引用)初始化不可变元素的重复列表
列表推导式(引用现有对象)浅复制❌(共享引用)显式引用现有对象时
list() 或切片浅复制❌(共享引用)需要外层列表独立但嵌套共享时
copy.copy()浅复制❌(共享引用)与切片行为一致
copy.deepcopy()深复制✅(完全独立)需要完全独立的嵌套结构
列表推导式(新建对象)无复制✅(独立对象)动态创建独立子列表(非复制场景)

5. 常见陷阱与解决方案

陷阱1:通过 * 初始化嵌套列表
matrix = [[0] * 3] * 3  # 所有行共享同一列表
matrix[0][0] = 1        # 所有行的第一列被修改
  • 解决方案:使用列表推导式创建独立子列表:
    matrix = [[0 for _ in range(3)] for _ in range(3)]
    
陷阱2:浅复制后意外修改嵌套对象
original = [[1, 2]]
shallow_copy = original.copy()
shallow_copy[0].append(3)  # 原对象被修改
  • 解决方案:深复制嵌套对象:
    import copy
    deep_copy = copy.deepcopy(original)
    

6. 最佳实践

  1. 默认使用列表推导式
    当需要独立子列表时,优先使用 [[] for _ in range(n)],避免共享引用。
  2. 明确深复制需求
    对多层嵌套结构(如列表的列表、字典的列表等),使用 copy.deepcopy()
  3. 警惕隐式共享引用
    使用 * 运算符或浅复制时,确保元素是不可变对象(如整数、字符串)。

相关文章:

  • STM32F407实现内部FLASH的读写功能
  • 【MySQL】MySQL数据库 —— 简单认识
  • 第3篇:深入 Framer Motion Variants:掌握组件动画编排的艺术
  • django项目之添加资产信息功能
  • YOLOv3模型架构与原理详解
  • Python 冷门魔术方法
  • Redis 高可用集群搭建与优化实践
  • 波束形成(BF)从算法仿真到工程源码实现-第十节-非线性波束形成
  • vue中使用swiper坑记录
  • 二、The Power of LLM Function Calling
  • 京东商品详情API接口调用讲解(实战案例)
  • Day56 | 99. 恢复二叉搜索树、103. 二叉树的锯齿形层序遍历、109. 有序链表转换二叉搜索树、113. 路径总和 II
  • 使用openpyxl时的一些注意点
  • 1.2 腾讯校招通关指南-面试官评分标准:技术岗/产品岗核心考核点揭秘
  • 【自相关】全局 Moran’s I 指数
  • OPPO Android 移动设备日志文件目录结构及其内容分析
  • git合并分支原理
  • 适合stm32 前端adc使用的放大器芯片
  • jetson orin nano 开发板conda 的 base 环境在 shell 启动时自动激活
  • 如何解决服务器文件丢失或损坏的问题?
  • 成都一医院孕妇产下七胞胎?涉事医院辟谣:信息不实已举报
  • 道客网络陈齐彦:技术无界化,开源让AI变成了“全民食堂”
  • 工信部:计划出台机械、汽车、电力装备等三个行业新一轮稳增长工作方案
  • 西北政法大学推无手机课堂,有学生称要求全交,学校:并非强制
  • 昆明盘龙区一火灾调查报告公布:老人火盆取暖引燃房屋致身亡
  • “努力稳住外贸基本盘”,浙江省委书记、省长接连调研外贸