01-列表
本章代码基于 Python 3.11+ 编写
列表是 Python 最常用的数据结构,用于存储有序的可变元素集合。
本章全面讲解 Python 列表的创建、访问、修改、复制和高级用法。
概念铺垫
为什么需要列表?一个真实的批量数据场景
问题场景: 你在开发一个学生成绩管理系统,需要记录多个学生的成绩。
不使用列表的麻烦:
python
# 每个成绩一个变量
score1 = 85
score2 = 92
score3 = 78
score4 = 96
score5 = 88
# 计算平均分?需要手动写所有变量
average = (score1 + score2 + score3 + score4 + score5) / 5问题:
- 如果有100个学生,需要100个变量
- 如何批量处理?很难!
使用列表的解决方案:
python
# 用一个列表存储所有成绩
scores: list[int] = [85, 92, 78, 96, 88]
# 一行代码计算平均分
average: float = sum(scores) / len(scores)
print(f"平均分:{average}")这就是列表的价值:用一个容器存储多个相关数据。
列表解决了什么问题?
列表的本质是:用一个名字,管理多个数据。
就像你用一个"购物清单"记录多个物品,而不是每样东西写一张纸条。
列表的优势:
- 批量管理:不用为每个数据创建单独的变量
- 动态调整:随时添加、删除、修改元素
- 统一处理:可以用循环处理所有元素
- 有序访问:通过索引快速找到任何元素
在Python中,列表是最常用的数据结构,几乎每个程序都会用到。
L1 理解层:会用
列表的最简用法(3分钟上手)
列表的核心操作:创建、访问、修改。
python
# 创建列表
fruits: list[str] = ["苹果", "香蕉", "橘子"]
# 访问元素
print(fruits[0]) # 苹果
# 修改元素
fruits[0] = "西瓜"
print(fruits) # ["西瓜", "香蕉", "橘子"]
# 添加元素
fruits.append("葡萄")
print(fruits) # ["西瓜", "香蕉", "橘子", "葡萄"]这就是列表的基本用法。接下来我们详细学习列表的所有功能。
第一部分:列表基础
什么是列表
列表(List) 是 Python 中最常用的数据结构,用于存储有序的元素集合。
┌─────────────────────────────────────────────────────────────┐
│ 列表的特点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 有序:元素有固定的顺序,可通过索引访问 │
│ ✅ 可变:可以修改、添加、删除元素 │
│ ✅ 可重复:允许有相同的元素 │
│ ✅ 异构:可以存储不同类型的元素 │
│ ✅ 动态:大小可变,自动调整内存 │
│ │
│ 表示方法:使用方括号 [] │
│ 例如:[1, 2, 3, 4, 5] │
│ │
│ 底层实现:动态数组 │
│ • 连续内存存储 │
│ • O(1) 索引访问 │
│ • O(n) 插入/删除(中间位置) │
│ │
└─────────────────────────────────────────────────────────────┘创建列表
python
# 方式 1:使用方括号
fruits = ["apple", "banana", "orange"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None] # 异构列表
# 方式 2:创建空列表
empty_list = []
empty_list2 = list()
# 方式 3:从其他可迭代对象创建
from_string = list("hello") # ['h', 'e', 'l', 'l', 'o']
from_range = list(range(5)) # [0, 1, 2, 3, 4]
from_tuple = list((1, 2, 3)) # [1, 2, 3]
# 方式 4:列表推导式
squares = [i ** 2 for i in range(1, 6)] # [1, 4, 9, 16, 25]
# 方式 5:重复创建
zeros = [0] * 5 # [0, 0, 0, 0, 0]
matrix = [[0] * 3 for _ in range(3)] # 3x3 矩阵索引访问
python
fruits = ["apple", "banana", "orange", "grape", "mango"]
# 正向索引(从 0 开始)
print(fruits[0]) # apple(第一个)
print(fruits[1]) # banana
print(fruits[4]) # mango(最后一个)
# 反向索引(从 -1 开始)
print(fruits[-1]) # mango(最后一个)
print(fruits[-2]) # grape(倒数第二个)
print(fruits[-5]) # apple(第一个)
# 索引越界
# fruits[5] # IndexError: list index out of range
# fruits[-6] # IndexError: list index out of range索引示意图:
┌─────────────────────────────────────────────────────────────┐
│ 列表索引示意图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 列表:["apple", "banana", "orange", "grape", "mango"] │
│ │
│ 正向索引: │
│ 0 1 2 3 4 │
│ ┌────────┬────────┬────────┬────────┬────────┐ │
│ │ apple │ banana │ orange │ grape │ mango │ │
│ └────────┴────────┴────────┴────────┴────────┘ │
│ │
│ 反向索引: │
│ -5 -4 -3 -2 -1 │
│ │
└─────────────────────────────────────────────────────────────┘列表切片
切片: 从列表中提取一部分元素,返回新列表。
语法结构:
┌─────────────────────────────────────────────────────────────┐
│ 切片语法 │
│ │
│ list[start:end:step] │
│ ↑ ↑ ↑ │
│ 起始 结束 步长 │
│ │
│ ⚠️ 注意: │
│ • end 不包含(切片到 end-1) │
│ • start/end 默认:从头到尾 │
│ • step 默认:1 │
│ │
│ 示例:numbers[2:5] │
│ 从索引2到索引4(不包含5) │
│ 结果:[2, 3, 4] │
└─────────────────────────────────────────────────────────────┘最简示例
python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 基本切片
print(numbers[2:5]) # [2, 3, 4]关键代码解释
| 切片 | 含义 | 结果 |
|---|---|---|
numbers[2:5] | 从索引2到4 | [2, 3, 4] |
numbers[:5] | 从头到索引4 | [0, 1, 2, 3, 4] |
numbers[5:] | 从索引5到尾 | [5, 6, 7, 8, 9] |
numbers[:] | 完整复制 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] |
详细示例
python
# 步长切片
print(numbers[::2]) # [0, 2, 4, 6, 8](每隔一个)
print(numbers[1::2]) # [1, 3, 5, 7, 9](奇数位置)
print(numbers[::3]) # [0, 3, 6, 9](每隔两个)
# 切片赋值
numbers[2:5] = [20, 30, 40]
print(numbers) # [0, 1, 20, 30, 40, 5, 6, 7, 8, 9]反向切片(核心)
反向切片: step 为负数时,从右向左切片。
反向切片规则:
┌─────────────────────────────────────────────────────────────┐
│ 反向切片:step < 0 │
│ │
│ list[start:end:-1] │
│ ↑ ↑ ↑ │
│ 起始 结束 从右向左 │
│ │
│ ⚠️ 注意: │
│ • start 必须大于 end │
│ • 从 start 向 end 方向切片 │
│ • end 不包含 │
│ │
│ 示例:numbers[5:2:-1] │
│ 从索引5向索引3切片(不包含2) │
│ 结果:[5, 4, 3] │
└─────────────────────────────────────────────────────────────┘最简示例
python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 反转列表
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]关键代码解释
| 切片 | 含义 | 结果 |
|---|---|---|
numbers[::-1] | step=-1,反转 | [9, 8, ..., 0] |
numbers[5:2:-1] | 从5向3切片 | [5, 4, 3] |
numbers[-1:-5:-1] | 从尾向倒数第5 | [9, 8, 7, 6] |
详细示例
python
# 反向切片
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0](反转)
print(numbers[5:2:-1]) # [5, 4, 3](反向取)
print(numbers[-1:-5:-1])# [9, 8, 7, 6]第二部分:列表方法
添加元素
添加方法: 在列表中插入新元素。
添加方法对比:
┌─────────────────────────────────────────────────────────────┐
│ 添加元素方法 │
│ │
│ append(x) 在末尾添加单个元素 │
│ insert(i, x) 在索引 i 处插入元素 │
│ extend(iter) 在末尾添加多个元素 │
│ + 运算符 合并列表(创建新列表) │
│ │
│ ⚠️ append vs extend 区别: │
│ append([4,5]) → 添加整个列表作为元素 │
│ extend([4,5]) → 添加列表中的每个元素 │
└─────────────────────────────────────────────────────────────┘最简示例
python
fruits = ["apple", "banana"]
fruits.append("orange")
print(fruits) # ['apple', 'banana', 'orange']关键代码解释
| 方法 | 作用 | 示例 |
|---|---|---|
append(x) | 末尾添加单个 | fruits.append("orange") |
insert(i, x) | 指定位置插入 | fruits.insert(1, "apricot") |
extend(iter) | 末尾添加多个 | fruits.extend(["mango", "peach"]) |
详细示例
python
fruits = ["apple", "banana"]
# insert() - 在指定位置插入
fruits.insert(1, "apricot") # 在索引 1 处插入
print(fruits) # ['apple', 'apricot', 'banana', 'orange']
# extend() - 在末尾添加多个元素
fruits.extend(["mango", "peach"])
print(fruits) # ['apple', 'apricot', 'banana', 'orange', 'mango', 'peach']
# + 运算符 - 合并列表(创建新列表)
new_fruits = fruits + ["grape"]
print(new_fruits) # 包含 grapeappend vs extend(核心)
区别: append 添加整个对象,extend 添加每个元素。
append vs extend:
┌─────────────────────────────────────────────────────────────┐
│ append vs extend 区别 │
│ │
│ append([4, 5]) │
│ ┌──────────────────────────────────────┐ │
│ │ 原列表: [1, 2, 3] │ │
│ │ 结果: [1, 2, 3, [4, 5]] │ ← 整个列表作为元素│
│ └──────────────────────────────────────┘ │
│ │
│ extend([4, 5]) │
│ ┌──────────────────────────────────────┐ │
│ │ 原列表: [1, 2, 3] │ │
│ │ 结果: [1, 2, 3, 4, 5] │ ← 添加每个元素 │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘最简示例
python
list1 = [1, 2, 3]
list1.append([4, 5])
print(list1) # [1, 2, 3, [4, 5]](添加整个列表)
list2 = [1, 2, 3]
list2.extend([4, 5])
print(list2) # [1, 2, 3, 4, 5](添加每个元素)关键代码解释
| 方法 | 效果 | 结果 |
|---|---|---|
append([4,5]) | 添加整个列表 | [1, 2, 3, [4, 5]] |
extend([4,5]) | 添加每个元素 | [1, 2, 3, 4, 5] |
删除元素
删除方法: 从列表中移除元素。
删除方法对比:
┌─────────────────────────────────────────────────────────────┐
│ 删除元素方法 │
│ │
│ 方法 按什么删除 返回值 不存在时 │
│ ───────────────────────────────────────────── │
│ remove() 值 无 ValueError │
│ pop() 索引 元素 IndexError │
│ del 索引/切片 无 IndexError │
│ clear() 全部 无 - │
│ │
│ 选择建议: │
│ • 知道值 → remove() │
│ • 知道位置 → pop() │
│ • 删除范围 → del │
│ • 清空列表 → clear() │
└─────────────────────────────────────────────────────────────┘最简示例
python
fruits = ["apple", "banana", "orange"]
# 按值删除
fruits.remove("banana")
print(fruits) # ['apple', 'orange']关键代码解释
| 方法 | 删除方式 | 返回值 |
|---|---|---|
remove("x") | 按值删除 | 无 |
pop(i) | 按索引删除 | 返回被删元素 |
del list[i] | 按索引删除 | 无 |
详细示例
python
fruits = ["apple", "banana", "orange", "grape", "mango"]
# pop() - 按索引删除并返回元素
last = fruits.pop() # 删除最后一个,返回 'mango'
first = fruits.pop(0) # 删除第一个,返回 'apple'
print(fruits) # ['banana', 'orange', 'grape']
# del 语句 - 按索引删除(不返回)
del fruits[0] # 删除第一个
del fruits[1:3] # 删除切片范围
# clear() - 清空列表
fruits.clear()
print(fruits) # []查找和统计
python
fruits = ["apple", "banana", "orange", "banana", "grape"]
# index() - 查找元素索引
print(fruits.index("banana")) # 1(第一个匹配)
print(fruits.index("banana", 2)) # 3(从索引 2 开始查找)
# count() - 统计元素出现次数
print(fruits.count("banana")) # 2
print(fruits.count("mango")) # 0
# len() - 获取长度
print(len(fruits)) # 5
# in 运算符 - 成员检查
print("apple" in fruits) # True
print("mango" in fruits) # False
# 查找所有匹配索引
indices = [i for i, x in enumerate(fruits) if x == "banana"]
print(indices) # [1, 3]排序和反转
python
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sort() - 原地排序(修改原列表)
numbers.sort() # 升序
print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9]
numbers.sort(reverse=True) # 降序
print(numbers) # [9, 6, 5, 4, 3, 2, 1, 1]
# sorted() - 返回新列表(不修改原列表)
original = [3, 1, 4, 1, 5]
sorted_copy = sorted(original)
print(original) # [3, 1, 4, 1, 5](不变)
print(sorted_copy) # [1, 1, 3, 4, 5]
# reverse() - 原地反转
numbers.reverse()
print(numbers)
# reversed() - 返回迭代器
reversed_iter = reversed(numbers)
reversed_list = list(reversed(numbers))
# 按条件排序
words = ["banana", "pie", "apple", "cherry"]
words.sort(key=len) # 按长度排序
words.sort(key=str.lower) # 按小写排序
words.sort(key=lambda x: x[-1]) # 按最后一个字母排序
# 多条件排序
students = [("Alice", 85), ("Bob", 90), ("Charlie", 85)]
students.sort(key=lambda x: (-x[1], x[0])) # 先按分数降序,再按名字升序其他方法
python
# copy() - 浅拷贝
original = [1, 2, 3]
copy_list = original.copy()
# 列表比较
list1 = [1, 2, 3]
list2 = [1, 2, 3]
print(list1 == list2) # True(内容相同)
print(list1 is list2) # False(不同对象)
# 列表连接和重复
list1 = [1, 2]
list2 = [3, 4]
print(list1 + list2) # [1, 2, 3, 4]
print(list1 * 3) # [1, 2, 1, 2, 1, 2]
# 最大值、最小值、求和
numbers = [1, 2, 3, 4, 5]
print(max(numbers)) # 5
print(min(numbers)) # 1
print(sum(numbers)) # 15
print(sum(numbers, 10)) # 25(初始值 10)第三部分:列表复制
浅拷贝 vs 深拷贝
概念说明
列表复制是 Python 中容易混淆的概念,理解浅拷贝和深拷贝的区别很重要。
┌─────────────────────────────────────────────────────────────┐
│ 浅拷贝 vs 深拷贝 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 浅拷贝(Shallow Copy): │
│ • 创建新列表对象 │
│ • 复制元素的引用(不复制元素本身) │
│ • 嵌套对象仍然是共享的 │
│ │
│ 深拷贝(Deep Copy): │
│ • 创建新列表对象 │
│ • 递归复制所有嵌套对象 │
│ • 完全独立,不共享任何对象 │
│ │
│ 方法对比: │
│ ───────────────────────────────────────────── │
│ 赋值 (=) 不拷贝,只是引用 │
│ copy() 浅拷贝 │
│ list[:] 浅拷贝 │
│ deepcopy() 深拷贝 │
│ │
└─────────────────────────────────────────────────────────────┘代码示例:
python
import copy
# 赋值(不拷贝)
original = [1, 2, 3]
reference = original
reference[0] = 100
print(original) # [100, 2, 3](原列表也被修改)
print(reference) # [100, 2, 3]
# 浅拷贝
original = [1, 2, 3]
shallow = original.copy() # 或 original[:] 或 list(original)
shallow[0] = 100
print(original) # [1, 2, 3](原列表不变)
print(shallow) # [100, 2, 3]
# 浅拷贝的问题:嵌套列表
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0][0] = 100
print(original) # [[100, 2], [3, 4]](嵌套列表被修改!)
print(shallow) # [[100, 2], [3, 4]]
# 深拷贝:解决嵌套问题
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0][0] = 100
print(original) # [[1, 2], [3, 4]](原列表不变)
print(deep) # [[100, 2], [3, 4]]图示:
┌─────────────────────────────────────────────────────────────┐
│ 拷贝示意图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 赋值(引用): │
│ original ──────┐ │
│ ───→ [1, 2, 3] │
│ reference ─────┘ │
│ (两个变量指向同一个对象) │
│ │
│ 浅拷贝: │
│ original ────────→ [1, 2, 3] │
│ shallow ────────→ [1, 2, 3](新对象) │
│ (两个独立对象,但元素是引用) │
│ │
│ 浅拷贝(嵌套列表): │
│ original ───→ [[ref1], [ref2]] │
│ shallow ───→ [[ref1], [ref2]] │
│ ↓ ↓ │
│ [1,2] [3,4](共享嵌套对象) │
│ │
│ 深拷贝: │
│ original ───→ [[1,2], [3,4]] │
│ deep ───→ [[1,2], [3,4]](完全独立) │
│ │
└─────────────────────────────────────────────────────────────┘第四部分:列表作为栈和队列
列表作为栈
概念说明
栈(Stack)是后进先出(LIFO)的数据结构,Python 列表天然支持栈操作。
┌─────────────────────────────────────────────────────────────┐
│ 栈(Stack) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 特点:后进先出(LIFO - Last In First Out) │
│ │
│ 操作: │
│ • push:入栈(添加元素) │
│ • pop:出栈(移除并返回栈顶元素) │
│ • peek:查看栈顶元素 │
│ │
│ Python 实现: │
│ • push → append() │
│ • pop → pop() │
│ • peek → list[-1] │
│ │
│ 示例: │
│ ┌───────────────────────────────────────────────┐ │
│ │ push(1) → [1] │ │
│ │ push(2) → [1, 2] │ │
│ │ push(3) → [1, 2, 3] │ │
│ │ pop() → 返回 3,列表变为 [1, 2] │ │
│ │ pop() → 返回 2,列表变为 [1] │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘代码示例:
python
# 使用列表作为栈
stack = []
# 入栈
stack.append(1)
stack.append(2)
stack.append(3)
print(stack) # [1, 2, 3]
# 出栈
top = stack.pop()
print(top) # 3
print(stack) # [1, 2]
# 查看栈顶
if stack:
print(stack[-1]) # 2
# 检查栈是否为空
print(len(stack) == 0) # False
print(not stack) # False
# 实际应用:括号匹配
def is_valid_parentheses(s: str) -> bool:
stack = []
pairs = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in pairs.values(): # 左括号
stack.append(char)
elif char in pairs.keys(): # 右括号
if not stack or stack.pop() != pairs[char]:
return False
return len(stack) == 0
print(is_valid_parentheses("()[]{}")) # True
print(is_valid_parentheses("([)]")) # False列表作为队列
概念说明
队列(Queue)是先进先出(FIFO)的数据结构。列表作为队列效率不高,推荐使用 collections.deque。
┌─────────────────────────────────────────────────────────────┐
│ 队列(Queue) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 特点:先进先出(FIFO - First In First Out) │
│ │
│ 操作: │
│ • enqueue:入队(添加元素到队尾) │
│ • dequeue:出队(移除并返回队首元素) │
│ │
│ 列表实现(不推荐): │
│ • enqueue → append() │
│ • dequeue → pop(0) ← O(n) 效率低! │
│ │
│ deque 实现(推荐): │
│ • enqueue → append() │
│ • dequeue → popleft() ← O(1) 效率高! │
│ │
│ 示例: │
│ ┌───────────────────────────────────────────────┐ │
│ │ enqueue(1) → [1] │ │
│ │ enqueue(2) → [1, 2] │ │
│ │ enqueue(3) → [1, 2, 3] │ │
│ │ dequeue() → 返回 1,列表变为 [2, 3] │ │
│ │ dequeue() → 返回 2,列表变为 [3] │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘代码示例:
python
from collections import deque
# 使用 deque 作为队列(推荐)
queue = deque()
# 入队
queue.append(1)
queue.append(2)
queue.append(3)
print(queue) # deque([1, 2, 3])
# 出队
front = queue.popleft()
print(front) # 1
print(queue) # deque([2, 3])
# 从左侧入队
queue.appendleft(0)
print(queue) # deque([0, 2, 3])
# 从右侧出队
right = queue.pop()
print(right) # 3
# 列表作为队列(不推荐,效率低)
list_queue = [1, 2, 3]
list_queue.append(4) # O(1)
list_queue.pop(0) # O(n) ← 所有元素都要移动!
# deque 其他方法
d = deque([1, 2, 3])
d.extend([4, 5]) # 右侧扩展
d.extendleft([0]) # 左侧扩展(注意顺序反转)
d.rotate(1) # 向右旋转 1 位:[5, 1, 2, 3, 4]
d.rotate(-1) # 向左旋转 1 位
d.clear() # 清空第五部分:嵌套列表
二维列表(矩阵)
python
# 创建二维列表
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# 访问元素
print(matrix[0]) # [1, 2, 3](第一行)
print(matrix[0][1]) # 2(第一行第二列)
print(matrix[-1][-1]) # 9(最后一行最后一列)
# 创建矩阵的正确方式
# ❌ 错误方式(引用共享)
matrix = [[0] * 3] * 3
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]](所有行被修改!)
# ✅ 正确方式
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
# 遍历矩阵
for row in matrix:
for element in row:
print(element, end=' ')
print()
# 使用 enumerate
for i, row in enumerate(matrix):
for j, element in enumerate(row):
print(f"matrix[{i}][{j}] = {element}")
# 矩阵转置
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
# 或使用 zip
transposed = list(map(list, zip(*matrix)))三维列表
python
# 创建三维列表
cube = [[[0 for _ in range(3)] for _ in range(3)] for _ in range(3)]
# 访问元素
cube[0][0][0] = 1 # 第一层第一行第一列
# 遍历三维列表
for i, layer in enumerate(cube):
for j, row in enumerate(layer):
for k, element in enumerate(row):
print(f"cube[{i}][{j}][{k}] = {element}")第六部分:列表性能
时间复杂度
┌─────────────────────────────────────────────────────────────┐
│ 列表操作时间复杂度 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 操作 时间复杂度 说明 │
│ ───────────────────────────────────────────── │
│ 索引访问 O(1) list[i] │
│ 索引赋值 O(1) list[i] = x │
│ append() O(1) 添加到末尾 │
│ pop() O(1) 从末尾删除 │
│ pop(0) O(n) 从开头删除 │
│ insert(0, x) O(n) 在开头插入 │
│ remove(x) O(n) 查找并删除 │
│ in / index() O(n) 查找元素 │
│ 切片 O(k) k 是切片长度 │
│ len() O(1) 获取长度 │
│ sort() O(n log n) 排序 │
│ reverse() O(n) 反转 │
│ │
│ 性能建议: │
│ • 频繁在开头插入/删除 → 使用 deque │
│ • 频繁查找元素 → 使用 set 或 dict │
│ • 大量数据排序 → sort() 比 sorted() 快(原地) │
│ │
└─────────────────────────────────────────────────────────────┘性能测试:
python
import time
# 测试 append vs insert
def test_append(n):
lst = []
for i in range(n):
lst.append(i)
return lst
def test_insert(n):
lst = []
for i in range(n):
lst.insert(0, i) # 在开头插入
return lst
n = 100000
start = time.time()
test_append(n)
print(f"append: {time.time() - start:.4f}s") # 约 0.01s
start = time.time()
test_insert(n)
print(f"insert: {time.time() - start:.4f}s") # 约 5s(慢很多!)
# 测试查找:列表 vs 集合
import timeit
lst = list(range(100000))
s = set(range(100000))
# 列表查找 O(n)
print(timeit.timeit('99999 in lst', globals=globals(), number=1000))
# 约 0.5s
# 集合查找 O(1)
print(timeit.timeit('99999 in s', globals=globals(), number=1000))
# 约 0.0001sL2 实践层:用好
最佳实践
列表方法选择指南
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 添加单个元素到末尾 | append() | O(1),最高效 |
| 添加多个元素到末尾 | extend() | 一次添加,避免多次 append |
| 指定位置插入 | insert() | O(n),尽量少用 |
| 删除末尾元素 | pop() | O(1),高效 |
| 删除指定值 | remove() | O(n),需先查找 |
| 删除指定位置 | del list[i] | 不需要返回值时 |
| 清空列表 | clear() | 比 list = [] 更明确 |
| 复制列表 | copy() 或 [:] | 浅拷贝,简单 |
| 排序 | sort() 原地 / sorted() 新列表 | 根据是否保留原列表决定 |
反模式:不要这样做
python
# ❌ 反模式 1:遍历时删除元素
numbers = [1, 2, 3, 4, 5]
for n in numbers:
if n % 2 == 0:
numbers.remove(n) # 漏删!索引变化
# ✅ 正确做法:列表推导式
numbers = [n for n in numbers if n % 2 != 0]
# 或遍历副本
for n in numbers[:]:
if n % 2 == 0:
numbers.remove(n)python
# ❌ 反模式 2:用乘法创建嵌套列表
matrix = [[0] * 3] * 3 # 所有行共享引用!
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
# ✅ 正确做法:列表推导式
matrix = [[0] * 3 for _ in range(3)] # 每行独立对象
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]python
# ❌ 反模式 3:可变默认参数
def add_item(item, lst=[]): # 默认列表被共享!
lst.append(item)
return lst
add_item(1) # [1]
add_item(2) # [1, 2] ← 不是 [2]!
# ✅ 正确做法:用 None 作为默认值
def add_item(item: int, lst: list[int] | None = None) -> list[int]:
if lst is None:
lst = []
lst.append(item)
return lstpython
# ❌ 反模式 4:列表作为队列(开头操作)
queue = [1, 2, 3]
queue.pop(0) # O(n),所有元素移动!
# ✅ 正确做法:使用 deque
from collections import deque
queue = deque([1, 2, 3])
queue.popleft() # O(1)python
# ❌ 反模式 5:频繁用 + 连接列表
result = []
for item in items:
result = result + [item] # 每次创建新列表,O(n²)
# ✅ 正确做法:用 extend 或 append
result = []
for item in items:
result.append(item) # O(n)适用场景
| 场景 | 是否推荐用列表 | 原因 |
|---|---|---|
| 存储有序数据序列 | ✅ 推荐 | 列表天生有序 |
| 需要索引访问 | ✅ 推荐 | O(1) 索引 |
| 频末尾添加/删除 | ✅ 推荐 | append/pop O(1) |
| 频开头添加/删除 | ❌ 不推荐 | 用 deque,O(1) vs O(n) |
| 频查找元素是否存在 | ❌ 不推荐 | 用 set,O(1) vs O(n) |
| 需要唯一元素 | ❌ 不推荐 | 用 set |
| 键值对映射 | ❌ 不推荐 | 用 dict |
| 不可变序列 | ❌ 不推荐 | 用 tuple |
性能专项:append vs insert
python
import timeit
# append 在末尾添加 — O(1) 平均
print(timeit.timeit('[].append(1)', number=10_000_000))
# ~0.3s
# insert(0, x) 在开头插入 — O(n)
print(timeit.timeit('[].insert(0, 1)', number=10_000_000))
# ~2.5s(慢 8 倍!)
# 结论:频繁插入开头 → 使用 collections.deque推导式 vs 手动循环
python
import timeit
# 列表推导式 — 字节码优化(LIST_APPEND 指令)
print(timeit.timeit('[x ** 2 for x in range(1000)]', number=10000))
# ~0.35s
# 手动循环 — append 方法调用开销
print(timeit.timeit('''
r = []
for x in range(1000):
r.append(x ** 2)
''', number=10000))
# ~0.55s(慢 ~60%)
# 结论:推导式不仅简洁,而且更快切片 vs copy() vs list()
python
import timeit
a = list(range(1000))
# 三者等价 — 都是浅拷贝
print(timeit.timeit('a[:]', globals={'a': a}, number=100000)) # 最快
print(timeit.timeit('a.copy()', globals={'a': a}, number=100000)) # 中等
print(timeit.timeit('list(a)', globals={'a': a}, number=100000)) # 最慢
# 推荐:a[:] 最快,a.copy() 可读性最好(显式表明意图)L3 专家层:深入
底层原理:动态数组
Python 如何实现列表
列表在 Python 中使用动态数组实现:
列表内部结构:
┌─────────────────────────────────────────────────────────────┐
│ 动态数组原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 内存布局: │
│ ┌────────────────────────────────────────────┐ │
│ │ [0] [1] [2] [3] [4] [空] [空] [空] │ │
│ │ 元素区(已用) 预留区(未用) │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 关键参数: │
│ • len:元素数量(5) │
│ • capacity:总容量(8) │
│ • growth factor:增长因子(约 1.125 倍) │
│ │
│ 扩容策略: │
│ • 空列表:capacity = 0 │
│ • 添加元素时容量不足 → 申请更大内存 │
│ • 复制所有元素到新内存 → 释放旧内存 │
│ • 新容量 ≈ 旧容量 × 1.125(Python 3.11+) │
│ │
└─────────────────────────────────────────────────────────────┘演示扩容过程
python
import sys
lst = []
for i in range(10):
lst.append(i)
print(f"len={len(lst)}, size={sys.getsizeof(lst)} bytes")
# 输出:
# len=0, size=56 bytes (空列表)
# len=1, size=64 bytes (扩容到 4)
# len=2, size=64 bytes
# len=3, size=64 bytes
# len=4, size=64 bytes
# len=5, size=72 bytes (扩容到 8)
# len=6, size=72 bytes
# len=7, size=72 bytes
# len=8, size=72 bytes
# len=9, size=80 bytes (再次扩容)性能考量
| 操作 | 时间复杂度 | 原因 |
|---|---|---|
list[i] 索引访问 | O(1) | 连续内存,直接计算地址 |
list[i] = x 索引赋值 | O(1) | 直接写入内存位置 |
append() | O(1) 平均 | 末尾添加,偶尔扩容 O(n) |
pop() | O(1) | 末尾删除 |
insert(0, x) | O(n) | 所有元素后移 |
pop(0) | O(n) | 所有元素前移 |
remove(x) | O(n) | 查找 + 移动 |
x in list | O(n) | 线性查找 |
len() | O(1) | 存储在对象头部 |
设计动机
| 设计选择 | 原因 | 替代方案对比 |
|---|---|---|
| 动态数组 | 索引 O(1),内存紧凑 | 链表:索引 O(n),内存分散 |
| 预留容量 | 避免频繁扩容 | 无预留:每次添加都申请内存 |
| 增长因子 ~1.125 | 平衡内存浪费和扩容次数 | 2 倍:内存浪费多,扩容少 |
知识关联
列表知识关联图:
┌─────────────────┐
│ 序列协议 │
│ __getitem__ │
│ __len__ │
└─────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 列表 │ │ 元组 │ │ 字符串 │
│ list │ │ tuple │ │ str │
│ 可变有序 │ │ 不可变 │ │ 不可变 │
└───────────┘ └───────────┘ └───────────┘
│
│ 需要快速开头操作
▼
┌───────────┐
│ deque │
│ 双端队列 │
│ O(1)两端 │
└───────────┘
│
│ 需要唯一性/快速查找
▼
┌───────────┐
│ 集合 │
│ set │
│ 唯一元素 │
│ O(1)查找 │
└───────────┘字节码验证:推导式 vs 循环
python
import dis
# 列表推导式 — 使用 LIST_APPEND 专用指令
dis.dis("[x ** 2 for x in range(5)]")
# 输出关键指令:
# BUILD_LIST 创建空列表
# GET_ITER 获取迭代器
# LIST_APPEND 2 追加元素 ← 专用字节码,O(1) 无方法查找
# 手动循环 — 使用 CALL_METHOD 调用 append
def manual_loop():
r = []
for x in range(5):
r.append(x ** 2)
return r
dis.dis(manual_loop)
# 输出关键指令:
# LOAD_METHOD append ← 方法查找开销
# CALL_METHOD 1 ← 函数调用开销
# 结论:LIST_APPEND 比 LOAD_METHOD + CALL_METHOD 快 ~60%扩容策略验证
python
import sys
lst = []
prev = sys.getsizeof(lst)
for i in range(20):
lst.append(i)
cur = sys.getsizeof(lst)
if cur != prev:
print(f"len={len(lst):2d} size={cur:4d} bytes 扩容!")
prev = cur
# 典型输出(Python 3.11+):
# len= 1 size= 88 bytes 扩容! (容量 4)
# len= 5 size= 120 bytes 扩容! (容量 8)
# len= 9 size= 184 bytes 扩容! (容量 16)
# 增长模式:0→4→8→16→24→32→...
# 预分配优化:提前知道大小时
n = 1_000_000
pre_alloc = [None] * n # 一次性分配
for i in range(n):
pre_alloc[i] = i
# 避免 20+ 次扩容,减少内存拷贝第九部分:常见错误
常见陷阱
python
# 陷阱 1:修改列表时遍历
numbers = [1, 2, 3, 4, 5]
# ❌ 错误:遍历时删除
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers) # [1, 3, 5](可能漏删)
# ✅ 正确:使用切片复制或列表推导
numbers = [n for n in numbers if n % 2 != 0]
# 或
for n in numbers[:]: # 遍历副本
if n % 2 == 0:
numbers.remove(n)
# 陷阱 2:列表乘法创建嵌套列表
matrix = [[0] * 3] * 3 # ❌ 所有行共享引用
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
# ✅ 正确
matrix = [[0] * 3 for _ in range(3)]
# 陷阱 3:默认参数使用列表
def add_item(item, lst=[]): # ❌ 默认列表会被修改
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2](不是 [2]!)
# ✅ 正确
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
# 陷阱 4:整数列表的乘法
a = [1, 2, 3]
b = a * 2 # [1, 2, 3, 1, 2, 3](复制元素)
# 不是 [2, 4, 6](数学乘法)
# 陷阱 5:比较列表
[1, 2] == [1, 2] # True(内容相同)
[1, 2] is [1, 2] # False(不同对象)本章小结
┌─────────────────────────────────────────────────────────────┐
│ 列表 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 创建: │
│ ✓ [1, 2, 3]、list()、推导式 │
│ │
│ 访问: │
│ ✓ 索引 [0]、切片 [start:end:step] │
│ ✓ 正向索引(0 开始)、反向索引(-1 开始) │
│ │
│ 方法: │
│ ✓ 添加:append、insert、extend │
│ ✓ 删除:remove、pop、del、clear │
│ ✓ 查找:index、count、in │
│ ✓ 排序:sort(原地)、sorted(新列表) │
│ │
│ 复制: │
│ ✓ 赋值 (=):引用,不拷贝 │
│ ✓ 浅拷贝:copy()、[:]、list() │
│ ✓ 深拷贝:copy.deepcopy() │
│ │
│ 特殊用途: │
│ ✓ 栈:append + pop(LIFO) │
│ ✓ 队列:使用 deque(FIFO) │
│ ✓ 矩阵:嵌套列表 │
│ │
│ 性能: │
│ ✓ 索引 O(1)、末尾操作 O(1) │
│ ✓ 开头操作 O(n)、查找 O(n) │
│ │
└─────────────────────────────────────────────────────────────┘