编程语言研究书库

列表 — 数据的集装箱

列表 — 数据的集装箱

列表是 Python 里使用最多的数据结构,没有之一。 前面所有章节的变量 一次只能装一个值;列表一次装一排,还能随时往里加、往外拿、排队、翻转。 原书把它排在第三部分(数据结构)的第一章,是因为后面的元组、集合、 字典全是它的「变体对照组」——学透列表,三扇门开了一扇半。

1. 这一章在全书链条里的位置

读法建议:本章是全书最长的一章(原书 24 页),但骨架其实只有一条—— 「序列机制(索引/切片/遍历)+ 可变性(增删改)+ 推导式(批量造)」。 所有方法都是这三个词的组合;练习看着多,是同一骨架的四十次换装。 抓住骨架读,24 页就是 3 页。

第 05 章埋的伏笔在这里兑现:索引、切片、len()、for 逐个遍历—— 那套机制属于「序列」,列表是序列的第一个完整实例。第 08 章练习里 split() 造出来的、打印出来的方括号 [...],也终于有了名字。 更关键的是后半场:列表推导式是「循环 + 分支 + 表达式」三合一的压缩写法, 它是 Python 代码从「能跑」到「地道」的分水岭。

2. 顶层全景:一个列表的六种操作

先把本章的六个抽屉与后面三章的关系标在图上:元组(第 12 章)抽走 「改」这个抽屉,集合(第 13 章)抽走「序」这个前提,字典(第 14 章) 换掉「按位取」的整个机制——列表是四种容器里功能最全的基准, 其余三种都是它的定向减法。

  造     [1, 2, 3] / list('abc')        → 中括号直接写,或从别的序列转换
  取     my_list[0]、my_list[-1]         → 按位置取一个(0 起,负索引从尾)
  切     my_list[1:4]、my_list[::-1]     → 按区间取一段(stop 不含,切片同弦)
  巡     for x in my_list:               → 逐个处理(直接迭代是地道写法)
  改     append / remove / insert / sort → 原地增删排序(列表是可变的!)
  推     [x**2 for x in my_list if ...]  → 一行造出新列表(本章压轴)

  图说:造、取、切、巡、改、推——列表工具箱的六个抽屉。

3. 核心原理一:列表是什么、能装什么

一个生活锚点:列表就是「购物清单」——有顺序(从上往下买)、 能加项(想起来了追加)、能划掉(买完了 remove)、同一商品可以买多个 (允许重复)。后面所有机制都能往这张清单上贴;贴不上的机制 (比如「按键取」)就是后面章节的新容器。

原书的定义:列表是动态的数据结构,装一组元素,元素可以任意类型, 可以改、可以伸缩1。三个定语逐个拆:

  • 有序:元素按加入顺序排队,每个元素有固定的位置编号(索引);
  • 任意类型(heterogeneous,异质):整数、浮点、字符串、布尔, 甚至别的列表,都能混装——student = ['David Wood', 18, 3.9, True] 一条记录一个列表2;
  • 可修改、可伸缩:和字符串的「不可变」正相反,列表造好之后 照样增删元素、改排顺序——这正是它成为主力数据结构的原因1。

「为什么能混装」书里给了机制层面的答案,值得记住:列表里存的其实是 引用(reference)——指向实际对象的地址,而不是对象本身。引用本身 格式统一,指向谁随意,所以什么类型都能装3。这正是第 03 章 「变量是引用」模型的延伸:变量是指向值的引用,列表是一排这样的引用。

一个容易数错的细节:len() 数的是第一层元素。嵌套的列表整体 算一个——my_list_6 = [1, 'Hello', 1.5, [1.5, 1.6], my_list_4] 的 len 是 5,不是把内层全展开数4。列表的名单是一层平面的, 里面的盒子不拆——想数全部元素,得逐层 len 相加。

创建方式两种,等价任选5:

list()                    # 空列表 → []
list('Hello, world')      # 从字符串转 → 12 个字符各成一个元素
[1, 3, 2, 4, 6]           # 中括号直接写(最常用,字面量法)

字面量法 [1, 2, 3] 是日常绝对主力——list() 构造函数主要用于 「把别的序列转过来」,造表本身用中括号。

注意 list('Hello, world') 把字符串拆成单个字符(12 个元素, 含空格和逗号各算一个)——序列转列表的默认动作是「逐个拆」5。

4. 核心原理二:按位取 — 索引与负索引

再给一个记忆钩:索引就是「快递柜的格口号」——从 0 开始编号是 柜子的规矩(别问为什么,记住),负索引是从最后一格往前数 (「倒数第三格」),越界是「没有这个柜格,柜机直接拒收」。 切片则是「从第 3 格到第 7 格,整段抱走」——段里哪怕有空格 (不存在的),也只给有的部分。

规则和第 05 章字符串一字不差:0 起编号,越界报 IndexError: list index out of range,负索引从 -1 倒数6:

fruits = ['apple', 'banana', 'orange', 'grape', 'kiwi']
fruits[0]           # 'apple',第一个
fruits[len(fruits)-1]  # 'kiwi',最后一个(笨写法)
fruits[-1]          # 'kiwi',最后一个(地道写法)
fruits[-len(fruits)]   # 'apple',用负索引取第一个
fruits[5]           # IndexError:list 只有 0–4 号
fruits[-6]          # IndexError:负索引最多到 -5

原书特意对比了 Java、C++(同样 0 起)和 R(从 1 起)—— Python 的 0 起索引不是天经地义,是约定,换语言时别带错习惯6。

5. 核心原理三:遍历 — 两种写法,一种地道

遍历列表是「把一整排数据过一遍」的动作——打印每一行、累加每一个数、 检查每一项合不合格。它是数据处理的基本功,相当于切菜的刀工: 后面的案例章(第 15 章)四个程序,个个都从「造一个空表 + for 循环 逐条处理 + append 收集」这个套路出发。刀工不稳,后面的菜都没法炒。

遍历一个列表有两条路7:

numbers = [1, 2, 3, 4, 5]

# 写法一:索引驱动(要手动管下标)
for i in range(len(numbers)):
  print(numbers[i])

# 写法二:直接迭代(推荐)
for num in numbers:
  print(num)

输出相同,但原书明确推荐第二种:不用管索引,代码更短、更少出错 (索引越界这类事故直接消失)7。「拿索引」只在一种情况下必要: 你需要同时知道元素的位置和值。什么时候才需要?比如要修改列表 特定位置的元素——那时候循环变量该用 range(len(...))。

练习区的四个小任务(求和、数 4 的个数、数偶数)是第 08 章 累加器与计数器模式的列表版——total = 0 起步、total += i 攒, 骨架原封不动,只是数据源从 input() 换成了列表8。 numbers = [1, 2, 2, 2, 3, 4, 4, 5] 里数 4:遍历,逢 4 计数, 得 2;数偶数:i % 2 == 0 当筛子,得 5。

6. 核心原理四:增删改 — 列表是可变的

「可变」三个字是本章与前半本书最大的分水岭:前面所有类型 (int/float/str/bool)造出来就定型,只有列表(以及后面三章的 字典、集合)允许原地改造。这个性质带来两种新生活:一是「渐进式 收集」——先造空表,循环里边算边 append,最后整表可用(第 15 章 案例的主力形态);二是「先造后改」——数据进来了再排序、去重、 修剪。两种新生活都建立在同一个事实上:列表是有生命周期的, 不是一锤子买卖。

字符串改一个字都要造新串;列表直接在原地上手。基本三件9:

my_list = [1, 2, 3, 4, 5]
my_list.append(6)        # 末尾加 → [1, 2, 3, 4, 5, 6]
my_list.remove(3)        # 按值删(删掉数字 3)→ [1, 2, 4, 5, 6]
my_list.insert(2, 10)    # 在 2 号位插 10,原来的往后让 → [1, 2, 10, 4, 5, 6]

三个细节:append 加的是一个元素(哪怕你 append 一个列表, 它也是作为一个整体进末尾);remove 按值删(删掉第一个匹配的, 不是按下标);insert 的插入点后面的元素整体后移—— 这就是「有序集合」的维护成本9。

进阶四件10:

my_list = [1, 2, 3, 4, 5]
my_list.extend([5, 6])       # 追加一个列表的全部元素 → [1,2,3,4,5,5,6]
my_list.reverse()            # 原地翻转 → [6, 5, 5, 4, 3, 2, 1]
my_list.sort()               # 原地升序 → [1, 2, 3, 4, 5, 5, 6]
my_list.sort(reverse=True)   # 原地降序 → [6, 5, 5, 4, 3, 2, 1]
del my_list[2]               # del 按索引删 → 删掉 3 号元素
my_list.clear()              # 清空 → []

append 与 extend 的分工是高频考点:append([5, 6]) 把列表 整体塞进末尾(嵌套一层),extend([5, 6]) 把元素拆散接上去 (长度加二)10。一个是装盒子,一个是倒货。

