编程语言研究书库

列表 — 主力数据结构,和它被滥用的方式

列表 — 主力数据结构,和它被滥用的方式

这一章讲 list:Python 里最重要的数据结构。 但原书的讲法有个鲜明的特点—— 每教一个操作,作者都同时告诉你什么时候不该用它:不该按下标取元素、不该切片、 不该在列表里混装不同类型的东西。读完你会拿到一套「列表使用哲学」,而不只是方法清单。

1. 这一章讲什么

原书开宗明义:列表(在其他语言里叫别的名字)是软件开发的驮马(workhorse)—— 它让你把多个对象存进一个变量,这「可能是最重要的数据结构」。[^1]

本章的隐含主线问题:一个列表装的是什么,决定了你对它能做什么、该做什么。 如果列表装的是「一组性质相同的对象」,那么遍历整个列表、对每个元素做同一件事,就是唯一健康的用法; 任何「挑其中一个出来特殊对待」的代码,都是信号:要么数据放错了容器,要么结构该改。

2. 顶层全景:列表的一生

  创建        使用                  收束
  []          遍历(for x in xs)     排序(sort/sorted)
  [a, b]      变换(列表推导式)      合并(+、append、zip)
  .append()   取元素(作者反对 [])
              切片(作者反对 [1:3])
  图说:上半行是作者推荐的,下半行是「能但不该」——本章的篇幅分配也是这个比例。

主线例子用到底:一份水果清单 ["apple", "pear", "banana"],每一步操作都落在它身上。[^2]

3. 核心原理

3.1 创建与填充:append 与 pop

解决什么问题: 手头有一组同类对象,要一个容器按先来后到存着。

怎么做:

empty_list = []            # 空列表
empty_list.append("hello") # 末尾加一个 → ['hello']
my_list = ["hello", "world"]  # 或直接初始化
my_list.pop()              # 删末尾并返回它 → 返回 'world',列表变回 ['hello']

append 和 pop 是一对:一个从末尾加,一个从末尾删并取出(这种后进先出的用法, 就是数据结构课里的「栈」;书里没点名,知道这个对应即可)。列表能装多少元素,只受内存限制。[^3]

一个立刻要立的规矩:别在列表里混装类型。 ["Apple", 5] 合法,但「你会很难找到这个对象的名字—— 这就是数据结构有缺陷的明显信号」。不同类型属性的组合,该用类(第 11 章),不该塞进一个列表。[^4]

3.2 列表推导式:一行造列表

解决什么问题: 「对 0 到 4 的每个数算平方,收集成列表」这种变换,用循环写要四五行,还有临时变量。

怎么做: Python 的招牌语法——列表推导式(list comprehension):

[i**2 for i in range(5)]
# [0, 1, 4, 9, 16]

走一遍: range(5) 依次给出 0–4,每个存进 i;i**2 算出平方;所有结果收集成一个新列表。[^5]

它还能带过滤器——只留满足条件的:

fruits = ["apple", "pear", "banana"]
[fruit for fruit in fruits if fruit != "pear"]   # ['apple', 'banana']
[i for i in range(10) if i % 2 == 0]             # [0, 2, 4, 6, 8] 偶数

为什么有效、边界在哪: 它把「遍历 + 变换 + 收集」压成一句,消掉了临时变量(还记得第 02 章吗: 变量越少,读者要记的东西越少)。但作者立刻警告:它能写得非常紧凑,也能写得非常难读—— 书里给了一个「过长」的反面例子(一行里挤进遍历、替换、去空格、两个条件),结论是: 「代码长一点但可读,永远好过短而难懂。」推导式超过「一个变换 + 一个条件」就该拆成普通循环。[^6]

3.3 遍历:首选 for x in xs

解决什么问题: 对列表每个元素做同一件事。

怎么做: 主线例子走一遍:[^2]

fruits = ["apple", "pear", "banana"]
for fruit in fruits:
    print(fruit)
