列表 List 详解

列表(list)是 Python 里使用最频繁的容器类型。要说 Python 的”灵魂容器”,字典可能占一半,列表就占另一半。学完基础后,几乎每一个项目、每一段脚本,你都会用到列表。这一篇要把列表讲通透:怎么创建、怎么增删改查、切片、常用方法、性能特性、以及一些新手才踩得到的坑。

一、什么是列表

Python 的列表是一个 可变有序可存放任意类型元素的容器。用中括号创建:

1
2
3
empty = []
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None, [1, 2]] # 什么都能装

跟 C 的数组、Java 的 ArrayList 类似,但 Python 的列表不要求元素类型一致。底层是一段连续的指针数组,指向真实对象。

二、创建列表的多种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
# 字面量
a = [1, 2, 3]

# list() 构造函数(从可迭代对象转)
b = list("abc") # ['a', 'b', 'c']
c = list(range(5)) # [0, 1, 2, 3, 4]
d = list((1, 2, 3)) # [1, 2, 3]

# 列表推导式(第 15 篇讲)
e = [x * 2 for x in range(5)] # [0, 2, 4, 6, 8]

# 重复元素
zeros = [0] * 5 # [0, 0, 0, 0, 0]

陷阱[[]] * 3 得到的是三个指向同一个内层列表的引用!

1
2
3
4
5
6
grid = [[]] * 3
grid[0].append(1)
print(grid) # [[1], [1], [1]] 三行都变了

# 正确写法
grid = [[] for _ in range(3)]

三、索引与切片

列表支持索引(正/负)和切片:

1
2
3
4
5
6
7
8
9
a = [10, 20, 30, 40, 50]

print(a[0]) # 10
print(a[-1]) # 50
print(a[1:4]) # [20, 30, 40]
print(a[:3]) # [10, 20, 30]
print(a[2:]) # [30, 40, 50]
print(a[::-1]) # [50, 40, 30, 20, 10] 反转
print(a[::2]) # [10, 30, 50] 步长 2

索引越界抛 IndexErrora[10] 会崩,但 a[10:20] 只会返回空列表。切片比索引更”宽容”。

切片返回新列表——修改切片不影响原列表:

1
2
3
4
b = a[1:3]
b[0] = 999
print(a) # 原列表不变
print(b) # [999, 30]

想拷贝整个列表:b = a[:]b = list(a)b = a.copy()

四、修改列表

列表可变,可以直接改:

1
2
3
4
5
6
7
8
9
a = [1, 2, 3]
a[0] = 99 # 单个元素赋值
print(a) # [99, 2, 3]

a[1:] = [7, 8, 9] # 切片赋值,可以替换任意长度
print(a) # [99, 7, 8, 9]

a[1:3] = [] # 切片删除
print(a) # [99, 9]

切片赋值非常强大,可以在中间插入元素:

1
2
3
a = [1, 2, 3]
a[1:1] = [10, 20] # 在索引 1 处插入 10, 20
print(a) # [1, 10, 20, 2, 3]

五、增删改查方法

添加

1
2
3
4
5
6
7
8
a = [1, 2, 3]
a.append(4) # 尾部添加单个:[1, 2, 3, 4]
a.extend([5, 6]) # 尾部添加多个:[1, 2, 3, 4, 5, 6]
a.insert(0, 0) # 在索引 0 处插入:[0, 1, 2, 3, 4, 5, 6]

# extend 与 + 的区别
a + [7] # 返回新列表,a 不变
a += [7] # 就地修改,等价于 a.extend([7])

性能提示append 是 O(1),insert(0, x) 是 O(n)(要移动所有元素)。频繁头部插入用 collections.deque

删除

1
2
3
4
5
6
7
8
9
10
a = [10, 20, 30, 20, 40]

a.remove(20) # 按"值"删除第一个匹配:[10, 30, 20, 40]
popped = a.pop() # 弹出并返回最后一个:40,a = [10, 30, 20]
popped = a.pop(0) # 弹出索引 0:10,a = [30, 20]

del a[0] # 按索引删除:a = [20]
del a[:] # 清空

a.clear() # 清空

陷阱remove 只删第一个匹配的,全部删除要用循环或列表推导式。

查找

1
2
3
4
5
6
a = [10, 20, 30, 20, 40]

print(20 in a) # True
print(a.index(20)) # 1,第一次出现的位置
print(a.index(20, 2)) # 3,从索引 2 开始找
print(a.count(20)) # 2,出现次数

找不到会抛 ValueError,所以查找前先 in 检查,或者用 try:

1
2
3
4
try:
i = a.index(99)
except ValueError:
i = -1

修改

除了索引赋值,还有排序和反转:

1
2
3
4
5
6
7
8
9
a = [3, 1, 4, 1, 5, 9, 2, 6]

a.sort() # 就地升序:[1, 1, 2, 3, 4, 5, 6, 9]
a.sort(reverse=True) # 降序
a.reverse() # 就地反转

# 不修改原列表,返回新列表
b = sorted(a)
c = list(reversed(a))

复杂排序

sortsorted 都支持 key 参数:

1
2
3
4
5
words = ["Banana", "apple", "cherry"]
words.sort(key=str.lower) # 忽略大小写

people = [("张三", 25), ("李四", 20), ("王五", 30)]
people.sort(key=lambda p: p[1]) # 按年龄

多级排序:

1
records.sort(key=lambda r: (r.grade, -r.score))   # 先按 grade,再按 score 降序

六、遍历

前面 循环语句 for 与 while 已讲过:

1
2
3
4
5
6
7
8
for x in a:
print(x)