还有两种「不动原列表」的写法,各管一件事11:

list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2      # + 拼接:造新列表,两个原列表不动
unsorted = [3, 1, 4, 2, 5]
s = sorted(unsorted)          # sorted():返回排好序的【新】列表
                              # unsorted 自己保持 [3, 1, 4, 2, 5]

sort() 与 sorted() 的对比是全章最容易考的点:sort() 原地排, 返回 None;sorted() 不动原列表,返回新列表。要保留原顺序就用 sorted();不在乎原顺序就用 sort()(省一份内存)11。

主线例子:一个水果列表的十二连操作

把上面所有操作串在一条时间线上,素材用练习区的 fruits (初始 ['apple', 'banana', 'orange', 'grape', 'kiwi'])12:

  ① fruits.append('pear')              → 六果,pear 在末尾
  ② fruits.remove('banana')            → banana 被按值删除
  ③ fruits.insert(2, 'blueberry')      → 2 号位插入,orange/grape/kiwi 后移
  ④ fruits.extend(['pineapple', 'strawberry'])
                                         → 八果(extend 拆开追加)
  ⑤ fruits.reverse()                   → 整个翻倒,strawberry 打头
  ⑥ fruits.sort()                      → 按字母序复位
  ⑦ del fruits[1]                      → 删掉 blueberry(当时的 1 号位)
  ⑧ double = fruits + fruits           → 自我拼接,十六果的新列表
  ⑨ fruits += ['blackberry', 'melon']  → 增强拼接,原地加长
  ⑩ ordered = sorted(fruits)           → 新列表按字母序,fruits 保持原样
     (book 演示:fruits 此时九果且无序,ordered 十果按字母序——
      因为 += 加了两个,演示输出两列对比分明)[^12]

  图说:①–⑦ 全是「原地操作」,列表 id 不变、内容大变;
  ⑧⑩ 是「造新列表」,旧列表纹丝不动。原地还是造新,是每记一个
  方法就要问的一句话。

7. 核心原理五:切片 — 字符串规则整包搬来

学到这里你应该有一次「会心一击」:第 05 章学切片时背的十二条规则, 在这里一条都不用重学——这就是当初说「切片是跨类型的通用机制」的 全部含义。通用机制是编程语言对学习者最大的善意:一次学会,处处可用。 下一章的元组还会再应验一次。

语法与默认值和字符串完全相同:list[start:stop:step],stop 不含, 三位置可省13。一组例子把行为钉死:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers[2:7]      # [2, 3, 4, 5, 6]
numbers[:5]       # [0, 1, 2, 3, 4]
numbers[3:]       # [3, 4, 5, 6, 7, 8, 9]
numbers[::2]      # [0, 2, 4, 6, 8]
numbers[1:8:3]    # [1, 4, 7]
numbers[-5:]      # [5, 6, 7, 8, 9]     负起点,从倒数第五取到尾
numbers[:-3]      # [0, 1, 2, 3, 4, 5, 6]  负终点,砍掉末尾三个
numbers[::-1]     # [9, 8, 7, ..., 0]  步长 -1:整个反转

正负混用的三刀更有味道(letters = ['a'…'j'],0–9 号)14:

  letters[2:-3]    → ['c','d','e','f','g']   正起点负终点:掐头(2 前)去尾(3 个)
  letters[-5:7]    → ['f','g']               负起点正终点:-5 即 5 号,取到 7 号前
  letters[-7:3]    → []                      起点 3 号在终点 3 号前?不,-7 是 3 号,
                                             起点=终点 → 空表!切片不报错,给空的
  letters[4:-2:2]  → ['e','g']               起点 4 号、终点 8 号前、隔一个取
  letters[-3::-1]  → ['h','g','f','e','d','c','b','a']  从 7 号倒着取到最头

  图说:切片永远「左闭右开」,正负只是两种数法。起点 ≥ 终点时不报错、给空表——
  这是切片与索引(报错)的第二次性格对比。

练习区再走一遍全模式:fruits[:3] 前三、fruits[-2:] 末二、 fruits[1:-1] 掐头去尾、fruits[::2] 隔一、fruits[::-1] 反转、 fruits[1::3] 从 1 号起每三个取一个——六个切片式就是日常数据的 六种取法15。

8. 核心原理六:列表推导式 — 一行造列表

推导式是 Python 的「招牌菜」——别的语言用户看到 [x**2 for x in nums if x % 2 == 0] 往往第一反应是「这什么咒语」, 而 Python 程序员读它像读英语。学会它,你写的代码就带上了 Python 口音;这也是书名副标题里 BiteSize 精神的浓缩: 一个能一行说清的造表动作,绝不写四行。

压轴机制。先看书名对照:同样的任务,循环三行、推导式一行16:

# 造 0–5 的平方表:循环版
squared = []
for num in range(6):
  squared.append(num ** 2)

# 推导式版
squared = [num ** 2 for num in range(6)]

语法骨架:[表达式 for 项 in 可迭代 if 条件],三个槽位16:

槽位管什么例子
表达式(最前)每个元素变成什么x**2、−x、x*3
for 从句(中间)从哪取原料for x in numbers1
if 条件(可选,最后)筛不筛if x % 2 == 0

三槽各演示一遍(输出全部来自原书练习)17:

power_of_2      = [x**2 for x in numbers1]                 # [1, 4, 9, 16, 25]  只变换
negative        = [-x for x in numbers2]                   # [-6, -7, -8, -9, -10]
even_squares    = [x**2 for x in numbers1 if x % 2 == 0]   # [4, 16]  变换+筛选
divisible_by_3  = [x for x in numbers2 if x % 3 == 0]      # [6, 9]   只筛选
odd_multiples_3 = [x*3 for x in numbers2 if x % 3 == 0]    # [18, 27]

一个诚实的练习设计:greater_than_7 从 numbers1(最大 5)里筛大于 7, 结果是 []——筛选可能筛空,空表也是合法结果,不是错误17。

高级推导式两招,都来自「多个 for」18:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for sublist in matrix for num in sublist]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]  ——压平嵌套列表( for 从句按从左到右嵌套 )

combinations = [(i, j) for i in [1, 2, 3] for j in [3, 4, 5] if i != j]
# 八个有序对,排除 i == j 的 (3, 3)

双 for 的读法:从左到右,先外后内——for sublist in matrix 是外圈,for num in sublist 是内圈,顺序和写两个嵌套 for 循环 完全一致。组合问题(配对、穷举)是它的主战场18。

第三个武器 zip():两个列表并排取对——zip(names, ages) 每次各取一个配成元组,推导式接 for name, age in zip(...) 拆包 逐对处理19:

names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
combined = [[name, age] for name, age in zip(names, ages)]
# [['Alice', 25], ['Bob', 30], ['Charlie', 35]]

sums = [a + b for a, b in zip(numbers1, numbers2)]       # [7, 9, 11, 13, 15]
products = [a * b for a, b in zip(numbers1, numbers2)]   # [6, 14, 24, 36, 50]

zip 与双 for 的分工:双 for 是穷举所有组合,zip 是按位对齐 ——数据来自同一批对象的成对属性,用 zip;要试所有配对,用双 for19。

10. 别名陷阱专节:为什么 b = a 不是复制

这是列表章最重要、也最容易被忽视的一课,值得单独一节。先看事故重现20:

a = [1, 2, 3]
b = a          # 你以为:复制了一份给 b
b.append(4)    # 往 b 里加个 4
print(a)       # [1, 2, 3, 4]  ← a 也跟着变了!

发生了什么?第 03 章的机制在这里完整兑现:b = a 不是拷贝内容, 而是把 b 也指向 a 所指向的那个列表——现在两个名字指着同一个列表, 改哪个都是改它自己。用第 03 章的话说:b 是 a 的别名(alias,另一个名字)。

  b = a 之前:        b = a 之后:
  a ──→ [1, 2, 3]    a ──┐
                       │   ├─→ [1, 2, 3]   ← 只有一个列表,两个名字
                       b ──┘

  图说:赋值复制的是「指向」,从来不是「内容」。字符串时代你感觉不到,
  因为字符串不可变,两个名字指同一个串也无人能改——列表可变,别名立刻显形。

正确的复制姿势,两种等价:

b = a.copy()    # 方法版:显式说「我要一份拷贝」
b = a[:]        # 切片版:取全区间,切片总是造新列表

两种都有效,因为切片天然造新对象。验证方法:a is b 在别名时得 True (同一个对象),复制后得 False;a == b 则两者都得 True(内容相等)。 is 问「是不是同一个」,== 问「长得一不一样」——复制问题用 is 验。

还有一个更阴的变体:浅拷贝只拷第一层。

matrix = [[1, 2], [3, 4]]
dup = matrix.copy()     # 浅拷贝:外层是新列表
dup[0][0] = 99          # 但内层 [1, 2] 还是共享的!
print(matrix)           # [[99, 2], [3, 4]]  ← 原矩阵中招