# apple / pear / banana

for fruit in fruits 一次取一个元素进 fruit——不需要下标、不需要长度、空列表时自然什么都不做。 作者贴心地备注:编程里不讲究英文语法,fruits 存一堆 fruit,复数对单数,够用就好(可读性优先于语法正确)。[^7]

需要下标时,用 enumerate,不要自己数:

for i, fruit in enumerate(fruits):
    print(i, fruit)
# 0 apple / 1 pear / 2 banana

enumerate 一边遍历一边给出序号(从 0 开始)。对比「不推荐」的写法 for i in range(len(fruits)): print(fruits[i])——书里把这种下标循环称为「iterator」用法(这其实是书里的误称,Python 的 iterator 另有所指;这里就当它说「下标迭代」), 评价是「混乱且易错,最好别用」。[^8]

为什么有效: 下标是间接层——你要的不是数字 i,是第 i 个元素;多一层间接,就多一层写错的空间 (差一位、越界,都是经典 bug)。for x in xs 把这层间接删了。书里配了一段 C++ 旧式写法的对照 (手动下标、以及更糟的 iterator 类型),说明「这种访问方式曾经到处都用,幸好已经过去了」。[^9]

3.4 按下标取元素与切片:能不用就不用

解决什么问题: 偶尔你确实需要「第几个」或「中间一段」。

怎么做(以及作者的反对票):

fruits[1] = "orange"     # 改第二个元素 → ['apple', 'orange', 'banana']
numbers = [1, 2, 3, 4, 5]
numbers[1:3]             # 切片:取索引 1 到 3 之前 → [2, 3]

作者对这两个操作的立场是本章最强的:「别用 [] 运算符。 如果你非要用,说明你的代码很可能有缺陷,该修。」 理由是第 1 节的哲学:按下标取一个元素,等于「从列表里随机挑一个特殊对待」—— 这违背「所有元素一视同仁」,而违背它的代码,测试要逐个情况覆盖,复杂度失控。[^10]

切片(slicing,[1:3] 这种取一段)同样「看起来酷,但应该少用;它是代码不够优的信号」。 如果非用不可,作者的条件是:配足单元测试,防差一位错误。[^11]

切片的几个技巧,书里承认「有用」但我们按作者口径只作了解:负数从尾部数([-1] 是最后一个)、 缺省首尾([:-1] 去掉最后一个)、第三个参数跳步([::2] 隔一个取一个)、[::-1] 整个倒序—— 顺带的彩蛋是用 word == word[::-1] 判断回文(正读反读相同的词,如 anna)。[^12]

判断(我们的,不是书里的): 「禁用下标」作为铁律太硬,但作为默认很对。 数据处理里 xs[0] 取第一个、解包失败时兜底,都是正当用法;真正该删的是裸数字下标满地跑的代码 (每出现一个 xs[3],读者都要回去数第四位是什么)。判据:下标是不是「有名字的常量」 (比如 FIRST = 0),不是就该重构。 如果错,会错在: 数值算法(本章的矩阵、第 18 章的数值积分)本质上就是在跟下标打交道; 那一类代码按下标工作是本职,「禁用」不适用——作者自己在矩阵例子里也用了 matrix[0][1]。

3.5 嵌套列表与矩阵

列表里装列表,就是二维结构——矩阵(matrix,排成行和列的数表):

matrix = [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]
matrix[0]      # [1, 2, 3] —— 第一行
matrix[0][1]   # 2 —— 第一行第二个
[row[0] for row in matrix]   # [1, 4, 7] —— 每行第一个,推导式直接做列切片

作者对嵌套的意见跟对切片一致:维度一多就乱,矩阵运算请交给 NumPy(第 09 章)—— 「出于性能原因强烈建议」。嵌套的列表推导式能快速变成天书,书里的替代是拆成普通循环写。[^13]

