列表 — 数据的集装箱
列表 — 数据的集装箱
列表是 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. 创建与混装练习走查
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]) # 19sum() 内建函数就是这个函数的官方版;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 的完整对照
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 a | sort()/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 list | remove 的值不存在 | 先 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. 可带走的
本章是全书操作密度之最,带走按「认知层、操作层、心法层」三层装盒。 建议合上书,只看这二十条能否逐条想起一个代码例子——想不起来的那条, 回到对应小节重练,不要重读。
认知层:
- 列表 = 有序 + 异质 + 可变的元素序列;存的是引用,所以什么都能装13。
len()数第一层;嵌套列表整体算一个元素4。- 索引、负索引、越界 IndexError——与字符串完全同规6。
- 遍历优先直接迭代;需要位置时才用
range(len(...))7。 append装整体、extend倒元素、remove按值、del按位910。sort()原地返回 None;sorted()造新列表原样保留——按需取用11。- 切片六式:前三
[:3]、末二[-2:]、掐头去尾[1:-1]、隔一[::2]、 反转[::-1]、定位步长[1::3]15。 - 推导式三槽:表达式(变成什么)for(原料)if(筛选)16。
- 双 for 穷举组合,zip 按位对齐——两种「多序列」推导的分工1819。
b = a是起别名不是复制;复制用a.copy()或a[:]20。 嵌套列表的 copy 是浅的,独立要 deepcopy。
操作层:
- 「第 N 个」的索引是 N-1;最后一个用 -1,别用 len(那是越界)6。
- 进料三兄弟:append 装箱、extend 倒货、insert 插队;
+=同 extend9。 - 取数三兄弟:remove 按值、del 按位删、pop 按位取;index 按值找位22。
- sort 原地(返回 None)、sorted 造新;反转同理——reverse() 原地、
[::-1]切片造新11。 - 改元素或要位置 → 索引循环;只读或造新表 → 直接迭代或推导式725。
心法层:
- 推导式三槽背成肌肉记忆:表达式(变成什么)for(原料)if(筛)16。
- 双 for 穷举组合、zip 按位对齐;推导式是纯造表,聚合归循环19。
- 别名事故是列表第一坑,亲手复现一次才算真懂(§30 练习路线)20。
- 「切片宽容、索引严格」:切片越界给空表,索引越界报错14。
- 列表按位置取、字典按键取(预告):记录型数据最终投奔字典(§31)。
附一张「一分钟自查卡」:能否不看资料写出——空表造法、取末位、 复制一个列表、按值删除、排序且保留原表、反转的两种写法、 「平方的偶数」推导式?七问全对,本章毕业;有卡顿的,回到对应小节, 十分钟后重测。
再附一条「本章之后的复习触发器」:学完第 12、13、14 章后, 回到 §27 的两张对照表(列表 vs 字符串、容器阶梯)各重读一遍—— 新容器的每一项差异,都会在那两张表里点亮一格。
43. 原文地图
使用说明:本表的「搜『…』」短语全部逐字取自原书转码文本(pdftotext 产物,行号 = 清洗文本的自然段号),
用 node scripts/book-verify.mjs bitesize-python-beginners 可逐条回核;
行号若因重新转码漂移,以短语搜索为准。
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 列表的定义与用途 | CHAPTER 11: List | text/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
-
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「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 -
出处:「CHAPTER 11: List」第 1000–1032 段(
text/20-ch11-chapter-11-list.txt:1002,搜「Summary of Python lists」;:1023,搜「pop(index=-1)」)。总结表十行与 pop 行见原文。 ↩ ↩2 ↩3 -
补充(不在书里,来自通用知识):index/count/in 的用法与「按值 vs 按位」的对照依据 Python 官方文档;原书仅在 Table 11.1 的 GenAI 互动区间接涉及。 ↩ ↩2
-
「推导式适合纯造表、循环适合有副作用的流程」是我们的经验判据;推导式相对循环的简洁性本身见原文第 784–796 段(
text/20-ch11-chapter-11-list.txt:787,搜「eliminating the need for explicit loop」)。 ↩ ↩2 -
补充(不在书里,来自通用知识):四种越界事故的报错措辞依据 Python 解释器;「先 len 检查再取位」的防御习惯与 truthiness 的关联是我们的建议(空列表为 False 是通用知识)。 ↩
-
补充(不在书里,来自通用知识):「直接迭代改不动元素」的机制依据 Python 的赋值语义(循环变量是元素引用/值的局部绑定);enumerate 是官方文档推荐的地道写法。 ↩ ↩2
-
补充(不在书里,来自通用知识):「zip 以最短的输入耗尽而停止」的行为依据 Python 官方文档;「静默截齐可能掩盖数据问题」是我们的提醒。 ↩
-
补充(不在书里,来自通用知识):九种报错的措辞依据 Python 解释器的实际报错;「前三行占八成」的频次判断是我们的经验归纳。 ↩