对策(深拷贝):import copy; dup = copy.deepcopy(matrix)—— 逐层复制,内层也不再共享。入门阶段记住两句话就够: 复制用 copy() 或 [:];内层要独立,deepcopy。

11. 方法速查总表(原书 Table 11.1 全表+扩编)

原书总结表按「语法 + 例子」两行一特征收拢全章21,我们补全并加注 「原地还是造新」——这一列才是用法的分水岭:

操作写法效果原地?
创建[] / list()空表或转换—
取一个my_list[i] / [-1]按位取元素—
切片my_list[a:b:c]取一段(造新)造新
追加my_list.append(x)末尾加一个原地
扩展my_list.extend(iter)末尾倒一串原地
插入my_list.insert(i, x)i 号位插入,后移原地
按值删my_list.remove(x)删第一个 x原地
按位取删my_list.pop(i)取出并删掉,默认末尾原地
排序my_list.sort()升序(可 reverse=True)原地(返回 None)
非破坏排sorted(my_list)返回排好的新列表造新
翻转my_list.reverse()原地倒序原地(返回 None)
拼接a + b / a += b合成新表 / 原地加长视写法
清空my_list.clear()归零原地
按位删del my_list[i]删掉 i 号位原地
推导式[expr for x in iter if c]一行造新表造新

三行「表外补充」正文没讲但常用:index(x)(找位置,找不到 ValueError)、 count(x)(数次数)、x in my_list(在不在,布尔)。 配套记忆一组「按位 vs 按值」:remove 按值、del/pop 按位; index 按值找位、my_list[i] 按位取值——两对互为镜像22。

12. 嵌套列表与矩阵:列表的列表

嵌套列表在第 09 章练习和本章压平推导里都已经登场,这里给它一个 正式的定位:列表套列表 = 表格/矩阵,内层每行等长时是矩阵18:

matrix = [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]
matrix[0]      # [1, 2, 3]      第 0 行(整行是一个元素)
matrix[0][1]   # 2              第 0 行第 1 列——索引连写,像坐标
matrix[-1][-1] # 9              右下角

取单个元素要两级索引 matrix[行][列]——先取行(一个列表), 再在该行里取列。压平(把矩阵摊成一维)的双 for 推导正好是 这个结构的逆运算:[num for sublist in matrix for num in sublist], 外圈逐行、内圈逐元素18。

len 对嵌套列表只数外层(3 行),总元素数要 len(matrix) * len(matrix[0]) (等长时)——第 §3 的「名单是一层平面的」在这里的印证4。

13. 推导式与循环的互译对照

书里的每组推导式演示都附了循环版,这是最好的学习素材—— 互译能力 = 你同时掌握了两门方言16:

任务循环版(三四行)推导式版(一行)
平方表s = [] + for + append(x**2)[x**2 for x in numbers]
取偶数s = [] + for + if + append[x for x in numbers if x % 2 == 0]
平方的偶数上面两者叠加[x**2 for x in numbers if x % 2 == 0]
对应位求和空表 + range + a[i]+b[i][a+b for a, b in zip(x, y)]

互译口诀:for 从句照抄循环头,if 条件照抄筛选行,append 的参数 照抄为表达式。任何「造一个列表」的循环都能这么翻;翻不过去, 说明那段循环不只是造表(还做了别的副作用),那它就不该翻。

什么时候坚持循环版?循环体超过一句、或者要中途 break—— 推导式适合「纯造表」,循环适合「有副作用的流程」。这个分寸 是「pythonic」的真正含义,不是「推导式越多越好」23。

14. 三大遍历模式:映射、筛选、聚合

列表遍历的书里散在多处(11.5.2、推导式、zip),收拢后其实是数据处理的 三大原语——每一个数据程序都由它们拼装81619:

映射(map):每个元素变成新东西。 输入 n 个,输出 n 个:

negative = [-x for x in numbers2]          # 取负: [−6, −7, −8, −9, −10]
power_of_2 = [x**2 for x in numbers1]      # 平方: [1, 4, 9, 16, 25]

筛选(filter):按条件留下一部分。 输出 ≤ 输入:

even_squares = [x**2 for x in numbers1 if x % 2 == 0]   # [4, 16]
divisible_by_3 = [x for x in numbers2 if x % 3 == 0]    # [6, 9]

聚合(reduce):全体合成一个值。 循环 + 累加器的专属领地, 推导式干不了这个:

total = 0
for i in numbers:
  total += i            # 求和:聚合
count_4 = 0
for i in numbers:
  if i == 4:
    count_4 += 1        # 计数:带筛选的聚合

三大模式各就各位:映射与筛选归推导式,聚合归循环累加器。 第 15 章案例里你会看到三者合流——先映射(算每项价格)、再聚合(求总价)。

15. 切片大走查:一张字母表讲透全部组合

letters = ['a','b','c','d','e','f','g','h','i','j'](0–9 号)是原书 最好的切片教具,十行全部输出摆开1314:

  全取          letters[:]        → a b c d e f g h i j    拷贝一份
  前段          letters[:5]       → a b c d e                从头取到 5 号前
  后段          letters[5:]       → f g h i j                从 5 号取到尾
  中段          letters[2:7]      → c d e f g                常规三段
  跳步          letters[::2]      → a c e g i                隔一个(偶数位)
  跳步          letters[1:8:3]    → b e h                    从 1 号起,步长 3
  倒序全取      letters[::-1]     → j i h g f e d c b a      反转的两种写法之一
  负起点        letters[-5:]      → f g h i j                倒数第五到尾
  负终点        letters[:-3]      → a b c d e f g            去掉末尾三个
  掐头去尾      letters[2:-3]     → c d e f g                正起点+负终点混用
  空表          letters[-7:3]     → []                       起点不小于终点
  倒着走        letters[-3::-1]   → h g f e d c b a          从 7 号倒走到头

十二个切片式覆盖了日常全部取数需求。两个「为什么」值得答:

  • letters[::-1] 和 reverse() 都能反转,差别是切片造新列表、 reverse 原地改——想要原列表就切片,不在乎就 reverse13。
  • 起点 ≥ 终点给空表不报错,因为切片的语义是「取交集,没有就没有」; 而单点索引越界必须报错,因为你指名要一个不存在的元素14。 同一个容器,「取一段」宽容、「点一个」严格,两条性格并存。

16. 推导式练习全走查:七道题的花名册

11.9.2 和 11.10.2 的七道推导式练习,逐题过一遍,每题点出它的「考点」1719:

  题 1 [x**2 for x in numbers1]                 纯映射 → [1,4,9,16,25]
  题 2 [-x for x in numbers2]                   映射:一元负号也是表达式 → [-6…-10]
  题 3 [x for x in numbers1 if x > 7]           纯筛选,但 numbers1 最大 5 → []
                                                  考点:筛空合法,别当错误处理
  题 4 [x**2 for x in numbers1 if x % 2 == 0]   映射+筛选叠加 → [4, 16]
  题 5 [x for x in numbers2 if x % 3 == 0]      筛选(整除试纸)→ [6, 9]
  题 6 [x*3 for x in numbers2 if x % 3 == 0]    注意:先筛 3 的倍数再乘 3
                                                  → [18, 27],不是全体乘 3
  题 7 [[a,b] for a in numbers1 for b in numbers2 if a+b < 10]
                                                  双 for 穷举 + 条件 →
                                                  [[1,6],[1,7],[1,8],[2,6],[2,7],[3,6]]
                                                  考点:双 for 的嵌套顺序——
                                                  外圈 a 慢、内圈 b 快,
                                                  所以输出按 a 分组

  图说:七题覆盖「纯映射/纯筛选/叠加/穷举」四种配方,是推导式的最小完备训练集。

zip 三题是另一组对照:[a+b for a, b in zip(numbers1, numbers2)] 按位求和得 [7, 9, 11, 13, 15];[a*b ...] 按位积得 [6, 14, 24, 36, 50]; |a-b| 那题还现场定义了 abs 函数再调用——推导式的表达式槽里 可以放任何函数调用19。对位求和与双 for 穷举是「多序列」的两种 正交需求,第七章的表已经把分工说清了。

17. 创建与混装练习走查

前几节的练习也有信息量,快速走查52:

  • list('List Constructor') → 16 个字符的列表,含空格—— 再次提醒:字符串转列表是逐字符的,空格不特殊5;
  • list2 = [2, 5, 3, 4, 1] 与 list4 = ['2','5','3','4','1']: 一个数字列表一个字符串列表,打印出来形状几乎一样、类型全不同—— 第 03 章「显示值 ≠ 类型」的旧课,在列表里又考一遍;
  • list6 = [list1, list2, list3, list4, list5]:列表的列表 (五张名单钉成一本),len 是 5(五个元素,每个元素是一张名单)4;
  • list7 = ['Neo', 'iPhone', 999.99, 2, True]:一条订单记录, 五类信息一个列表——异质的正当用途:记录 = 定长的混合列表2。

