Skip to content
This page has been auto-translated and may contain errors.View in English

列表

变量只能保存一个东西。列表可以按顺序保存许多东西,全部归在一个名字下。排行榜是一串有序的分数。测验是一组题目的集合。一旦你需要管理一组相关的值,你就需要一个列表。

列表是 Python 中通用的有序、可变序列。它非常适合任何随时间变化的事物:增加或删除项、打乱顺序、过滤或排序内容。当顺序很重要且集合会发生变化时,列表通常是首选。

list 是 Python 的动态数组:一种有序、可变的序列,底层由连续的堆分配空间支撑。随机访问的复杂度为 O(1)。append() 的均摊复杂度为 O(1),因为数组会预留额外空间,在溢出时增长。insert()remove() 的复杂度为 O(n),因为它们需要移动后续元素。这些代价应指导你选择何时使用其他数据结构。

创建列表

使用方括号,值之间用逗号分隔。列表可以混合保存任何类型的值,空列表也是合法的,常作为逐步构建的起点。

列表使用方括号语法定义,并保留插入顺序。它们可以保存任何 Python 值,包括其他列表。空列表 [] 是逐步累积元素时的标准起点。

方括号字面量会在堆上分配一个新的 list 对象,并预留一定容量。元素可以是任何 Python 对象;列表存储的是引用,而不是直接的值。允许混合元素类型,但除了快速脚本之外,实践中并不常见。

python
scores   = [87, 92, 74, 65, 91]
players  = ["小明", "小红", "小刚"]
mixed    = ["小明", 87, True, 3.14]   # 任何类型,但不常见
empty    = []

索引和切片

列表使用与字符串相同的编号方式:位置从 0 开始,负数从末尾倒数。你可以按位置读取任何元素。因为列表是可变的,所以你也可以向特定位置写入。

列表的索引和切片规则与字符串相同。关键区别在于可变性:你可以对索引或切片赋值来就地修改元素,这是字符串不允许的。

list.__getitem__ 接受整数和 slice 对象,遵循与 str 相同的截断规则。__setitem__ 支持元素赋值。切片赋值会替换一段元素,如果替换内容长度不同,列表大小也会随之改变:lst[1:3] = [10, 20, 30] 用三个元素替换两个元素。

python
scores = [87, 92, 74, 65, 91]

scores[0]      # 87  (第一个)
scores[-1]     # 91  (最后一个)
scores[1:3]    # [92, 74]
scores[:2]     # [87, 92]
scores[::-1]   # [91, 65, 74, 92, 87]  (反转)

scores[0] = 90   # 可变:可以工作(字符串会抛出 TypeError)

添加元素

有三种添加元素的方法。append() 将单个元素添加到末尾,这是你几乎每次都会用的方法。insert() 在指定位置添加。extend() 合并另一个列表。

append() 的均摊复杂度为 O(1),是逐个构建列表的标准方式。insert() 的复杂度为 O(n),因为它要移动后续元素。extend() 等价于 +=,比在循环中重复调用 append() 更高效。

append() 使用预分配的缓冲区,只有在溢出触发扩容时才进行复制。前几次扩容后,增长因子约为 1.125 倍,因此均摊复杂度为 O(1)。insert(0, x) 的复杂度为 O(n):每个元素都要右移。对于频繁的头部插入,collections.deque 提供 O(1) 的 appendleftextend(iterable) 调用一次 __iter__,并在一次操作中完成增长。

python
scores = [87, 92, 74]

scores.append(65)          # [87, 92, 74, 65]
scores.insert(1, 100)      # [87, 100, 92, 74, 65]
scores.extend([55, 71])    # [87, 100, 92, 74, 65, 55, 71]

一个常见的错误:对一个列表使用 append() 会将整个列表作为一个元素添加,得到一个嵌套在列表中的列表。要合并,请使用 extend():

append(x) 总是将 x 作为单个元素添加。给 append() 传入一个列表会得到嵌套列表。当你想要将另一个列表的所有元素合并进来时,应该使用 extend():

append(x) 不论类型,都将 x 作为单个对象传入 list_appendextend(iterable) 对参数调用 __iter__,逐个添加每个元素。+= 运算符调用 __iadd__,其底层调用的就是 extend

python
scores.append([55, 71])    # [..., [55, 71]]  嵌套列表,可能是错的
scores.extend([55, 71])    # [..., 55, 71]    合并,正确

删除元素

有四种工具可以删除元素。remove() 按值搜索。pop() 按位置删除,并返回该元素。del 按位置删除,但不返回值。clear() 清空整个列表。