3.6 排序:sort 还是 sorted,这是个契约问题

解决什么问题: 把列表按大小排好。

怎么做:

a = [3, 1, 2]
a.sort()                  # 就地改:a 变成 [1, 2, 3]
a_sorted = sorted(a)      # 不动原列表,返回一个排好的新列表
a.sort(reverse=True)      # 降序

区别表面很小,选择上作者明确站 sorted:「它给我一个新列表、一个新名字。」 回想第 02、03 章:不复用变量、不让数据在你看不见的地方被改——sort 正好违反这两条, 只有「你就是故意要改原列表」时才用它。[^14]

附带两个彩蛋:sorted 不限于列表——字符串、dict 都能排,总是返回列表 (sorted("hello") → ['e','h','l','l','o'];对 dict 排的是它的键)。[^15]

3.7 合并:加号、逐个 append、zip

怎么做:

a = [1, 2, 3]; b = [4, 5, 6]
a + b                          # [1,2,3,4,5,6] —— 造一个新列表
for element in b: a.append(element)   # 把 b 的元素逐个人肉搬进 a
list(zip(a, b))                # [(1,4),(2,5),(3,6)] —— 两两配对成 tuple 列表

zip 把两个列表的对应位置配成一对,返回可转成列表的 zip 对象。 但作者的警告照旧:「合并列表的时候,你多半该停下来想想——危险在于合并出一个元素不同质的列表。 不同的东西,保持分开。」[^16]

3.8 字符串是「只读的列表」:一个跨章节的桥

书里插了一个对照框:字符串大致就是一个字符的列表,所以列表的操作它很多都有—— for letter in "Hello" 可以逐个字符遍历,"Hello"[1] 能取下标。[^13b] 关键差别在第 03 章已经立过:字符串不可变,word[1] = "a" 直接报 TypeError: 'str' object does not support item assignment——想「改」某个字符,只能造新字符串。[^13c]

这个对应关系值钱的地方在于:学完列表,字符串的大部分直觉你已经有了; 第 05 章讲字符串时,可以直接从「它和列表的异同」切入,不用从零学。

3.9 学不完的 API 怎么办

本章最后,作者对「列表有几百个函数,学不完」给了全书方法论的一句总结: 读文档是老派做法;更实用的是用 Google 搜「我想对列表做什么」,练会 Python 的词汇; 再现代一点,问 AI 工具——「它们能帮到你,但小心,它们也会把你带偏。」 唯一不可替代的是:自己动手练。[^17]

4. 书里还有那些操作:逐个走一遍

第 3 节按「哲学」组织,这一节把书里的操作示例逐个走一遍——每段代码旁边都有具体的数, 方便你在 REPL 里照着敲。

4.1 切片四技巧,各配一个具体输入

主线例子换数字列表 [1, 2, 3, 4, 5],四个技巧各走一遍:[^12]

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

list[1:3]      # [2, 3]    —— 基本款:取索引 1 到 3 之前
list[-2:]      # [4, 5]    —— 负数从尾部数:-2 是倒数第二个
list[:-1]      # [1, 2, 3, 4] —— 起止可以省略:不写起点 = 从头,不写终点 = 到尾
list[::2]      # [1, 3, 5]  —— 第三个参数是步长:每隔一个取一个
list[::-1]     # [5, 4, 3, 2, 1] —— 负步长 = 倒着走:整个翻转

边界规则一句话:左闭右开——[1:3] 含索引 1、不含索引 3,所以得 [2, 3]。 这条规则造成的「差一位」错误,正是作者要求切片配测试的原因。[^11]

回文判断是作者唯一主动夸过的切片用法:

word = "anna"
word == word[::-1]     # True —— 正读和反读相同

「这段代码看着确实酷。但老实说,你该尽量少用切片——它是坏代码的信号。」作者的原话里, 夸和贬隔着一行,这就是他全章的基调。[^12]