18. 列表 × 函数:第 09 章的零件上岗

列表是数据,函数是动作——两者配对,第 10 章预告的组合正式开工。 本章其实已经在多处示范了这种配对,收拢成三个标准用法:

函数吃列表。 把列表当参数传进去,函数内部遍历它:

def total(nums):
  s = 0
  for n in nums:
    s += n
  return s

total([1, 2, 2, 2, 3, 4, 4, 5])    # 19

sum() 内建函数就是这个函数的官方版;len() 是它的老搭档。 「统计量函数」一族(第 09 章 stats 的列表版)全按这个模板写。

函数吐列表。 return 一个列表,调用方接着处理:

def squares(n):
  return [x**2 for x in range(n)]

squares(5)        # [0, 1, 4, 9, 16]

第 09 章「多值返回用元组」那条,在列表面前有平行的选择: 数量固定且各不同质 → 元组;数量不定且同种 → 列表。 squares 的产出数量由参数决定、每个元素地位相同——列表当仁不让。

函数在推导式里当零件。 表达式槽放函数调用,过滤槽放布尔函数:

def is_even(x):
  return x % 2 == 0

[n for n in numbers if is_even(n)]    # 把第 07 章的判断封装成函数再筛

第三种是「业务逻辑复杂化」后的必然:条件一复杂,塞在推导式里就读不懂了, 起个名字(函数)是唯一的出路——这又回到了第 09 章的单一职责。

19. append vs extend 的完整对照

两字之差,效果天差,值得单独对一遍账910:

a = [1, 2, 3]

a.append([4, 5])    # 把 [4, 5] 当一个元素塞末尾
a                   # [1, 2, 3, [4, 5]]   ← len 是 4!最后一个元素是列表

b = [1, 2, 3]
b.extend([4, 5])    # 把 4 和 5 拆散接上去
b                   # [1, 2, 3, 4, 5]     ← len 是 5

记忆钩子:append 是装箱(东西不动,外面套个箱子),extend 是倒货 (箱子打开,东西一个个搬)。错用的后果不对称:append 错用了只是 多一层嵌套,慢慢能发现;extend 错用了元素散一地,好发现。 书里主线例的 extend 演示 my_list.extend([5, 6]) 得 [1, 2, 3, 4, 5, 5, 6](七元素,有个重复的 5)——注意 extend 不查重,原样照接10。

再补两个近亲的完整区分,凑成「进料三兄弟」:

写法进料方式结果
append(x)x 整体进末尾长度 +1
extend(iter)拆开逐项进末尾长度 +len(iter)
insert(i, x)x 整体插 i 号位长度 +1,后面后移
a += b同 extend(原地)长度 +len(b)

20. 字符串 ↔ 列表:两种互转的完整通道

第 05 章的 split/join 和本章的 list() 构造函数,合起来是文本与 列表之间的双向桥,一张图说清:

  字符串 → 列表
    list('abc')        逐字符拆 → ['a', 'b', 'c']     (不切词,只拆字)
    'a,b,c'.split(',') 按分隔符切 → ['a', 'b', 'c']   (切词,留整词)

  列表 → 字符串
    ''.join(['a','b','c'])   无分隔接 → 'abc'
    ','.join(['a','b','c'])  带分隔接 → 'a,b,c'
    str([1, 2, 3])           整体转字符串(打印形态)→ '[1, 2, 3]'

  图说:list() 与 split() 方向相同(拆),join 是唯一的归途(接);
  str(list) 是「打印形态」,不是归途——它得到的是带括号的文本;
  f'{[1, 2, 3]}' 嵌入的也是这个形态,日志输出常用,数据交换勿用。

四个通道各自的坑:list() 拆字符不拆词;split() 默认按空白 且会吃掉多余空格;join 要求列表元素全是字符串(数字要先 str); str(list) 的结果是给人看的,不能 join 回去5。

21. 索引事故博物馆

按位取元素的报错全集,各配现场(机制依据本章语法,案例是我们的归纳)24:

你写的你会看到的原因
fruits[5](len=5)IndexError: list index out of range最大合法索引是 len-1 = 4
fruits[-6](len=5)同上负索引最小到 -len = -5
fruits[len(fruits)]同上经典的「多一」错误:0 起编号下,len 本身永远越界
空列表 [][0]同上空表没有任何合法索引——先 len 检查后取位

第四行引出一个防御习惯:从 input 或文件造出的列表,取位前先确认 非空(if my_list:),空表的任何索引都是炸点。这正是第 07 章 truthiness(空列表算 False)的实战用途——两条知识在这里会师。

22. 什么时候必须用索引遍历

§5 说「优先直接迭代」,但剩下两种情况非索引不可——补齐这个判据, 遍历的选择才算完整7:

改元素的时候。 直接迭代拿到的是元素的拷贝(引用另说),赋值改不到 原列表:

nums = [1, 2, 3]
for n in nums:
  n = n * 10        # 改的是循环变量,nums 原封不动!
print(nums)         # [1, 2, 3]

# 正解:索引驱动
for i in range(len(nums)):
  nums[i] = nums[i] * 10
print(nums)         # [10, 20, 30]

需要位置信息的时候。 打印「第几名是谁」、相邻元素比较、 按位置对齐另一个列表——都要 for i in range(len(a)) 然后 a[i](或者更地道的 for i, x in enumerate(a),等学完自己补)25。 还有第三种场合:遍历时想提前退出(break)——直接迭代同样支持, 这条不构成选索引的理由。

23. 排序的完整能力:reverse 参数只是开始

sort() 的全貌比本章演示的大。已讲:sort() 升序、 sort(reverse=True) 降序10。待补(通用知识,不在书里): key 参数指定「按什么排」——

words = ['banana', 'kiwi', 'apple']
words.sort(key=len)            # 按长度排:['kiwi', 'banana', 'apple']
pairs = [('a', 2), ('b', 1)]
pairs.sort(key=lambda p: p[1]) # 按元组第 2 项排

key 的思路一句话:排序前先把每个元素映射成一个可比的键值, 排键值。len 把单词映射成整数、lambda 把元组映射成第二项。 这个功能把 sort 从「排数字」扩展到「按任意标准排任何对象」, 是列表通往「真数据处理」的通行证。

还有一条性质值得知道:Python 的排序是稳定的——键值相等的元素, 排序后保持原先后顺序。合并多个排序条件(先按成绩、同分按姓名) 靠「稳定 + 多次排序」或「元组键」实现,是数据处理的老把戏。

24. 全章练习的「数据故事」串讲

把散在全章的练习按一条业务线串起来,你会看到本书练习设计的全貌—— 它们其实在演同一个剧本:「经营一个水果摊」121517:

  第 1 幕(11.6.2)开摊:五种水果上架,append 进货 pear、
    remove 处理坏果 banana、insert 补位 blueberry
  第 2 幕(11.7.2)扩张:extend 批量进货 pineapple/strawberry、
    reverse 倒序陈列、sort 按字母码货、del 下架、
    + 与 += 开分店、sorted 出一份目录不扰货架
  第 3 幕(11.8.2)盘点:切片六道——前三热销[:3]、
    末二新到[-2:]、掐头去尾[1:-1]、隔一采样[::2]、
    倒序核对[::-1]、隔三抽检[1::3]

  图说:一个生意场景,二十个操作全覆盖——这就是「案例驱动」编排的功力。

练习不是散题,是连续剧。读原书时如果觉得练习重复,用这条线串着看, 重复感会消失——每道题都是同一世界里的新事件。

25. 双 for 与 zip 的再深入:顺序是语义

双 for 推导的输出顺序由 for 从句的顺序严格决定——这不是风格,是语义。 把题 7 的 [[a, b] for a in numbers1 for b in numbers2 if a+b < 10] 的输出按组拆开看1719:

  a = 1 时,b 扫 numbers2 全体:[1,6] [1,7] [1,8]   (9 不满足 a+b<10)
  a = 2 时:[2,6] [2,7]                            (2,8 和为 10 不满足)
  a = 3 时:[3,6]
  a = 4、5 时:b 最小 6,和已 ≥ 10,无输出

  图说:外层 a 是「慢变量」,每组内层 b 转完一圈 a 才进一格——
  和写两个嵌套 for 循环的顺序完全一致。两个 for 从句写反,
  输出行的排列顺序跟着反(内容集合不变,顺序不同)。

zip 则有一个「不等长」的行为值得知道:zip 在最短的输入耗尽时停止。 zip([1, 2, 3], ['a', 'b']) 只产出两对——第三元素被静默丢弃。 数据清洗时两列长度不齐,zip 会「帮」你截齐,这可能恰好掩盖了数据问题 (补充,来自通用知识)1926。

26. 推导式的性能观:为什么它不只是「短」

推导式比等价的 for 循环略快,原因很实际:推导式的循环在解释器内部 以专门的字节码(Python 预先编译出的中间形态)跑,循环体没有反复的 append 方法调用开销(通用知识, 不在书里)23。量级上,百万级元素的造表,推导式通常快两三成—— 不是数量级差异,但免费午餐没有拒绝的理由。