remove() 的复杂度为 O(n):它按值扫描第一个匹配项。pop() 不带参数时,删除最后一项的复杂度为 O(1)。pop(i) 在其他位置时复杂度为 O(n),因为元素需要移动。del scores[i] 等价于 pop(i),但丢弃返回值。

remove(value) 对每个元素调用 __eq__ 直到找到匹配项,然后将所有后续元素左移:复杂度 O(n)。pop(-1) 复杂度为 O(1),不需要移动。pop(i) 在其他索引时复杂度为 O(n)。如果需要频繁地从任意位置删除,请考虑重构数据或使用其他集合类型。

python
scores = [87, 92, 74, 65, 91]

scores.remove(74)    # 删除第一次出现的 74
scores.pop()         # 删除并返回最后一项 (91)
scores.pop(0)        # 删除并返回位置 0 的元素 (87)
del scores[1]        # 删除位置 1 的元素,无返回值
scores.clear()       # 删除所有元素

如果值不在列表中,remove() 会抛出 ValueError。如果不确定,可以先用 in 检查:

python
if 74 in scores:
    scores.remove(74)

remove() 找不到值时会抛出 ValueErrorin 检查会增加一次 O(n) 的扫描;你做了两次遍历。对于一次性代码这是可以的。使用 try/except ValueError 的正规错误处理将在 文件与异常 章节介绍。

in + remove() 模式是两次 O(n) 扫描。当不需要保留顺序时,更快的做法是将目标与最后一个元素交换并 pop:复杂度 O(1)。如果需要 O(1) 的成员查询,请使用 set 而不是 list,这将在 元组与集合 章节介绍。

排序

sorted() 返回一个全新的已排序列表,原列表保持不变。.sort() 就地排序并返回 None。这个差别比听起来更重要。

sorted() 是安全的默认选择:它从不修改原列表。.sort() 就地修改并返回 None,这是个常见的陷阱。把 .sort() 的返回结果赋给变量,你得到的是 None,而不是排序后的列表。需要保留原列表时使用 sorted();只需要排序版本时使用 .sort()

两者都使用 Timsort:一种混合归并/插入排序,最坏情况 O(n log n),对几乎已排序的数据为 O(n)。Timsort 是稳定的:相等元素保留其原有的相对顺序。.sort() 故意返回 None(命令-查询分离原则)。sorted() 接受任何可迭代对象,不仅仅是列表,并且总是返回列表。

python
scores = [87, 42, 96, 55, 71]

ranked = sorted(scores)            # [42, 55, 71, 87, 96] (新列表)
scores.sort()                      # 就地排序,返回 None
scores.sort(reverse=True)          # [96, 87, 71, 55, 42]

result = scores.sort()             # result 是 None,不是排序后的列表

常用操作

Python 有一组直接作用于列表的内置工具。len()sum()min()max() 是你会经常用到的四个。

内置的序列函数可以作用于任何列表。对列表来说,in 是线性扫描;如果需要快速重复进行成员测试,请转换为集合。.index() 在找不到值时会抛出 ValueError

len()sum()min()max() 都会调用 __iter__,可用于任何可迭代对象,而不仅仅是列表。对列表来说 in 复杂度为 O(n);如需 O(1) 查找,请使用 set.index(value) 同样是 O(n)。sum() 默认 start=0,不支持字符串拼接;对字符串请使用 "".join()

python
scores = [87, 92, 74, 65, 91]

len(scores)          # 5
sum(scores)          # 409
min(scores)          # 65
max(scores)          # 92
scores.count(87)     # 1
scores.index(74)     # 2
74 in scores         # True
74 not in scores     # False
scores.copy()        # 浅拷贝
scores.reverse()     # 就地反转

迭代

for 循环会一次一个地遍历列表。for 后面的变量依次接收每个元素。当你还需要位置时,enumerate() 可以同时给你两者,而无需手动计数。

for item in list 调用列表的迭代器,并在每一步推进它。enumerate(iterable, start=0) 包装迭代器并产生 (index, value) 对。使用 enumerate() 比维护计数器变量更简洁,也更不容易出错。

for 调用 iter(list) 获取 list_iterator,然后对其调用 next() 直到 StopIterationenumerate() 包装任何迭代器并产生 (i, value) 对。start 参数偏移计数器,但不影响底层索引。i, item = pair 解包可以工作,因为 enumerate 产生的是元组。

python
players = ["小明", "小红", "小刚"]

for player in players:
    print(player)

for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")
# 1. 小明
# 2. 小红
# 3. 小刚

for 循环和 enumerate

forenumerate() 将在 控制流 章节完整介绍。简短版本:for player in players 每个元素运行一次;enumerate() 在每次迭代中同时给你位置和值。