4.2 多维列表:一个矩阵的完整一生

书里给了一个 3×3 矩阵的完整操作链,走一遍:[^13]

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

matrix[0]       # [1, 2, 3] —— 第一个元素是一整行(一个列表)
matrix[0][1]    # 2 —— 第一行的第二个:先取行,再取列,两层 []

# 排成方阵的样子写,更易读(Python 不在乎,人在乎):
matrix = [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]

# 列表推导式直接做「取每行第一个」的列操作:
[row[0] for row in matrix]     # [1, 4, 7]

三层嵌套的推导式书里也演示了,但随即给出两个更平的替代(普通循环版),结论一样: 「能读性优先,嵌套推导式很快变得令人困惑。」[^13]

一个警告顺带记下:各行列数不同(参差不齐的矩阵)「大概率说明你的代码哪里怪,该重想」—— 方阵之外的结构,通常该用别的东西(第 09 章的 NumPy 或第 11 章的类)。

4.3 排序与合并的输出,逐个对账

a = [3, 1, 2]
a.sort();            a      # [1, 2, 3] —— 就地改
a.sort(reverse=True); a      # [3, 2, 1] —— 降序

b = [3, 1, 2]
b_sorted = sorted(b)
b            # [3, 1, 2] —— 原列表没动
b_sorted     # [1, 2, 3] —— 新列表

sorted 还能排别的东西,书里给了两个彩蛋:对字符串返回字符列表 sorted("hello") → ['e','h','l','l','o']; 对 dict 返回排好序的键列表 sorted({1:"2", 4:"5", 3:"11"}) → [1, 3, 4]。[^15]

合并的三种写法,输出相同、形状不同:[^16]

a = [1, 2, 3]; b = [4, 5, 6]
a + b                                  # [1,2,3,4,5,6] —— 新列表
for element in b: a.append(element)    # a 自己变长
list(zip(a, b))                        # [(1,4),(2,5),(3,6)] —— 配对成 tuple

zip 的要点:它返回的不是列表,是一个「配对迭代器」,要 list(...) 包一层才看得见内容; 配对在短列表结束处停止(长度不等时,多余的被丢掉——这是补充,书里没强调)。

4.4 enumerate 与「i 的没落」

fruits = ["apple", "pear", "banana"]
for (i, fruit) in enumerate(fruits):      # 括号可省:for i, fruit in ...
    print(i, fruit)
# 0 apple / 1 pear / 2 banana

对比作者不推荐的下标版 for i in range(len(fruits)): print(i, fruits[i]): 两条代码的输出一模一样,但下标版多了一层「先取数字、再拿数字换元素」的间接—— i 有机会越界、有机会差一位,而 enumerate 版没有这个机会。 书里的历史注脚:C++ 当年连下标都嫌高级,还有更糟的手写 iterator 写法(类型名长到一行的那种), 「幸好那些日子过去了」。[^9]

4.5 本章的三个加框小注,一并收拢

原书在列表一节插了三个加框,各占一行话:

  • Old-School Arrays:C++ 当年用 new/delete 手工申请释放内存,「非常脆弱、极易出错」; 如今连 C++ 都有了接近 Python 列表的 vector——手工内存管理的年代「一去不复返了」。[^18]
  • English Grammar in Programming:for fruit in fruits 里 fruit/fruits 单复数对不上语法没关系—— 编程的目标是代码好懂,语法正确性优先级很低。(for person in people 这种对不上的,照写。)[^7]
  • Strings vs. Lists:字符串近似「只读的字符列表」,遍历、下标都通,唯独不能改单个字符 word[1] = "a" → TypeError: 'str' object does not support item assignment。[^13c]

三个框合起来是一句话:列表的直觉大量可迁移,但「可变性」这条界线不可迁移。

4.6 「别用 []」的正面教材:什么时候遍历是对的

回到第 3.3 节的哲学,给它配一个完整对比例子。[^10]