更重要的一点:推导式的产出永远是新列表。这在「改原列表」的场景里 既是保护也是开销:想原地改,还是得用索引遍历(§22)。 两个事实合起来就是选型口诀:造新表用推导式,改旧表用索引循环, 聚合一个值用累加器循环。

27. 列表 vs 字符串:同一张规则表,两种性格

列表和字符串共享整套序列机制(索引、切片、len、for、in),但性格相反—— 把对照表钉在墙上,两章的知识就焊在一起了:

维度字符串 str列表 list
内容只能是字符任意类型,可混装
可变性不可变:改一个字符要造新串可变:append/remove/sort 原地改
拼接+ 造新串+ 造新表;+=/extend 原地加长
反转切片 [::-1] 造新reverse() 原地 或 切片造新
排序没有 sort 方法sort() 原地 / sorted() 造新
复制语义不可变,复制问题不存在b = a 是别名!要 copy()
越界索引报错、切片宽容完全一样
遍历for 逐字符for 逐元素
从哪来字面量、input()、split()中括号、list()、推导式

一句话总结:字符串是「只读的字符列表」,列表是「可读写的万能序列」。 学了列表再回看字符串,会突然明白为什么字符串「改不了」是优点 (随便传、随便共享、无别名事故)——第 12 章的元组就是把这种优点 从字符推广到任意数据。

28. 元组预告:为什么「不可变」还要再发明一次

第 09 章多值返回交还的是元组,本章又处处见到它的影子(zip 产出元组、 组合练习 (i, j) 是元组)。元组 = 只读的列表,语法上圆括号替代方括号:

point = (10, 20)        # 元组:造好之后不能改
point[0] = 99           # TypeError:不可变

它的存在理由,列表章的体验已经替你预习了一半:不可变的东西可以安全地 到处传——函数返回坐标、字典的键、一组固定配置,都用元组,因为没人 (包括你自己)能中途把它改掉。完整的对照和取舍是第 12 章的内容, 这里只留一个意识:写代码时每次写列表前问一句「这东西以后会被改吗? 不会?那也许该是元组。」

30. 学习路径建议:本章怎么练才算会

这一章操作密度全书最高,光读不练等于没学。给一条经过本章全部机制的 最小练习路线,每站只用十分钟:

  ① 手造一个「本周购物清单」(字符串列表),append 加三项、
    remove 去掉一项、insert 插队一项——熟悉「改」的手感
  ② 对同一列表练六个切片:前三、末二、掐头去尾、隔一、反转、隔三——
    直到不查表就能写
  ③ 用推导式重做一遍:原表 → [大写版]、[长度小于 5 的项]、
    [长度的平方]——映射与筛选各一
  ④ 造两个等长列表(品名与价格),zip 配对后按位相乘求总价——聚合收尾
  ⑤ 最后做一道「事故题」:b = a;b.append(x);print(a)——
    亲手看别名事故,再用 copy() 修复

  图说:五站走完,造、取、切、改、推、zip、别名,本章机制全覆盖。

第 ⑤ 站最容易被跳过,也最值得做:别名事故不看一次现场, 将来在几百行的程序里踩到时,你会花十倍的时间找凶手。

31. 变量 → 列表 → 字典:容器概念的第一级阶梯

全书的数据容器到此已经登场两级,把它们的「分工」提前摆出来, 本章的定位会更清晰(字典是第 14 章,这里只展望):

容器查找方式类比本章里的角色
变量按名字贴着标签的盒子装不下「一批」
列表按位置(0 起编号)编号储物柜本章主角
字典按键(自定义名字)查号台第 14 章(预告)

列表的「按位置取」有个先天弱点:编号本身不带语义。student[1] 是 年龄还是学号?读代码的人不知道,写代码的人三个月后也悬——这就是为什么 记录型数据(每条有名字、年龄、成绩好几个字段)最终大多投奔字典: 键自带语义,位置不带。本章学生记录和订单记录用列表演示, 是教学顺序的安排,不是工程推荐——心里有这个谱,第 14 章会有一声 「原来如此」的顿悟。

32. 索引练习走查:list_practice 的八连问

11.4.2 的八个小任务是全章最朴素、也最能查漏的一段,值得完整走一遍 (素材 list_practice = [1, 3, 5, 7, 9])6:

  ① [0]           → 1     第 1 个(0 起)
  ② [1]           → 3     第 2 个——「第 N 个」的索引永远是 N-1
  ③ [2]           → 5     第 3 个
  ④ [len-1]       → 9     用公式取末位(笨写法,走一遍知道为什么笨)
  ⑤ [-1]          → 9     负索引取末位(地道写法,与 ④ 对照)
  ⑥ [-len]        → 1     负索引取首位:-5 号就是 0 号
  ⑦ [6]           → IndexError     正越界(len=5,合法 0–4)
  ⑧ [-6]          → IndexError     负越界(最小 -5)

  图说:八问把「正向、负向、越界」三个方向全部走通,再配 §15 的切片十二式,
  取数这关就彻底过了。

② 和 ④ 是两处新人必停的地方:「第 2 个」不是 [2] 而是 [1]; 「最后一个」不是 [len] 而是 [len-1] 或 [-1]。这两处停下来 想明白,比往后翻十页都值钱。

34. 遍历练习四题走查:累加与计数的方阵

11.5.2 的四道题(numbers = [1, 2, 2, 2, 3, 4, 4, 5])是聚合模式的 完整方阵——两个维度交叉:求和还是计数 × 无条件还是带筛选8:

  ① 求和(无条件):total = 0;逢数累加 → 0+1+2+2+2+3+4+4+5 = 23
  ② 计数(带筛选):逢 4 加一 → 数列里 4 出现 2 次
  ③ 计数(带筛选):逢偶数加一 → 2,2,2,4,4 共 5 个
  (④ 求和(带筛选)书里没出,补上 mentally:逢偶累加 → 2+2+2+4+4 = 14)

  图说:四格方阵缺一格——好练习的设计往往留白一格让你自己补。

① 和 ②③ 的结构差一行(有没有 if),但骨架同一副: 初始化 → 遍历 → 条件?→ 更新 → 循环后使用。这副骨架在第 08 章 成绩册里首次亮相(min/max 擂台是它的孪生),在数据分析里是铁打的主力—— 任何「统计一批数」的需求都是它的变体。把 total 换成 count、 把 += i 换成 += 1,求和与计数就互为变体;把 if 的条件换掉, 筛选就换题。一道题练熟,一类题通吃。

35. 自组合问题:全序对的完整输出

11.10.1 的 fruits 自组合例([(f1, f2) for f1 in fruits for f2 in fruits if f1 != f2]) 是「双 for + 不等条件」的标准形态,六个元组按外圈 f1 分组输出18:

  f1 = 'apple':  ('apple','banana') ('apple','orange')
  f1 = 'banana': ('banana','apple') ('banana','orange')
  f1 = 'orange': ('orange','apple') ('orange','banana')

  图说:3 元素 → 3×3 = 9 个候选,去掉 3 个对角(f1 == f2),剩 6 个。
  这是「排列数」n×(n−1) 的活样本——组合数学的第一课用一行推导式演完。

把 if 条件去掉就是 9 个全对(含自身);把 if f1 != f2 换成 if f1 < f2(字符串比大小,按字母序)就只剩 3 对——条件槽里 换一个字,组合的定义就换一种。这类「穷举 + 条件」的推导式, 是面试手写题和日常数据配对的双料工具。

36. list() 构造器的三个用法层级

list() 在本章的出场贯穿始终,三个用法由浅入深排开5:

  第 1 级  list()              → []            造空表(同 [])
  第 2 级  list('abc')         → ['a','b','c'] 序列转列表(逐元素)
  第 3 级  list(range(5))      → [0,1,2,3,4]   转化 range(常见组合)

  图说:第 2、3 级都是「可迭代 → 列表」,规则统一:逐个取、挨个装。

「可迭代 → 列表」是通用规则,后面还会见到 list(元组)(第 12 章)、 list(集合)(第 13 章)、list(字典的键)(第 14 章)—— 构造函数同一个,只是原料不同。记住这个通用性,四个章节的知识 自动连成一条线:list() 是「任何序列转列表」的万能转换器。

与它配对的一个高频组合是 list(range(...)):range 本身是 「惰性」的数列工厂(不占内存地知道怎么数),套一层 list 才变成 看得见摸得着的列表。书里 for 循环直接吃 range(不需要 list), 只有当「要把数列当数据存起来、传出去」时才需要这层转换。

37. 列表操作的「读—改」分离纪律

把全章方法按「只读」和「会改」两栏分开,用法纪律自然浮现:

只读(安全,随便用)会改原列表(小心别名)
len(a)、a[i]、切片 a[:]append/extend/insert
sorted(a)、a.count(x)remove/pop/del/clear
a.index(x)、x in asort()/reverse()、a += b
推导式(永远造新)a[i] = 新值