嵌套列表

列表可以包含其他列表。这就是表示网格或表格的方式:一个行的列表,每行是一个值的列表。两组方括号访问一个元素:第一组选择行,第二组选择列。

嵌套列表是列表引用的列表。每个内部列表都是独立的对象。使用链式下标访问:grid[row][col]。修改内部列表会影响外部列表,因为外部列表持有的是同一个对象的引用。

嵌套列表不是真正的 2D 数组:外部列表持有对象引用,内部列表可以是不同的长度和类型。访问会链式调用两次 __getitem__。嵌套列表的浅拷贝复制外层容器但不复制内部列表;对内部列表的修改会影响两个副本。

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

grid[0]       # [1, 2, 3]
grid[1][2]    # 6  (第 1 行,第 2 列)

可变性:陷阱所在

这几乎让每个人都吃过亏。将列表赋给一个新变量并不会创建副本。两个名字指向同一个列表。改一个就改了另一个。要得到独立的副本,你必须显式请求。

列表赋值复制的是引用,而不是对象。两个名字指向同一个底层列表。通过任一名字进行的修改都会影响同一份数据。当你需要独立的数据时,请用 .copy()list() 或完整切片 [:] 显式复制。

b = a 将第二个名字绑定到同一个列表对象。通过 b 进行的任何修改都会修改 a 同样指向的那个对象。.copy()a[:] 创建的是浅拷贝:一个新的列表对象,但持有相同的元素引用。对于不可变值的平坦列表,这是安全的;对于嵌套列表,内部对象仍然是共享的。

python
a = [1, 2, 3]
b = a            # b 不是副本;它指向同一个列表

b.append(4)
print(a)         # [1, 2, 3, 4]  (变了:a 和 b 是同一个列表)
python
b = a.copy()    # 独立的副本
b = list(a)     # 同样的结果
b = a[:]        # 也一样

# 嵌套列表的内部对象仍然是共享的:
matrix = [[1, 2], [3, 4]]
copy   = matrix.copy()

copy[0].append(99)
print(matrix)   # [[1, 2, 99], [3, 4]]  (内部列表是共享的)

对于需要完全独立的嵌套结构,请手动复制每个内部列表,或使用标准库中的 copy.deepcopy(),这将在 模块 章节介绍。

更多方法

方法作用
.append(item)添加到末尾
.insert(i, item)在位置 i 插入
.extend(iterable)从可迭代对象添加所有元素
.remove(value)删除第一次出现的值
.pop(i)删除并返回位置 i 的元素(默认:最后一个)
.clear()删除所有元素
.index(value)第一次出现的位置
.count(value)出现的次数
.sort()就地排序
.reverse()就地反转
.copy()返回浅拷贝

实战

构建一个分数跟踪器:添加结果、排序,并打印摘要。

python
scores = []

scores.append(87)
scores.append(54)
scores.append(92)
scores.append(67)
scores.append(45)

scores.sort(reverse=True)

print(f"排名分数: {scores}")
print(f"最高: {scores[0]}")
print(f"最低: {scores[-1]}")
print(f"平均: {sum(scores) / len(scores):.1f}")
print(f"前 3 名: {scores[:3]}")

两个并行的名字和分数列表:找出最佳选手并打印排名结果。

python
names  = ["小明", "小红", "小刚", "小华"]
scores = [87, 74, 92, 55]

best_score  = max(scores)
best_index  = scores.index(best_score)
best_player = names[best_index]

print(f"第一名: {best_player} ({best_score})")
print(f"平均:    {sum(scores) / len(scores):.1f}")

ranked = sorted(scores, reverse=True)
print(f"分布(排名): {ranked}")

for i in range(len(ranked)):
    print(f"  第 {i + 1} 名: {ranked[i]}")

演示别名与复制之间的区别,以及嵌套列表的浅拷贝与深拷贝之间的区别。

python
# 别名:b 不是副本
a = [1, 2, 3]
b = a
b.append(4)
print(a)    # [1, 2, 3, 4]  (同一个对象)

# 浅拷贝:外层列表独立,内部列表共享
matrix    = [[1, 2, 3], [4, 5, 6]]
shallow   = matrix.copy()
shallow[0].append(99)
print(matrix)    # [[1, 2, 3, 99], [4, 5, 6]]  (内部列表共享)

# 用 for 循环手动深拷贝(无需导入)
matrix    = [[1, 2, 3], [4, 5, 6]]
deep_copy = []
for row in matrix:
    deep_copy.append(row[:])    # 显式复制每个内部列表

deep_copy[0].append(99)
print(matrix)    # [[1, 2, 3], [4, 5, 6]]  (未变)