for i, x in enumerate(a):
print(i, x)

for x, y in zip(a, b):
print(x, y)

七、常用统计与聚合

1
2
3
4
5
6
7
a = [3, 1, 4, 1, 5, 9, 2, 6]

print(len(a)) # 8
print(min(a)) # 1
print(max(a)) # 9
print(sum(a)) # 31
print(sum(a) / len(a)) # 平均

想要中位数、方差、标准差:statistics 模块。

1
2
3
4
import statistics
statistics.median(a) # 3.5
statistics.mean(a) # 3.875
statistics.stdev(a) # 标准差

八、拷贝:浅拷贝 vs 深拷贝

前面提到 a[:] 拷贝,这是浅拷贝——只复制最外层,内层对象仍是共享的:

1
2
3
4
5
6
7
8
9
10
11
import copy

original = [[1, 2], [3, 4]]

shallow = original[:] # 或者 original.copy()
shallow[0][0] = 999
print(original) # [[999, 2], [3, 4]] 内层被改了!

deep = copy.deepcopy(original)
deep[0][0] = 111
print(original) # 不受影响

规则:

  • 元素都是不可变(int/str/tuple)时,浅拷贝够用;
  • 元素是可变对象(list/dict/自定义类)且需要独立修改,用 copy.deepcopy

九、列表作为栈和队列

(LIFO)用 list 完美:

1
2
3
4
stack = []
stack.append(1) # push
stack.append(2)
stack.pop() # pop → 2

队列(FIFO)用 list 也行,但 pop(0) 是 O(n)。频繁头部弹出请用 collections.deque

1
2
3
4
5
6
from collections import deque
q = deque([1, 2, 3])
q.append(4) # 右侧添加
q.appendleft(0) # 左侧添加
q.popleft() # 左侧弹出 → 0,O(1)
q.pop() # 右侧弹出 → 4

十、二维列表(矩阵)

1
2
3
4
5
6
7
8
9
10
11
# 3x4 全 0 矩阵
matrix = [[0] * 4 for _ in range(3)]

# 访问
matrix[1][2] = 9

# 遍历
for row in matrix:
for cell in row:
print(cell, end=" ")
print()

别用 [[0]*4] * 3——那是三个指向同一行的引用!这是新手最常翻车的地方之一。

十一、性能特性

  • 索引访问:O(1)
  • append / pop(尾部):均摊 O(1)
  • insert(0, x) / pop(0):O(n)
  • x in list:O(n)(用 set 更快,见 集合 Set
  • list.remove(x):O(n)
  • 排序:O(n log n),用的是 Timsort,稳定排序
  • 内存:一个元素约 8 字节指针 + 对象自身

十二、一个综合例子:图书管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# books.py
"""极简图书管理,演示列表增删改查、排序、查找。"""

books = []

def add(title: str, author: str, year: int) -> None:
books.append({"title": title, "author": author, "year": year})

def remove(title: str) -> bool:
for b in books:
if b["title"] == title:
books.remove(b)
return True
return False

def find_by_author(author: str) -> list[dict]:
return [b for b in books if b["author"] == author]

def sort_by_year(desc: bool = False) -> list[dict]:
return sorted(books, key=lambda b: b["year"], reverse=desc)

def top_n_recent(n: int) -> list[dict]:
return sort_by_year(desc=True)[:n]


if __name__ == "__main__":
add("Python 编程", "Guido", 2020)
add("Fluent Python", "Ramalho", 2022)
add("Effective Python", "Slatkin", 2019)
add("Python Tricks", "Bader", 2018)

print("=== 全部(按年份升序)===")
for b in sort_by_year():
print(b)

print("=== 最新两本 ===")
for b in top_n_recent(2):
print(b)

print("=== Guido 的书 ===")
for b in find_by_author("Guido"):
print(b)

remove("Python Tricks")
print(f"删除后剩 {len(books)} 本")

十三、常见陷阱

陷阱 1:[[0]*n] * m 二维列表

1
2
3
4
grid = [[0] * 3] * 2
grid[0][0] = 1
print(grid) # [[1, 0, 0], [1, 0, 0]]
# 正确:grid = [[0] * 3 for _ in range(2)]

陷阱 2:循环中修改列表

1
2
3
for x in items:
if not valid(x):
items.remove(x) # 索引错乱,跳漏元素

用推导式重建:items = [x for x in items if valid(x)]

陷阱 3:默认参数是列表

1
2
3
def add(item, lst=[]):     # ❌
lst.append(item)
return lst

多次调用会共享同一个列表。改成:

1
2
3
4
5
def add(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst

陷阱 4:== 与 is

1
2
[1, 2] == [1, 2]    # True
[1, 2] is [1, 2] # False

判断值相等永远用 ==

陷阱 5:+=+ 的区别

1
2
3
4
5
6
7
8
9
a = [1, 2]
b = a
a += [3] # 就地修改,b 也变
print(b) # [1, 2, 3]

a = [1, 2]
b = a
a = a + [3] # 创建新列表,a 指过去,b 不变
print(b) # [1, 2]

十四、小结与延伸阅读

  • 列表是可变、有序、任意类型的容器;
  • 增:append/extend/insert;删:remove/pop/del;改:索引/切片赋值;
  • 排序有 list.sort(就地)和 sorted(返回新),都支持 key
  • 拷贝分浅/深,可变元素警惕引用共享;
  • 头部操作慢,用 deque;
  • 二维列表用 [[0]*n for _ in range(m)]
  • 遍历时不要改容器。

延伸阅读:

下一篇 元组 Tuple 与不可变序列 我们讲列表的孪生兄弟——元组。