纪律一句话:只读操作可以随便用在共享的列表上;会改的操作动手前 问自己「还有没有别人指着它?」——有,先 copy。 这张表也是调试清单:列表内容莫名变了,从上表右栏找嫌疑人, 再查所有别名(谁还持有这个列表的引用)。

39. 本章常见错误速查总表

把全章的事故集中成一张「急诊表」,撞墙时先查它(案例是我们的归纳, 机制依据本章与前章语法)27:

症状病因处方
IndexError: list index out of range索引越界(含 [len(a)] 这种多一错误)合法区间 0..len-1;末位用 -1
改了 b,a 跟着变b = a 起别名b = a.copy() 或 a[:]
TypeError: ... not iterable对非序列用 for/sorted/extend先确认对方是序列(list/str/tuple)
ValueError: list.remove(x): x not in listremove 的值不存在先 if x in a: 再 remove
TypeError: can only concatenate list to list+ 两侧类型不同(如列表+字符串)同第 05 章:先统一类型
append 了一个列表,结果多一层嵌套该用 extend 用了 append见 §19 进料三兄弟
sort() 之后原顺序丢了sort 是原地操作要原顺序用 sorted()
join 列表报 TypeError列表里有非字符串元素先 [str(x) for x in ...]
空表取位 IndexError空表无合法索引取位前先 if my_list:

九行里,前三行(越界、别名、类型)占了新手事故的八成。尤其第二行(别名)——它不会报错, 没有红字提醒你,只在数据莫名其妙变样时现身,排查成本最高, 值得把 §10 的演示亲手敲一遍。

40. 作者的判断与证据

最后记录一个本章的「元判断」——关于书本身:把列表排在四种容器之首, 且用最大篇幅(24 页)讲它,是这个教材最稳的编排决策。列表是后续一切 的通用基座:元组的「不可变」要靠对比列表才能讲清,集合的「去重」 要靠对比列表的「允许重复」才能讲清,字典的「按键取」要靠对比列表的 「按位取」才能讲清。读后续三章时,不断回来翻本章的对照表(§27、 §31),是最高效的路径。

判断(我们的,不是书里的): 如果只从本章带一样东西出门, 带「原地还是造新」这一个问题。它不只是 sort/sorted 的区别, 它是所有容器操作的第一问——答错了,轻则数据被改,重则调试半天。 其余一切(方法名、参数顺序)都可以查表,唯独这个问题必须内化成反射。 如果错,会错在: 对函数式编程背景的读者,「尽量造新、永不原地改」 是另一种根深蒂固的纪律——两种纪律各有拥趸,本章的 Python 惯例 (原地为主、造新为辅)是社区主流,不是唯一正解。

判断(我们的,不是书里的): 「列表是通用基座」这句编排评价 有一个可检验的推论:若一本书把集合或字典排在列表前面讲, 读者会不断被迫回头补列表的课——序列机制(索引/切片/遍历) 不先立住,后三种容器都是空中楼阁。本库后续的拆解若遇到 「数据结构」主题的书,可以用这条推论预判其章节顺序的合理性。

判断(我们的,不是书里的): 本章四个机制的重要度并不均匀—— 切片和推导式是「天天用」的核心生产力,别名陷阱是「一次不懂、 处处中招」的认知地雷,而 sort 的 reverse 参数、extend 与 append 的 差别这类细节,用到再查表就行,不值得预先背。学习的力气按 「切片 > 别名 > 推导式 > 其余」分配,其余部分浏览一遍、留印象即可。 如果错,会错在: 对要走算法竞赛/面试路线的读者,复杂度分析 (insert 是 O(n) 这类)的优先级会大幅提前——那是另一条赛道的要求, 本书目标(数据处理入门)下我们的排序成立。

判断(我们的,不是书里的): 「异质列表」这个 Python 特色是双刃剑。 书里展示了它的便利(学生记录一例),但工程实践中,同构列表 (元素全同类型)几乎总是更好:遍历处理时不用每步判断类型, 出错的表面积大减。混装记录请用字典或专门的类(第 14 章的方向), 列表留给「一批同类的东西」——这是对书里示范的温和修正。 如果错,会错在: 小规模一次性脚本里,异质列表的便利是真实的, 严格分型的收益不明显——修正的价值随代码规模和生命周期增长。

  • 「索引迭代不如直接迭代」的推荐、sort/sorted 的原地性对比、 推导式相对循环的简洁性——这些都是 Python 社区的通行共识, 原书的表述与官方文档的口吻一致71116。
  • 「列表存引用、异质由此而来」的机制解释(11.3.3)是本章最有深度的 一段——它把第 03 章的引用模型与列表的混装能力连成了一条线3。 这是「为什么这么设计」类问题在本书中少见的正面回答。
  • 章末总结表(Table 11.1)按「创建/访问/切片/追加/扩展/插入/删除/ 排序/翻转/推导式」十行收拢全章,还补了正文没讲的 pop()—— 表比正文宽的老毛病又出现了,我们补在边界节21。
  • GenAI 互动区的最后一问是「想复制列表结果改了原列表」20—— 这是列表章被问得最多的真问题,正文偏偏没讲,见下面的边界节。

41. 边界与局限

先把最常被问的三件事置顶:① b = a 是别名不是复制(§10);② sort() 会改原列表、sorted() 不会(§6);③ 切片越界不报错、索引越界报错(§7)。三件之外,本章的边界有一个统一的主题:列表功能最全,但「最全」不等于「处处最优」。 每条局限都是在说「某个具体场景下,另一种容器或工具更好」——这不是 列表的失败,是生态的分工。带着「什么时候不该用列表」的意识学完本章, 比带着「列表万能」的意识出去,要安全得多。

  • 别名陷阱(aliasing)——本章最大的留白,必须补讲。 列表是可变的, 赋值传的是引用:b = a 之后,a 和 b 是同一个列表的两个名字, 改 b 等于改 a。想真复制,用 b = a.copy() 或 b = a[:](切片拷贝)。 GenAI 区问的「不小心改了原列表」多半就是这个20。字符串没有这个问题 (不可变,改不了),所以这是字符串老兵转列表时第一个要重学的直觉。
  • 列表与 for-else、while-else 一样,属于「正文讲一半」的家族。 本章每个方法都给了演示,但没有一篇「什么时候用什么容器」的总索引—— 那个总索引要等第 14 章的字典讲完才轮得到(四种容器齐备才好比)。 如果等不及,记住一条粗判据:先想怎么取(按位?按键?按存在性?), 取法决定容器。
  • pop() 只在总结表出现。 my_list.pop() 弹出(取出并删除)末尾 元素并返回它;pop(1) 弹指定位置。它是「取走」而非「删掉」—— 栈(后进先出)结构就靠它和 append 配对实现21。
  • 列表的内存与扩容机制没讲。 列表「可伸缩」的代价是底层数组超额 分配(预留空位),append 均摊 O(1)、insert 中间 O(n)——这种 「复杂度」视角属于算法课,本书正确地漏掉了,但 insert(0, x) 在大列表上慢这个事实值得凭直觉记住:中间插队,后面全挪。
  • index()、count()、in 只在总结表或 GenAI 区出现。 my_list.index(x) 找 x 的位置(找不到报 ValueError); my_list.count(x) 数出现次数;x in my_list 判断在不在。 配套记忆:remove 按值删、del 按位删、pop 按位取,三兄弟各司其职。
  • 嵌套列表的「浅拷贝」问题。 copy() 只拷第一层——内层列表还是 共享的。matrix.copy() 之后改内层,原 matrix 跟着变。深拷贝要 copy.deepcopy()(需 import copy)。入门阶段知道「嵌套的拷贝是浅的」 即可,踩到时再深挖。
  • 没有讲元组/列表的选择判据。 为什么有时要用不可变的元组(第 12 章)? 列表的「可变」是特性也是风险(被别处改掉)——这正是下一章的存在理由, 我们在 §28 给了预告。
  • 没有讲数组(numpy)。 数据分析真正的大规模数值运算用的是 numpy 的 数组——它长得像列表、行为更像数学向量(整体乘一个数、逐元素相加), 性能高一到两个数量级。本书的列表是通用容器,numpy 是数值专科; 走到数据分析,两者会接力。知道有这层就好,第 01 章点名的 NumPy 就是这个。
  • 列表推导式没有讲条件表达式(三元式)。 表达式槽里还能写 x if x > 0 else -x(单行的 if-else)——与 if 筛选槽不同, 它做「变换」不做「筛选」。本书没出现,用到时查「ternary conditional expression」。

42. 可带走的

本章是全书操作密度之最,带走按「认知层、操作层、心法层」三层装盒。 建议合上书,只看这二十条能否逐条想起一个代码例子——想不起来的那条, 回到对应小节重练,不要重读。