fruits = ["apple", "pear", "banana"]
for fruit in fruits:        # 每个元素都被同样对待
    print(fruit)
# apple / pear / banana

这段代码里没有任何下标——因为没有元素需要被特殊对待。 而一旦写下 fruits[1] = "orange",你就是在说:「第二个元素和其他的不一样,我要亲手动它。」 作者的判断:这行代码本身就说明数据结构有问题——

「这段代码违背了『列表所有元素应被一视同仁』的原则——你只是从列表里随机挑了一个。 所以你应该一般性地避免 [] 运算符。如果非用不可,你的代码很可能有缺陷,该修。」[^10]

修的方向在上一章已经给过:要么换容器(不同属性的组合该进 dict 或类),要么换数据形状 (让元素们重新变得同质),而不是给下标操作配更多注释。

4.7 列表推导式的过滤器,两个完整走查

第 3.2 节给了语法,这里把书里的两个例子走到底。[^6]

走查一:从水果清单里剔除梨。

fruits = ["apple", "pear", "banana"]
no_pear = [fruit for fruit in fruits if fruit != "pear"]
# 拆开读:for 把 fruits 里的元素依次放进 fruit;
#        if 检查「fruit 不是 pear」,通过的才留下;
#        留下的 fruit 收集成新列表
no_pear     # ["apple", "banana"]