认知层:

  1. 列表 = 有序 + 异质 + 可变的元素序列;存的是引用,所以什么都能装13。
  2. len() 数第一层;嵌套列表整体算一个元素4。
  3. 索引、负索引、越界 IndexError——与字符串完全同规6。
  4. 遍历优先直接迭代;需要位置时才用 range(len(...))7。
  5. append 装整体、extend 倒元素、remove 按值、del 按位910。
  6. sort() 原地返回 None;sorted() 造新列表原样保留——按需取用11。
  7. 切片六式:前三 [:3]、末二 [-2:]、掐头去尾 [1:-1]、隔一 [::2]、 反转 [::-1]、定位步长 [1::3]15。
  8. 推导式三槽:表达式(变成什么)for(原料)if(筛选)16。
  9. 双 for 穷举组合,zip 按位对齐——两种「多序列」推导的分工1819。
  10. b = a 是起别名不是复制;复制用 a.copy() 或 a[:]20。 嵌套列表的 copy 是浅的,独立要 deepcopy。

操作层:

  1. 「第 N 个」的索引是 N-1;最后一个用 -1,别用 len(那是越界)6。
  2. 进料三兄弟:append 装箱、extend 倒货、insert 插队;+= 同 extend9。
  3. 取数三兄弟:remove 按值、del 按位删、pop 按位取;index 按值找位22。
  4. sort 原地(返回 None)、sorted 造新;反转同理——reverse() 原地、 [::-1] 切片造新11。
  5. 改元素或要位置 → 索引循环;只读或造新表 → 直接迭代或推导式725。

心法层:

  1. 推导式三槽背成肌肉记忆:表达式(变成什么)for(原料)if(筛)16。
  2. 双 for 穷举组合、zip 按位对齐;推导式是纯造表,聚合归循环19。
  3. 别名事故是列表第一坑,亲手复现一次才算真懂(§30 练习路线)20。
  4. 「切片宽容、索引严格」:切片越界给空表,索引越界报错14。
  5. 列表按位置取、字典按键取(预告):记录型数据最终投奔字典(§31)。

附一张「一分钟自查卡」:能否不看资料写出——空表造法、取末位、 复制一个列表、按值删除、排序且保留原表、反转的两种写法、 「平方的偶数」推导式?七问全对,本章毕业;有卡顿的,回到对应小节, 十分钟后重测。

再附一条「本章之后的复习触发器」:学完第 12、13、14 章后, 回到 §27 的两张对照表(列表 vs 字符串、容器阶梯)各重读一遍—— 新容器的每一项差异,都会在那两张表里点亮一格。

43. 原文地图

使用说明:本表的「搜『…』」短语全部逐字取自原书转码文本(pdftotext 产物,行号 = 清洗文本的自然段号), 用 node scripts/book-verify.mjs bitesize-python-beginners 可逐条回核; 行号若因重新转码漂移,以短语搜索为准。

主题原书章原文位置
列表的定义与用途CHAPTER 11: Listtext/20-ch11-chapter-11-list.txt:13(搜「versatile and dynamic data structure」) · text/20-ch11-chapter-11-list.txt:26(搜「storing and processing sequential data」)
两种创建方式同上text/20-ch11-chapter-11-list.txt:41(搜「list() # Create an empty list」) · text/20-ch11-chapter-11-list.txt:53(搜「my_list_3 = [1, 3, 2, 4, 6]」)
异质与嵌套、len 数一层同上text/20-ch11-chapter-11-list.txt:145(搜「my_list_6 = [1, 'Hello'」) · text/20-ch11-chapter-11-list.txt:156(搜「it is still counted as one」)
异质的机制解释(引用)同上text/20-ch11-chapter-11-list.txt:200(搜「associating variables with references」)
0 起索引、越界、负索引同上text/20-ch11-chapter-11-list.txt:214(搜「starts indexing from 0」) · text/20-ch11-chapter-11-list.txt:242(搜「list index out of range」) · text/20-ch11-chapter-11-list.txt:266(搜「fruits = ['apple', 'banana'」)
直接迭代 vs 索引迭代同上text/20-ch11-chapter-11-list.txt:346(搜「sequential processing of each element」) · text/20-ch11-chapter-11-list.txt:364(搜「for i in range(len(numbers))」)
遍历练习(累加与计数)同上text/20-ch11-chapter-11-list.txt:386(搜「Calculate the sum in numbers」) · text/20-ch11-chapter-11-list.txt:393(搜「Count how many 4」)
append/remove/insert同上text/20-ch11-chapter-11-list.txt:411(搜「most commonly used functions」) · text/20-ch11-chapter-11-list.txt:425(搜「my_list.append(6)」)
extend/reverse/sort/clear/del同上text/20-ch11-chapter-11-list.txt:466(搜「extend() function」) · text/20-ch11-chapter-11-list.txt:485(搜「my_list.reverse()」) · text/20-ch11-chapter-11-list.txt:497(搜「reverse = True」) · text/20-ch11-chapter-11-list.txt:510(搜「del my_list[2]」) · text/20-ch11-chapter-11-list.txt:515(搜「my_list.clear()」)
+ 拼接与 sorted() 非破坏排序同上text/20-ch11-chapter-11-list.txt:521(搜「Concatenating two lists」) · text/20-ch11-chapter-11-list.txt:528(搜「leaving the original list unchanged」)
水果列表操作链同上text/20-ch11-chapter-11-list.txt:443(搜「Define a list of fruits」) · text/20-ch11-chapter-11-list.txt:551(搜「extend(['pineapple'」) · text/20-ch11-chapter-11-list.txt:628(搜「sorted(fruits)」)
切片语法与正负混用同上text/20-ch11-chapter-11-list.txt:653(搜「extraction of a subset」) · text/20-ch11-chapter-11-list.txt:664(搜「numbers = [0, 1, 2, 3」) · text/20-ch11-chapter-11-list.txt:711(搜「letters = ['a', 'b'」)
切片练习六式同上text/20-ch11-chapter-11-list.txt:742(搜「Extract the first three fruits」)
推导式语法与三槽同上text/20-ch11-chapter-11-list.txt:785(搜「concise and elegant way」) · text/20-ch11-chapter-11-list.txt:793(搜「[expression for item in iterable」)
推导式练习(变换与筛选)同上text/20-ch11-chapter-11-list.txt:858(搜「power_of_2 = [x**2」) · text/20-ch11-chapter-11-list.txt:872(搜「greater_than_7」)
嵌套推导与组合同上text/20-ch11-chapter-11-list.txt:909(搜「matrix = [[1, 2, 3]」) · text/20-ch11-chapter-11-list.txt:933(搜「combinations = [(i, j)」)
zip 并排迭代同上text/20-ch11-chapter-11-list.txt:949(搜「zip() function」) · text/20-ch11-chapter-11-list.txt:980(搜「zip(numbers1, numbers2)」)
总结表(含 pop)同上text/20-ch11-chapter-11-list.txt:1002(搜「Summary of Python lists」)
GenAI 区关于改原列表的一问同上text/20-ch11-chapter-11-list.txt:1059(搜「modifying the original list when creating a copy」)

Footnotes

  1. 出处:「CHAPTER 11: List」第 13–25 段(text/20-ch11-chapter-11-list.txt:13,搜「versatile and dynamic data structure」;:16,搜「ordered collections」;:22,搜「efficient random access」)。「有序、异质、可变」的三定语概括依据原文各段。 ↩ ↩2 ↩3

  2. 出处:「CHAPTER 11: List」第 127–144 段(text/20-ch11-chapter-11-list.txt:134,搜「student record might include」;:150,搜「student = ['David Wood', 18, 3.9, True]」)。学生记录例逐字依据原文。 ↩ ↩2 ↩3

  3. 出处:「CHAPTER 11: List」第 196–208 段(text/20-ch11-chapter-11-list.txt:196,搜「Test your understanding」;:201,搜「references (or addresses)」;:206,搜「heterogeneity of Python lists arises」)。异质的引用机制解释逐字依据原文 11.3.3 的自测答案。 ↩ ↩2 ↩3

  4. 出处:「CHAPTER 11: List」第 155–166 段(text/20-ch11-chapter-11-list.txt:156,搜「it is still counted as one」;:159,搜「output 5」)。len 数第一层的规则与两个例子见原文。 ↩ ↩2 ↩3 ↩4

  5. 出处:「CHAPTER 11: List」第 41–95 段(text/20-ch11-chapter-11-list.txt:46,搜「Create a list from a string」;:51,搜「direct assignment」)。五种创建与 len 输出(0/12/5/2/4)逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  6. 出处:「CHAPTER 11: List」第 211–299 段(text/20-ch11-chapter-11-list.txt:212,搜「done using indices」;:214,搜「starts indexing from 0」;:216,搜「R, which starts indexing from 1」;:242,搜「list index out of range」;:269,搜「fruits[-1]」)。语言对比与全部输出见原文。 ↩ ↩2 ↩3 ↩4 ↩5

  7. 出处:「CHAPTER 11: List」第 345–377 段(text/20-ch11-chapter-11-list.txt:346,搜「sequential processing of each element」;:364,搜「for i in range(len(numbers))」;:368,搜「for num in numbers」)。两种迭代与「直接迭代更优」的推荐逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  8. 出处:「CHAPTER 11: List」第 379–406 段(text/20-ch11-chapter-11-list.txt:386,搜「Calculate the sum in numbers」;:393,搜「Count how many 4」;:400,搜「number of even integers」)。求和与两种计数的练习代码见原文。 ↩ ↩2 ↩3

  9. 出处:「CHAPTER 11: List」第 408–440 段(text/20-ch11-chapter-11-list.txt:411,搜「most commonly used functions」;:425,搜「my_list.append(6)」;:431,搜「my_list.remove(3)」;:437,搜「my_list.insert(2, 10)」)。三操作的输出链逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5

  10. 出处:「CHAPTER 11: List」第 462–518 段(text/20-ch11-chapter-11-list.txt:466,搜「extend() function」;:479,搜「my_list.extend(additional_list)」;:485,搜「my_list.reverse()」;:491,搜「my_list.sort()」;:497,搜「reverse = True」;:510,搜「del my_list[2]」;:515,搜「my_list.clear()」)。全部输出([1,2,3,4,5,5,6]、[6,5,5,4,3,2,1] 等)逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  11. 出处:「CHAPTER 11: List」第 520–546 段(text/20-ch11-chapter-11-list.txt:521,搜「Concatenating two lists」;:528,搜「leaving the original list unchanged」;:534,搜「list1 + list2」;:541,搜「sorted(unsorted_list)」)。+ 拼接与 sorted 的非破坏性对照见原文。 ↩ ↩2 ↩3 ↩4 ↩5

  12. 出处:「CHAPTER 11: List」第 442–648 段(text/20-ch11-chapter-11-list.txt:448,搜「fruits.append('pear')」;:459,搜「insert(2, 'blueberry')」;:551,搜「extend(['pineapple'」;:587,搜「del fruits[1]」;:628,搜「sorted(fruits)」)。水果操作链的各步输出逐字依据原文;「十二连」的时间线整理与「原地 vs 造新」的归纳是我们的加工。 ↩ ↩2

  13. 出处:「CHAPTER 11: List」第 650–705 段(text/20-ch11-chapter-11-list.txt:653,搜「extraction of a subset」;:664,搜「numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]」)。切片语法与八例输出([2,3,4,5,6]、[0,2,4,6,8]、[9,8,…,0] 等)逐字依据原文。 ↩ ↩2 ↩3

  14. 出处:「CHAPTER 11: List」第 707–736 段(text/20-ch11-chapter-11-list.txt:711,搜「letters = ['a', 'b', 'c', 'd'」;:714,搜「letters[2:-3]」;:718,搜「letters[-5:7]」;:726,搜「letters[-7:3]」;:734,搜「letters[-3::-1]」)。正负混用五例及其输出(含空表)逐字依据原文。 ↩ ↩2 ↩3 ↩4

  15. 出处:「CHAPTER 11: List」第 738–780 段(text/20-ch11-chapter-11-list.txt:744,搜「fruits[:3]」;:750,搜「fruits[-2:]」;:756,搜「fruits[1:-1]」;:761,搜「fruits[::2]」;:772,搜「fruits[::-1]」;:778,搜「fruits[1::3]」)。六式练习的输出逐字依据原文。 ↩ ↩2 ↩3

  16. 出处:「CHAPTER 11: List」第 782–851 段(text/20-ch11-chapter-11-list.txt:785,搜「concise and elegant way」;:793,搜「[expression for item in iterable」;:802,搜「[num for num in range(6)]」;:824,搜「if num % 2 == 0」;:846,搜「num ** 2 for num in numbers」)。推导式语法与三组循环对照见原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  17. 出处:「CHAPTER 11: List」第 852–896 段(text/20-ch11-chapter-11-list.txt:858,搜「power_of_2 = [x**2」;:865,搜「negative_numbers = [-x」;:872,搜「greater_than_7」;:879,搜「even_squares」;:893,搜「odd_multiples_of_3」)。五道练习的输出([1,4,9,16,25]、[]、[4,16] 等)逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5

  18. 出处:「CHAPTER 11: List」第 897–947 段(text/20-ch11-chapter-11-list.txt:909,搜「matrix = [[1, 2, 3], [4, 5, 6]」;:912,搜「for sublist in matrix for num in sublist」;:933,搜「combinations = [(i, j)」;:943,搜「for f1 in fruits for f2 in fruits」)。压平、两表组合与自组合三例的输出逐字依据原文。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  19. 出处:「CHAPTER 11: List」第 949–998 段(text/20-ch11-chapter-11-list.txt:949,搜「zip() function」;:960,搜「zip(names, ages)」;:980,搜「zip(numbers1, numbers2)」;:986,搜「a * b for a, b in zip」)。zip 的定义、组合例与 sums/products 输出逐字依据原文;「穷举 vs 对齐」的分工归纳是我们的提炼。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  20. 出处:「CHAPTER 11: List」第 1055–1060 段(text/20-ch11-chapter-11-list.txt:1059,搜「modifying the original list when creating a copy」)。「改原列表」之问出自 GenAI 互动区,正文未答;别名机制、copy()/[:] 解法与浅拷贝边界是我们的补充(依据通用知识)。 ↩ ↩2 ↩3 ↩4 ↩5

  21. 出处:「CHAPTER 11: List」第 1000–1032 段(text/20-ch11-chapter-11-list.txt:1002,搜「Summary of Python lists」;:1023,搜「pop(index=-1)」)。总结表十行与 pop 行见原文。 ↩ ↩2 ↩3

  22. 补充(不在书里,来自通用知识):index/count/in 的用法与「按值 vs 按位」的对照依据 Python 官方文档;原书仅在 Table 11.1 的 GenAI 互动区间接涉及。 ↩ ↩2

  23. 「推导式适合纯造表、循环适合有副作用的流程」是我们的经验判据;推导式相对循环的简洁性本身见原文第 784–796 段(text/20-ch11-chapter-11-list.txt:787,搜「eliminating the need for explicit loop」)。 ↩ ↩2

  24. 补充(不在书里,来自通用知识):四种越界事故的报错措辞依据 Python 解释器;「先 len 检查再取位」的防御习惯与 truthiness 的关联是我们的建议(空列表为 False 是通用知识)。 ↩

  25. 补充(不在书里,来自通用知识):「直接迭代改不动元素」的机制依据 Python 的赋值语义(循环变量是元素引用/值的局部绑定);enumerate 是官方文档推荐的地道写法。 ↩ ↩2

  26. 补充(不在书里,来自通用知识):「zip 以最短的输入耗尽而停止」的行为依据 Python 官方文档;「静默截齐可能掩盖数据问题」是我们的提醒。 ↩

  27. 补充(不在书里,来自通用知识):九种报错的措辞依据 Python 解释器的实际报错;「前三行占八成」的频次判断是我们的经验归纳。 ↩

On this page

列表 — 数据的集装箱1. 这一章在全书链条里的位置2. 顶层全景:一个列表的六种操作3. 核心原理一:列表是什么、能装什么4. 核心原理二:按位取 — 索引与负索引5. 核心原理三:遍历 — 两种写法,一种地道6. 核心原理四:增删改 — 列表是可变的主线例子:一个水果列表的十二连操作7. 核心原理五:切片 — 字符串规则整包搬来8. 核心原理六:列表推导式 — 一行造列表10. 别名陷阱专节:为什么 b = a 不是复制11. 方法速查总表(原书 Table 11.1 全表+扩编)12. 嵌套列表与矩阵:列表的列表13. 推导式与循环的互译对照14. 三大遍历模式:映射、筛选、聚合15. 切片大走查:一张字母表讲透全部组合16. 推导式练习全走查:七道题的花名册17. 创建与混装练习走查18. 列表 × 函数:第 09 章的零件上岗19. append vs extend 的完整对照20. 字符串 ↔ 列表:两种互转的完整通道21. 索引事故博物馆22. 什么时候必须用索引遍历23. 排序的完整能力:reverse 参数只是开始24. 全章练习的「数据故事」串讲25. 双 for 与 zip 的再深入:顺序是语义26. 推导式的性能观:为什么它不只是「短」27. 列表 vs 字符串:同一张规则表,两种性格28. 元组预告:为什么「不可变」还要再发明一次30. 学习路径建议:本章怎么练才算会31. 变量 → 列表 → 字典:容器概念的第一级阶梯32. 索引练习走查:list_practice 的八连问34. 遍历练习四题走查:累加与计数的方阵35. 自组合问题:全序对的完整输出36. list() 构造器的三个用法层级37. 列表操作的「读—改」分离纪律39. 本章常见错误速查总表40. 作者的判断与证据41. 边界与局限42. 可带走的43. 原文地图Footnotes