走查二:0 到 9 里的偶数。条件 i % 2 == 0 用取余:i 除以 2 的余数为 0 就是偶数(第 03 章的 ///% 在此复用)。

[i for i in range(10) if i % 2 == 0]     # [0, 2, 4, 6, 8]
[i**2 for i in range(10)]                # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] —— 不带过滤的平方版

书里的「过长反面例」值得原样看一眼,建立尺度感:

files = [file.stem.replace(" ", " ").strip() for file in
         p.iterdir() if file.is_file() and file.suffix is ".py"]

一行里塞了:遍历目录、替换、去空格、两个条件。「这样的推导式能写,但不该写—— 宁可长一点、可读,不要短而难懂。」[^6]

4.8 「学不完」的正确姿势

原章末尾的那张方法论,展开成可执行的三步:

  1. 先练词汇,再查文档。 「对列表做 X」的 X 用 Python 的哪个词说?append、slice、filter…… 词汇来自亲手敲,不来自阅读。
  2. 搜索优先于文档。 文档是全集,问题是个子集;带着「我想做什么」去搜,命中率远高于翻目录。 「这需要练,但值得。」[^17]
  3. AI 工具可用但要验。 「它们能帮你,也会带你走偏」——带偏的方式是给出看似合理但跑不起来的代码, 对策只有一个:任何 AI 给的代码,自己跑一遍再信(这也是第 01 章「测试是唯一检查」的日常版)。

4.9 创建与填充,从空列表到成品

把第 3.1 节的 API 扩成一次完整的「制造过程」:[^3]

empty_list = []
empty_list          # [] —— 空列表本身没啥用,但它是起点

empty_list.append("hello")
empty_list          # ["hello"] —— 加了一个

my_list = ["hello", "world"]   # 或者直接初始化:方括号 + 逗号分隔
my_list             # ["hello", "world"]

my_list.pop()       # 返回 "world",my_list 变回 ["hello"]

pop 的完整语义:删除末尾元素并把它还给你。所以 my_list.pop() 单独一行时, 元素被删了但没人接,白拿了——通常写作 last = my_list.pop(),既删了末尾又拿到了值。 append/pop 这一对配合,就是数据结构课里「栈」(后进先出)的完整实现, 只是 Python 没给它起这个名字。[^3]

再补一个「混装反例」的正面对照。书里用 ["Apple", 5] 展示混装有多难命名—— 试试给它起个名字:fruit_and_count?item?怎么起都别扭。 而同质列表起名毫不费力:fruits、prices、ages——一个名字起得顺不顺,就是数据结构对不对的试纸。[^4]

4.10 嵌套推导式的三个版本,复杂度肉眼可见

书里用一道题对比了三个版本:造一个「每行长度递增」的锯齿二维列表 [[], [0]], [[0], [0, 0]], [[0, 0], [0, 0, 0]]。[^13]

# 版本一:双层列表推导式(一行,但 reader 哭了)
[[[0] * (i + j) for i in range(2)] for j in range(3)]

# 版本二:外层循环 + 内层推导式(推荐)
matrix = []
for j in range(3):
    sub_list = [[0] * (i + j) for i in range(2)]
    matrix.append(sub_list)

# 版本三:全循环(最直白)
matrix = []
for j in range(3):
    sub_list = []
    for i in range(2):
        sub_list.append([0] * (i + j))
        matrix.append(sub_list)

三个版本输出相同;作者的裁决:版本二最好——「简单的列表推导式是这个 case 的最佳解」。 判据和前面一脉相承:推导式的合法长度,以「读的人不皱眉」为界。

4.11 「数字母」式直觉:列表是序列的通用形态

把本章收在一个更大的图景上:字符串、tuple、range、文件逐行——它们都不是列表,但都能被 for x in xs 遍历、被 sorted 排序、被 in 查询。 列表是这种「序列」形态里唯一可变的、最通用的一个;学会它,等于同时学会半个 Python 的数据面。 这也是为什么作者把列表放在所有数据结构最前面讲:它不是「四选一」之一,是其他几个的参照物。

4.12 主线例子,最后一次完整回放

把全章的操作串在同一份数据上,一次看完:

  fruits = ["apple", "pear", "banana"]
      │
      ├─ 遍历     for fruit in fruits:        → 每个元素被同样对待
      ├─ 带序号   enumerate(fruits)           → (0,"apple") (1,"pear") (2,"banana")
      ├─ 变换     [f.upper() for f in fruits] → ["APPLE","PEAR","BANANA"](变换规则为你所写)
      ├─ 过滤     [f for f in fruits if f != "pear"] → ["apple","banana"]
      ├─ 排序     sorted(fruits)              → ["apple","banana","pear"] 新列表
      └─ 配对     list(zip(fruits, [3,2,5]))  → [("apple",3),("pear",2),("banana",5)]
  图说:主线例子到此走完;从创建到输出的每一步,全章没有一个 [] 下标。
  (upper() 把字符串转成全大写,是第 05 章字符串操作的预告;zip 的配对在短列表结束处停止。)

这份回放同时是本章的「自测题」:遮住右列,只看左列的操作名,能写出输出形状(不必精确到值),
说明本章已经读透;写不出的那一行,回到对应小节重走一遍例子即可。

最后交代一句本章的边界:列表能装的元素数量只受内存限制——书里明说「上限就是你计算机的内存空间」,这不是修辞,是机制:列表是动态数组,不够就自动扩,扩到操作系统不肯给为止。这也是为什么「列表里塞什么」比「列表能塞多少」重要得多——容量永远不是瓶颈,元素不同质才是。

### 4.13 与 NumPy 的分工预告

本章所有「二维以上」的例子,作者在正文里都指向了同一个出口:**矩阵运算请交给 NumPy**。
理由在第 09 章展开(性能),这里先记住分工边界——纯 Python 的嵌套列表适合「装结构」
(棋盘、表格、树的孩子列表,第 19 章的渗流模拟就是),不适合「算数值」。
一到乘法、求逆、滤波这类数值动作,就换 NumPy 的数组——那是它的主场,而且快上几个数量级。


## 5. 作者的判断与证据

**书里给了证据的:** 每个操作的代码示例和输出都可直接运行;`enumerate` 与下标循环的对照、
`sort` 与 `sorted` 的对照,都是「跑一次就明白」的演示。[^8][^14]

**作者的判断(立场性主张):**

- 「别用 []、少用切片」——立场最强的部分;他的依据是「特殊对待 = 复杂度」,逻辑自洽,但如我们的判断块所说,数值代码里要放松;
- 「列表里只装同类对象」——判断+经验,普遍认同;
- 「AI 工具会带你偏」的提醒——2026 年的朴素说法,我们补充:这不是 Python 的问题,是概率性生成工具的固有属性。

## 6. 边界与局限

- 本章没讲 **insert/remove/index 等常用方法**,也没讲 `del`——作者的方法论是「用到再查」,不是全表;
- 浅复制与深复制的区别在本章仍是隐含的(第 03 章提过 `copy()`);
- 生成器表达式、`map`/`filter` 没出现——书里认定列表推导式覆盖日常所需;
- 性能(Big-O)层面一句没提:list 尾部 append 快、中间插入慢这些,书里零涉及。

## 7. 可带走的

**本章主线例子,一行写完:** `fruits = ["apple", "pear", "banana"]` → `for fruit in fruits` 一视同仁遍历、
需要序号就 `enumerate`、要变换就 `[... for ... if ...]`、要排序用 `sorted` 拿新列表——
全程不碰下标。[^2]

1. **列表装同类对象**;混装类型是数据结构选错的信号,该用类。
2. **遍历首选 `for x in xs`**;需要序号用 `enumerate`,别用 `range(len(xs))`。
3. **下标和切片默认不用**;非用不可时配足测试。回文判断 `word == word[::-1]` 是少数作者也认的妙用。
4. **列表推导式 = 一个变换 + 一个过滤为限**;更长就拆成循环。
5. **`sorted` 优于 `sort`**(新列表 vs 暗中改原列表),除非你故意要改。
6. **合并前三思**:加号造新列表、zip 配对,但合并往往意味着该重新审视数据结构。
7. **矩阵交给 NumPy**,别用嵌套列表硬扛。
8. **API 学不完是正常的**:练会词汇 → 会搜 → 会问;但必须自己写。

再补一条书里夹在正文中的旧闻:作者在「Old-School Arrays」小框里顺带解释,数组(array)是 C++ 时代的概念,
那时要用 `new` 和 `delete` 手工申请和释放内存,「非常脆弱、极易出错」;如今连 C++ 都有了接近 Python 列表的 vector——
那套手工内存管理的年代,「一去不复返了」。[^18]

## 8. 原文地图

| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 列表是驮马 | Lists | `text/13-fm-lists.txt:37`(搜「workhorses」) |
| 混装类型的警告 | Lists | `text/13-fm-lists.txt:47`(搜「Apple”, 5」) |
| append/pop | Lists | `text/13-fm-lists.txt:61`(搜「append(”hello”)」) · `text/13-fm-lists.txt:82`(搜「pop」) |
| 列表推导式与过滤器 | Lists | `text/13-fm-lists.txt:90`(搜「i**2 for i」) · `text/13-fm-lists.txt:101`(搜「no_pear」) |
| 过长的推导式反面例 | Lists | `text/13-fm-lists.txt:116`(搜「a little too long」) |
| for x in xs 遍历、语法备注 | Lists | `text/13-fm-lists.txt:126`(搜「for fruit in fruits」) · `text/13-fm-lists.txt:133`(搜「English Grammar」) |
| enumerate 与下标循环之贬 | Lists | `text/13-fm-lists.txt:144`(搜「enumerate」) · `text/13-fm-lists.txt:155`(搜「not as recommended」) |
| C++ 旧式迭代对照 | Lists | `text/13-fm-lists.txt:170`(搜「Old-School」) |
| 「别用 []」的主张 | Lists | `text/13-fm-lists.txt:193`(搜「use the [] operator」) |
| 切片与技巧、回文 | Lists | `text/13-fm-lists.txt:211`(搜「slicing」) · `text/13-fm-lists.txt:242`(搜「anna」) |
| 嵌套列表、NumPy 建议 | Lists | `text/13-fm-lists.txt:262`(搜「Multidimensional」) · `text/13-fm-lists.txt:272`(搜「highly advisable」) |
| sort / sorted 的选择 | Lists | `text/13-fm-lists.txt:327`(搜「ascending order」) · `text/13-fm-lists.txt:348`(搜「prefer sorted」) |
| 合并与 zip、合并警告 | Lists | `text/13-fm-lists.txt:361`(搜「Merging Lists」) · `text/13-fm-lists.txt:388`(搜「rethink」) |
| 「学会自己去查」方法论 | Strings(本章收尾段) | `text/14-fm-strings.txt:1`(搜「hundreds of functions」) |

[^1]: 出处:「Lists」第 37–40 段(`text/13-fm-lists.txt:37`,搜「workhorses」)。
[^2]: 出处:「Lists」第 125–131 段(`text/13-fm-lists.txt:126`,搜「for fruit in fruits」)。
[^3]: 出处:「Lists」第 56–85 段(`text/13-fm-lists.txt:61`,搜「append」;第 80 段,搜「pop」)。
[^4]: 出处:「Lists」第 43–55 段(`text/13-fm-lists.txt:49`,搜「avoid doing this」)。
[^5]: 出处:「Lists」第 87–96 段(`text/13-fm-lists.txt:88`,搜「list comprehension」)。
[^6]: 出处:「Lists」第 113–118 段(`text/13-fm-lists.txt:114`,搜「difficult to read」;第 116 段,搜「too long」)。
[^7]: 出处:「Lists」第 133–138 段(`text/13-fm-lists.txt:133`,搜「English Grammar」)。
[^8]: 出处:「Lists」第 142–167 段(`text/13-fm-lists.txt:144`,搜「enumerate」;第 165 段,搜「iterator is simply a number」)。
[^9]: 出处:「Lists」第 169–186 段(`text/13-fm-lists.txt:170`,搜「Old-School」)。
[^10]: 出处:「Lists」第 188–203 段(`text/13-fm-lists.txt:190`,搜「starts at 0」;第 199 段,搜「simply pick an element」)。
[^11]: 出处:「Lists」第 204–215 段(`text/13-fm-lists.txt:212`,搜「sign of suboptimal code」)。
[^12]: 出处:「Lists」第 216–248 段(`text/13-fm-lists.txt:218`,搜「-2」;第 242 段,搜「anna」)。
[^13]: 出处:「Lists」第 262–322 段(`text/13-fm-lists.txt:264`,搜「nested」;第 272 段,搜「NumPy」;第 302 段,搜「quickly becomes confusing」)。
[^14]: 出处:「Lists」第 324–350 段(`text/13-fm-lists.txt:348`,搜「prefer sorted」)。
[^15]: 出处:「Lists」第 351–359 段(`text/13-fm-lists.txt:353`,搜「sorted(”hello”)」)。
[^16]: 出处:「Lists」第 361–389 段(`text/13-fm-lists.txt:385`,搜「zip」;第 388 段,搜「rethink」)。
[^17]: 出处:「Strings」开头(`text/14-fm-strings.txt:1`,搜「hundreds of functions」;第 6 段,搜「practice by yourself」)。此段在文件中位于 Lists 章收尾处,内容是列表的延续讨论。

[^13b]: 出处:「Lists」第 251–255 段(`text/13-fm-lists.txt:252`,搜「list of characters」)。
[^13c]: 出处:「Lists」第 256–260 段(`text/13-fm-lists.txt:260`,搜「item assignment」)。报错原文:`TypeError: 'str' object does not support item assignment`——字符串不可变,想改只能造新串。

[^18]: 出处:「Lists」第 73–78 段(「Old-School Arrays」框,`text/13-fm-lists.txt:75`,搜「new and delete」;「those days are over」见第 77 段)。

On this page