二、列表与元组(P11–P22)
P11 · 知识点:列表特性
【题目】关于列表(list)的说法,错误的是?
A. 列表是可变的 B. 列表可以嵌套(元素可以是列表) C. 列表支持索引和切片 D. 列表中的所有元素必须是同一类型
答案:D
【考点】列表是可变、有序、可异构的序列。
【结论】选 D。列表是可变、有序、可嵌套的序列,其元素类型不必统一,可以任意混搭。
【逐项辨析】
- A. 列表是可变的——正确。列表支持 append、pop、insert、del 等操作,属于可变对象(mutable)。
- B. 列表可以嵌套(元素可以是列表)——正确。列表元素可以是任意 Python 对象,包括另一个列表,如 [1, [2, 3]]。
- C. 列表支持索引和切片——正确。列表是有序序列,支持正向/负向索引以及 [start:stop:step] 切片。
- D. 列表中的所有元素必须是同一类型——错误。列表是“异构”容器,元素类型可以混搭,如 [1, "a", True, [2]];这是它与 C 语言数组的本质区别。
【知识点】列表(list)是 Python 中最常用的内置序列类型之一,具备四大核心特征:
- 可变(Mutable):创建后可通过 append、extend、insert、pop、remove、sort、reverse 等方法修改其内容,也可通过索引赋值直接修改元素。
- 有序(Ordered):元素按插入顺序排列,每个元素都有固定的位置索引(从 0 开始),支持切片访问子序列。
- 可嵌套(Nestable):元素可以是任意对象,包括另一个列表、字典、自定义类的实例等,从而构造出多维数据结构。
- 异构(Heterogeneous):元素类型不要求一致,同一个列表中可以同时存放整数、字符串、布尔值、None、对象引用等。
与 C 语言数组的对比:C 数组在声明时即确定元素类型和长度,内存连续且类型单一;Python 列表底层通过动态数组(over-allocated array)存储指向 PyObject 的指针,因此类型灵活、长度动态。
【记忆锚点】“变序嵌杂”——可变、有序、可嵌、杂类型。或者记口诀:“列表像个筐,啥都往里装;能增又能删,顺序不会乱。”
【易混对比】
| 特性 | 列表 list | 元组 tuple | C 数组 |
|---|---|---|---|
| 可变 | 是 | 否 | 固定长度,元素可改 |
| 有序 | 是 | 是 | 是 |
| 元素类型 | 可混搭 | 可混搭 | 必须一致 |
| 可哈希 | 否 | 是(元素均可哈希时) | — |
| 嵌套 | 支持 | 支持 | 支持(多维数组) |
换问法:以下哪个不是列表的特性?(答案:元素必须是同一类型。)
【自测】
L = [1, "hello", 3.14, [2, 3]]
print(type(L[3])) # 输出什么?答:输出
<class 'list'>,因为 L[3] 是嵌套列表 [2, 3]。 与第 P18 题连考。
【知识关联】
- 同库关联:与 P12–P17 列表题群、P22(引用拷贝)、J10/J32(Java 数组/ArrayList)。
- 实现层:CPython list 是动态数组(过度分配);O(1) 均摊 append;中间插入 O(n)。
- 面试追问:① list 与 tuple 选型?② 底层为何不是链表?
【拓展延伸】
- 变式问法:嵌套列表浅拷贝陷阱。
- 版本差异:语义稳定。
- 工程注意点:大数据考虑 array/numpy;避免在列表头频繁 insert(0)。
P12 · 知识点:切片
【题目】L = [0, 1, 2, 3, 4, 5]; L[1:5:2] 的结果是? A. [1, 3] B. [1, 3, 5] C. [1, 2, 3, 4] D. [1, 3, 4]
答案:A
【考点】切片三参数 [start:stop:step]:含 start、不含 stop、按步长取。
【结论】选 A。切片 L[1:5:2] 从索引 1 开始,到索引 5(不含)结束,步长为 2,取索引 1 和 3 对应的元素。
【推导过程】
- 原列表索引映射:
值: 0 1 2 3 4 5
索引:0 1 2 3 4 5- start = 1 → 从索引 1(值 1)开始;
- stop = 5 → 到索引 5(值 5)之前结束,即索引 5 本身不被包含;
- step = 2 → 每隔 2 个索引取一个:
- 第 1 个:索引 1(值 1)
- 第 2 个:索引 1 + 2 = 3(值 3)
- 第 3 个:索引 3 + 2 = 5,已等于 stop,超出边界,停止。
- 结果:[1, 3]。
【逐项辨析】
- A. [1, 3]——正确。索引 1 和 3 均在 [1, 5) 范围内,且步长为 2。
- B. [1, 3, 5]——错误。索引 5 是 stop 边界,切片“含头不含尾”,不会取到。
- C. [1, 2, 3, 4]——错误。这是 L[1:5](步长默认 1)的结果,忽略了 step=2。
- D. [1, 3, 4]——错误。步长为 2 时不会取到索引 4。
【知识点】Python 切片(slice)是序列类型(list、tuple、str)的核心访问方式,语法为 seq[start:stop:step],三个参数均可省略:
- start:起始索引(包含),默认为 0;若为负数则从末尾倒数。
- stop:结束索引(不包含),默认为序列长度;若为负数则从末尾倒数。
- step:步长,默认为 1;为负值时表示逆向切片。
关键规则:
- 含头不含尾:stop 是硬边界,永远不会被包含。
- 不抛边界异常:start 或 stop 超出有效范围时,Python 会自动截断到合法边界,不会报错。
- 空切片:当 start 与 stop 交叉且 step 方向不匹配时,返回空列表。
- 复制与逆序:
L[:]是浅拷贝;L[::-1]是逆序拷贝。
【记忆锚点】“切西瓜不切刀”——start 是下刀处(含),stop 是刀背处(不含)。或者记:“左闭右开,步长跳着来。”
【易混对比】
| 表达式 | 结果 | 说明 |
|---|---|---|
| L[1:5] | [1, 2, 3, 4] | 默认 step=1 |
| L[1:5:2] | [1, 3] | step=2,隔一个取 |
| L[::2] | [0, 2, 4] | 从头取到尾,步长 2 |
| L[::-1] | [5, 4, 3, 2, 1, 0] | 逆序全取 |
| L[5:1:-1] | [5, 4, 3, 2] | 逆序,含头不含尾 |
换问法:L[::2] 与 L[1::2] 的结果分别是?(答案:[0, 2, 4] 和 [1, 3, 5]。)
【自测】
L = [0, 1, 2, 3, 4, 5, 6]
print(L[-3::1]) # 输出?
print(L[-3::-1]) # 输出?答:
L[-3::1]输出[4, 5, 6],从倒数第 3 个元素到末尾;L[-3::-1]输出[4, 3, 2, 1, 0],从倒数第 3 个元素逆序到开头。 与第 P13 题连考。
【知识关联】
- 同库关联:与 P13(负索引)、P16(反转切片);与 Java 无原生切片对照。
- 实现层:切片创建新列表(浅拷贝);步长可为负;赋值切片可变长度。
- 面试追问:①
L[1:10:2]含义?②L[::-1]是否新对象?
【拓展延伸】
- 变式问法:
L[:]拷贝;L[2:2]=[9]插入。 - 版本差异:语义稳定。
- 工程注意点:大列表全切片拷贝耗内存;注意别名。
P13 · 知识点:负索引
【题目】L = [0, 1, 2, 3, 4]; L[-2:] 的结果是?
A. [2, 3] B. [3, 4] C. [4] D. [2, 3, 4]
答案:B
【考点】负索引从尾部计数(-1 是最后一个元素)。
【结论】选 B。负索引 -2 对应倒数第二个元素(值 3),切片到末尾得到 [3, 4]。
【推导过程】
- 原列表索引双映射:
正向索引: 0 1 2 3 4
值: 0 1 2 3 4
负向索引: -5 -4 -3 -2 -1- L[-2:] 中 start = -2,stop 省略(默认到末尾),step 省略(默认 1);
- -2 对应正向索引 3(值 3);
- 从索引 3 开始顺序取到末尾:索引 3(值 3)、索引 4(值 4);
- 结果:[3, 4]。
【逐项辨析】
- A. [2, 3]——错误。这是 L[-3:-1] 或 L[2:4] 的结果。
- B. [3, 4]——正确。-2 对应索引 3,切片到末尾含 3、4。
- C. [4]——错误。这是 L[-1:] 或 [L[-1]] 的结果。
- D. [2, 3, 4]——错误。这是 L[-3:] 的结果。
【知识点】Python 支持双向索引系统:
- 正向索引从 0 开始,到 len(seq)-1 结束。
- 负向索引从 -1 开始(最后一个元素),到 -len(seq) 结束(第一个元素)。
负索引的本质:Python 在内部会将负索引转换为 len(seq) + index。例如 L[-2] 等价于 L[len(L)-2] = L[3]。
负索引与切片结合是非常高效的尾部操作写法:
- L[-1]:取最后一个元素。
- L[-2:]:取最后两个元素。
- L[:-1]:去掉最后一个元素(常用于路径分隔、字符串处理)。
- L[::-1]:逆序(经典面试题)。
【记忆锚点】“负一倒数第一,负二倒数第二;冒号右边空,一直取到终。”
【易混对比】
| 表达式 | 结果 | 实际含义 |
|---|---|---|
| L[-2] | 3 | 单个元素(倒数第 2 个) |
| L[-2:] | [3, 4] | 从倒数第 2 个取到末尾 |
| L[:-2] | [0, 1, 2] | 从头到倒数第 2 个之前 |
| L[::-2] | [4, 2, 0] | 逆序隔一个取 |
| L[-2:-1] | [3] | 含头不含尾,只取倒数第 2 个 |
换问法:L[:-2] 与 L[-2:] 的结果分别是?(答案:[0, 1, 2] 与 [3, 4]。)
【自测】
L = [10, 20, 30, 40, 50]
print(L[-4:-1]) # 输出?
print(L[-1:-4:-1]) # 输出?答:
L[-4:-1]输出[20, 30, 40](正向,含头不含尾);L[-1:-4:-1]输出[50, 40, 30](负向步长,从 -1 到 -4 之前)。 与第 P12 题连考。
【知识关联】
- 同库关联:与 P12;负索引 -1 为最后一个。
- 实现层:内部加 len 规范化;越界 IndexError。
- 面试追问:①
L[-10]对 len=3?② 循环负索引?
【拓展延伸】
- 变式问法:
L[-1:-3]为空(步长默认+1);需步长-1。 - 版本差异:语义稳定。
- 工程注意点:动态索引先判长度;用切片代替手写下标环。
P14 · 知识点:append 与 extend
【题目】L = [1, 2, 3],L.append(4) 与 L.extend([4, 5]) 的区别是? A. 两者效果相同 B. append 把整个参数作为单个元素添加;extend 把可迭代对象中的元素逐个添加 C. extend 一次只能添加一个元素 D. append 改变原列表,extend 不改变
答案:B
【考点】append 添加单个对象,extend 展开添加多个元素。
【结论】选 B。append 将参数整体作为一个元素追加到末尾;extend 将可迭代对象中的元素逐个展开追加。
【逐项辨析】
- A. 两者效果相同——错误。append([4,5]) 得到 [1,2,3,[4,5]];extend([4,5]) 得到 [1,2,3,4,5]。
- B. append 把整个参数作为单个元素添加;extend 把可迭代对象中的元素逐个添加——正确。这是二者最本质的区别。
- C. extend 一次只能添加一个元素——错误。extend 的参数是任意可迭代对象,可以添加多个元素,甚至成千上万个。
- D. append 改变原列表,extend 不改变——错误。两者都就地修改原列表,并返回 None。
【知识点】列表的元素添加方法族:
- L.append(x):将对象 x 整体作为一个新元素追加到列表尾部。时间复杂度 O(1)(均摊)。
- L.extend(iterable):遍历 iterable,将其中的每个元素依次追加到列表尾部。时间复杂度 O(k),k 为可迭代对象长度。
- L.insert(i, x):在索引 i 处插入元素 x,后续元素后移。时间复杂度 O(n)。
- L += iterable:等价于 extend,就地修改。
常见陷阱:
L.append([4, 5])产生嵌套列表,而L.extend([4, 5])产生扁平列表。L.extend("abc")会将字符串拆分为 'a','b','c' 三个字符添加,而L.append("abc")添加整个字符串。- 不要写
L = L.append(x),因为 append 返回 None。
【记忆锚点】“append 打包送,extend 拆包送;一个整进门,个个排排坐。”
【易混对比】
| 方法 | 参数类型 | 对原列表的影响 | 返回值 | 结果示例(L=[1,2,3]) |
|---|---|---|---|---|
| append(x) | 任意对象 | 就地修改 | None | [1,2,3,4] 或 [1,2,3,[4,5]] |
| extend(iter) | 可迭代对象 | 就地修改 | None | [1,2,3,4,5] |
| insert(i, x) | 索引+对象 | 就地修改 | None | [1,4,2,3] |
| + 运算符 | 两个列表 | 产生新列表 | 新列表 | [1,2,3,4,5](原列表不变) |
换问法:执行 L.append([4, 5]) 后,len(L) 的值是多少?(答案:4,因为只新增了一个列表元素。)
【自测】
L = [1, 2, 3]
L.extend("ab")
print(L) # 输出?答:输出
[1, 2, 3, 'a', 'b']。字符串是可迭代对象,extend 会逐字符拆开添加,而不是添加整个 "ab"。 与第 P16 题连考。
【知识关联】
- 同库关联:与 P11;append vs extend 与 Java List.add/addAll 对照。
- 实现层:append 追加单对象;extend 迭代追加;均就地返回 None。
- 面试追问:①
L.append(L)的后果?② 与+=关系?
【拓展延伸】
- 变式问法:extend 字符串逐字符;extend 生成器。
- 版本差异:语义稳定。
- 工程注意点:类型别搞混;空用 list() 清空而不是 L=[] 若有别名。
P15 · 知识点:sort 与 sorted
【题目】关于 sort() 和 sorted(),下列说法正确的是?
A. 两者都返回一个新的已排序列表 B. list.sort() 就地排序并返回 None;sorted() 返回新列表 C. sort() 只能升序排列 D. sorted() 只能用于列表
答案:B
【考点】就地修改(sort)与返回新对象(sorted)的差异。
【结论】选 B。sort() 是列表的就地排序方法,返回 None;sorted() 是内置函数,对任意可迭代对象排序并返回新列表。
【逐项辨析】
- A. 两者都返回一个新的已排序列表——错误。sort() 就地修改原列表,返回 None。
- B. list.sort() 就地排序并返回 None;sorted() 返回新列表——正确。这是二者最核心的区别。
- C. sort() 只能升序排列——错误。通过 reverse=True 可实现降序;通过 key 参数可实现自定义排序规则。
- D. sorted() 只能用于列表——错误。sorted() 接受任何可迭代对象(元组、字符串、字典、集合、生成器等)。
【知识点】排序在 Python 中的两种实现:
list.sort(key=None, reverse=False):
- 是列表对象的方法,只能用于列表。
- 采用 Timsort 算法(归并排序+插入排序的混合),时间复杂度 O(n log n),空间复杂度 O(n)。
- 就地修改原列表,稳定排序,返回 None。
- key 参数接收一个函数,用于提取排序依据(如
L.sort(key=len)按长度排序)。
sorted(iterable, key=None, reverse=False):
- 是 Python 内置函数。
- 接受任意可迭代对象,先将其元素收集到新列表,再排序。
- 返回新列表,原对象不受影响。
- 底层同样使用 Timsort。
稳定排序的含义:当两个元素 key 值相等时,保持它们在原序列中的相对顺序。
【记忆锚点】“sort 原地蹲,sorted 搬新家;一个改自己,一个生娃娃。”
【易混对比】
| 特性 | list.sort() | sorted() |
|---|---|---|
| 类型 | 列表方法 | 内置函数 |
| 适用对象 | 仅列表 | 任意可迭代对象 |
| 原对象 | 被修改 | 不变 |
| 返回值 | None | 新列表 |
| 稳定性 | 稳定 | 稳定 |
| 时间复杂度 | O(n log n) | O(n log n) |
换问法:对元组 t = (3, 1, 2) 排序,应该用什么?(答案:sorted(t),因为元组没有 sort 方法。)
【自测】
L = ["banana", "pie", "Washington"]
L.sort(key=len)
print(L) # 输出?答:输出
['pie', 'banana', 'Washington']。key=len 按字符串长度排序,三者长度分别是len("banana")=6、len("pie")=3、len("Washington")=10(实测),升序即 3 < 6 < 10,故 pie 在最前、banana 次之、Washington 最后。 与第 P14 题连考。
【知识关联】
- 同库关联:与 P16、J43(Comparable/Comparator);sorted 可对任意可迭代。
- 实现层:Timsort 稳定排序;key 提取;sort 就地返回 None。
- 面试追问:① 稳定排序含义?② 如何多级排序?
【拓展延伸】
- 变式问法:
sorted(d)排的是 key;L.sort(key=lambda x: x[1], reverse=True)。 - 版本差异:语义稳定。
- 工程注意点:不要
L=L.sort();大数据可用 heapq 堆选 topk。
P16 · 知识点:列表反转
【题目】L = [1, 2, 3]; L.reverse() 执行后,L 的值是? A. [3, 2, 1] B. [1, 2, 3](原列表不变) C. 报错 D. None
答案:A
【考点】reverse() 就地反转;reversed()/切片返回新对象。
【结论】选 A。reverse() 是就地反转方法,执行后原列表变为 [3, 2, 1]。
【逐项辨析】
- A. [3, 2, 1]——正确。reverse() 将列表元素按位置互换,原列表被修改。
- B. [1, 2, 3](原列表不变)——错误。reverse() 不是纯函数,它会改变原列表。
- C. 报错——错误。reverse() 对任何列表都合法,空列表调用也不报错。
- D. None——错误。虽然 reverse() 返回 None,但题目问的是 L 的值,不是返回值。
【知识点】Python 提供三种反转方式,行为差异极大:
- L.reverse():列表方法,就地反转,修改原列表,返回 None。
- reversed(L):内置函数,返回一个逆序迭代器,不修改原列表,需 list(reversed(L)) 才能得到列表。
- L[::-1]:切片语法,返回原列表的逆序浅拷贝,原列表不变。
常见笔试陷阱:
L = L.reverse()是严重错误,因为 reverse() 返回 None,执行后 L 变成 None。- 若要获得逆序且不改原列表,首选切片
L[::-1],其次list(reversed(L))。
【记忆锚点】“reverse 原地翻,reversed 看镜面;切片拷贝最保险。”
【易混对比】
| 方式 | 修改原列表 | 返回值 | 类型 | 适用场景 |
|---|---|---|---|---|
| L.reverse() | 是 | None | 方法 | 不需要保留原顺序 |
| reversed(L) | 否 | 迭代器 | 内置函数 | 只需遍历,不建列表 |
| L[::-1] | 否 | 新列表 | 切片 | 需要逆序副本 |
| list(reversed(L)) | 否 | 新列表 | 组合 | 需要逆序列表 |
换问法:执行 L = [1,2,3]; print(L.reverse()) 会输出什么?(答案:None。)
【自测】
L = [1, 2, 3]
M = L[::-1]
print(L is M) # 输出?
print(L == M) # 输出?答:
L is M输出False(不同对象);L == M输出False(顺序不同,[1,2,3] != [3,2,1])。 与第 P15 题连考。
【知识关联】
- 同库关联:与 P12(切片反转);reverse/reversed/切片三对照。
- 实现层:reverse 就地;reversed 返回迭代器。
- 面试追问:①
list(reversed(L))与L[::-1]?② 返回值?
【拓展延伸】
- 变式问法:
print(L.reverse())输出 None。 - 版本差异:语义稳定。
- 工程注意点:只要遍历逆序用 reversed 省内存。
P17 · 知识点:列表推导式
【题目】list(x**2 for x in range(3)) 的结果是? A. [0, 1, 4] B. [0, 1, 2] C. [0, 1, 4, 9] D. (0, 1, 4)
答案:A
【考点】生成器表达式与列表转换。
【结论】选 A。range(3) 生成 0、1、2,平方后得到 0、1、4,经 list() 物化为列表。
【推导过程】
range(3)生成整数序列:0, 1, 2(注意不含终点 3)。- 生成器表达式
(x**2 for x in range(3))惰性地计算每个元素的平方:- x = 0 →
0**2 = 0 - x = 1 →
1**2 = 1 - x = 2 →
2**2 = 4
- x = 0 →
list()遍历生成器,将所有结果收集为新列表。- 最终结果:[0, 1, 4]。
【逐项辨析】
- A. [0, 1, 4]——正确。0、1、2 的平方,不含 3。
- B. [0, 1, 2]——错误。这是
list(range(3))的结果,遗漏了平方运算。 - C. [0, 1, 4, 9]——错误。range(3) 不含 3,因此没有
3**2=9。 - D. (0, 1, 4)——错误。这是元组形式,而 list() 返回的是列表。
【知识点】列表推导式与生成器表达式:
- 列表推导式(List Comprehension):
[expr for var in iterable if cond],一次性构建完整列表,内存占用与结果长度成正比。 - 生成器表达式(Generator Expression):
(expr for var in iterable if cond),惰性求值,遍历时才计算下一个元素,内存占用极小,适合大数据流。
二者语法仅差一个括号:方括号 [] vs 圆括号 ()。生成器表达式只能迭代一次,第二次迭代需重新创建。
range() 函数:
range(stop):从 0 开始到 stop-1。range(start, stop[, step]):左闭右开区间。- Python 3 中 range 返回 range 对象(惰性),不是列表。
【记忆锚点】“range 左闭右开,平方跟着来;圆括号是生成器,list 一叫全出来。”
【易混对比】
| 表达式 | 结果 | 类型 | 内存特点 |
|---|---|---|---|
[x**2 for x in range(3)] | [0, 1, 4] | 列表 | 立即生成,占用内存 |
(x**2 for x in range(3)) | <generator> | 生成器 | 惰性,几乎不占用 |
list(x**2 for x in range(3)) | [0, 1, 4] | 列表 | 生成器转列表 |
{x**2 for x in range(3)} | 集合 | 去重 |
换问法:[x**2 for x in range(4)] 与 list(x**2 for x in range(4)) 结果是否相同?(答案:是,都是 [0, 1, 4, 9]。)
【自测】
g = (x**2 for x in range(3))
print(list(g)) # 第一次输出?
print(list(g)) # 第二次输出?答:第一次输出
[0, 1, 4];第二次输出[],因为生成器只能迭代一次,耗尽后为空。 与第 P12 题连考。
【知识关联】
- 同库关联:与 P50/P51(map/filter)、P73(生成器);推导式是 Python 特色。
- 实现层:列表推导一次物化;圆括号生成器惰性;CPython 对推导有快速路径。
- 面试追问:① 推导式与 map 性能?② 嵌套推导可读性边界?
【拓展延伸】
- 变式问法:字典/集合推导;条件过滤
if。 - 版本差异:Py3.8 海象运算符可进推导。
- 工程注意点:复杂逻辑别硬塞推导;生成器适合流式大数据。
P18 · 知识点:元组特性
【题目】关于元组(tuple)的说法,正确的是? A. 元组是可变的 B. 元组可以作为字典的键 C. 元组中不能包含列表 D. 元组定义后可以使用 append 添加元素
答案:B
【考点】元组不可变 → 可哈希 → 可作为字典键。
【结论】选 B。元组不可变,因此可哈希,满足字典键的要求。
【逐项辨析】
- A. 元组是可变的——错误。元组是不可变序列,创建后不能增删改元素。
- B. 元组可以作为字典的键——正确。不可变对象通常可哈希,可作为字典键和集合元素(前提是元组内的元素也都可哈希)。
- C. 元组中不能包含列表——错误。元组可以包含列表,如
(1, [2, 3])完全合法;只是此时元组不再可哈希。 - D. 元组定义后可以使用 append 添加元素——错误。元组没有 append、extend、pop 等方法。
【知识点】元组(tuple)是 Python 的核心不可变序列类型:
- 不可变性:创建后无法修改长度和内容。这种设计使元组可以作为“常量集合”使用,保证数据不被意外篡改。
- 可哈希性:不可变是可哈希的必要条件(非充分)。若元组内所有元素都可哈希,则整个元组可哈希。可哈希对象可作为 dict 的键和 set 的元素。
- 性能优势:元组占用的内存比列表小,创建和遍历速度略快;编译器可对元组做某些优化。
- 解包与多元赋值:
a, b = (1, 2)或a, b = 1, 2是 Python 经典语法。 - 单元素元组:
(1,)必须带逗号,否则(1)只是整数。
注意:“元组不可变”指的是元组对象本身的结构(长度和元素引用)不可变,但如果元素是可变对象(如列表),该元素的内容是可以修改的,例如 t = (1, [2]); t[1].append(3) 是合法的。
【记忆锚点】“元组戴枷锁,不能增删改;能当字典键,只因它听话。”
【易混对比】
| 特性 | 列表 list | 元组 tuple |
|---|---|---|
| 可变 | 是 | 否 |
| 可哈希 | 否 | 是(元素均可哈希时) |
| 字典键 | 不可 | 可以 |
| 方法数量 | 丰富(11+) | 极少(count, index) |
| 内存占用 | 较大 | 较小 |
| 速度 | 略慢 | 略快 |
| 单元素语法 | [1] | (1,)(逗号不可少) |
换问法:t = (1, [2]),执行 t[1].append(3) 后 t 的值是多少?(答案:(1, [2, 3]),元组结构没变,但内部列表被修改。)
【自测】
t = (1, 2, [3, 4])
d = {t: "hello"} # 是否报错?答:报错
TypeError: unhashable type: 'list'。因为 t 中包含列表,列表不可哈希,导致整个元组不可哈希,不能作为字典键。 与第 P11 题连考。
【知识关联】
- 同库关联:与 P07;不可变与可哈希。
- 实现层:存储紧凑;无 append/assign。
- 面试追问:① 元组解包?② namedtuple?
【拓展延伸】
- 变式问法:
a,b=1,2;星号解包。 - 版本差异:typing.NamedTuple。
- 工程注意点:多返回值用元组;结构化用 dataclass。
P19 · 知识点:类型转换
【题目】tuple([1, 2, 3]) 和 list((1, 2, 3)) 的结果分别是? A. (1, 2, 3) 和 [1, 2, 3] B. [1, 2, 3] 和 (1, 2, 3) C. 抛异常 D. 都是 (1, 2, 3)
答案:A
【考点】tuple() 与 list() 互相转换。
【结论】选 A。tuple() 将可迭代对象转为元组,list() 将可迭代对象转为列表。
【推导过程】
tuple([1, 2, 3]):将列表作为可迭代对象传入 tuple(),逐元素收集,生成新元组(1, 2, 3)。list((1, 2, 3)):将元组作为可迭代对象传入 list(),逐元素收集,生成新列表[1, 2, 3]。- 两者均不修改原对象,而是返回全新的序列对象。
【逐项辨析】
- A. (1, 2, 3) 和 [1, 2, 3]——正确。tuple() 返回元组,list() 返回列表。
- B. [1, 2, 3] 和 (1, 2, 3)——错误。顺序颠倒,tuple() 不会返回列表。
- C. 抛异常——错误。列表和元组都是合法的可迭代对象,类型转换安全。
- D. 都是 (1, 2, 3)——错误。list() 返回的是列表,不是元组。
【知识点】Python 内置的序列构造器:
- list([iterable]):从可迭代对象构建新列表。若不带参数,返回空列表 []。
- tuple([iterable]):从可迭代对象构建新元组。若不带参数,返回空元组 ()。
可接受的可迭代对象包括:字符串、列表、元组、字典(取键)、集合、range 对象、生成器、文件对象等。
类型转换的深层含义:这是一种“浅拷贝”行为。新序列的元素引用与原序列相同,若元素是可变对象,修改元素会影响两个序列。
【记忆锚点】“tuple 套圆括号,list 套方括号;互为变形金刚,互换皮囊不换瓤。”
【易混对比】
| 转换表达式 | 结果 | 原对象是否改变 |
|---|---|---|
| tuple([1, 2, 3]) | (1, 2, 3) | 否 |
| list((1, 2, 3)) | [1, 2, 3] | 否 |
| set([1, 2, 2]) | 否 | |
| list("ab") | ['a', 'b'] | 否 |
| tuple({"x":1}) | ('x',) | 否(取字典键) |
换问法:tuple([1, [2]]) 的结果是否可哈希?(答案:否,因为内部包含列表。)
【自测】
a = [1, 2]
b = tuple(a)
a.append(3)
print(b) # 输出?答:输出
(1, 2)。类型转换创建了新元组对象,b 与 a 不再共享引用;a 后续修改不影响 b。 与第 P20 题连考。
【知识关联】
- 同库关联:与 P09(同考点深化)、P02。
- 实现层:构造器协议
__int__/__float__/__index__。 - 面试追问:①
int(True)?② 截断与 round 区别?
【拓展延伸】
- 变式问法:
int(3.7)vsround(3.7)。 - 版本差异:银行家舍入 round 规则注意(Py3)。
- 工程注意点:序列索引用
__index__;财务舍入显式。
P20 · 知识点:字符串转列表
【题目】list("abc") 的结果是? A. ['a', 'b', 'c'] B. ['abc'] C. 'abc' D. ('a', 'b', 'c')
答案:A
【考点】字符串是字符序列,list() 逐字符拆分。
【结论】选 A。字符串是可迭代对象,list() 会逐字符拆分并收集为列表。
【推导过程】
- 字符串 "abc" 是一个字符序列,迭代时依次产生 'a'、'b'、'c' 三个单字符字符串。
list("abc")内部调用字符串的__iter__方法,遍历每个字符。- 将 'a'、'b'、'c' 依次放入新列表。
- 结果:
['a', 'b', 'c']。
【逐项辨析】
- A. ['a', 'b', 'c']——正确。字符串逐字符拆分。
- B. ['abc']——错误。这是
["abc"](直接写单元素列表)的结果,不是 list("abc")。 - C. 'abc'——错误。list() 返回的是列表,不是字符串。
- D. ('a', 'b', 'c')——错误。这是
tuple("abc")的结果。
【知识点】字符串在 Python 中是不可变字符序列,支持所有序列操作:
- 索引:
"abc"[0]→'a' - 切片:
"abc"[1:]→'bc' - 迭代:
for ch in "abc": ... - 成员判断:
'a' in "abc"→True
list() 对字符串的特殊行为常常与 split() 混淆:
list("a b c")→['a', ' ', 'b', ' ', 'c'](按字符拆)"a b c".split()→['a', 'b', 'c'](按空白拆)"a,b,c".split(",")→['a', 'b', 'c'](按指定分隔符拆)
【记忆锚点】“list 见字符串,字符逐个拎;要想按词分,请用 split 请。”
【易混对比】
| 表达式 | 结果 | 拆分粒度 |
|---|---|---|
| list("abc") | ['a', 'b', 'c'] | 单个字符 |
| "abc".split() | ['abc'] | 按空白(无空白则整体) |
| "a,b,c".split(",") | ['a', 'b', 'c'] | 按逗号 |
| [c for c in "abc"] | ['a', 'b', 'c'] | 单个字符(同 list()) |
换问法:list("a b c") 与 "a b c".split() 的结果分别是?(答案:['a', ' ', 'b', ' ', 'c'] 与 ['a', 'b', 'c']。)
【自测】
s = "123"
print(list(map(int, s))) # 输出?答:输出
[1, 2, 3]。map(int, s) 将字符串每个字符转为整数,list() 收集为列表。 与第 P19 题连考。
【知识关联】
- 同库关联:与 P36/P37(join/split)、P11。
- 实现层:
list(s)得字符列表;s.split()按空白;s.split(sep)。 - 面试追问:①
list("abc")?② split 与 splitlines?
【拓展延伸】
- 变式问法:
"a,b".split(",");maxsplit。 - 版本差异:语义稳定。
- 工程注意点:CSV 用 csv 模块;注意空串 split 行为。
P21 · 知识点:成员判断
【题目】判断元素是否在列表中存在,应使用?
A. contains() B. in C. exist() D. find()
答案:B
【考点】in 运算符与 __contains__ 魔法方法。
【结论】选 B。Python 使用 in 运算符判断成员关系,底层调用对象的 __contains__ 方法。
【逐项辨析】
- A. contains()——错误。Python 没有内置的 contains() 函数(collections.abc 中有抽象基类方法,但不是直接语法)。
- B. in——正确。
x in L是判断成员关系的标准写法。 - C. exist()——错误。exist() 不是 Python 内建语法,某些 ORM(如 SQLAlchemy)中有 exists,但与列表成员判断无关。
- D. find()——错误。find() 是字符串方法(返回子串索引或 -1),列表没有 find 方法;列表有 index(),但找不到会抛 ValueError。
【知识点】成员关系判断是 Python 的核心语法之一:
x in container:判断 x 是否在容器中,返回布尔值。底层调用container.__contains__(x)。x not in container:in 的否定形式,等价于not (x in container)。
各类容器的 in 行为:
- 列表/元组:逐个比较元素,时间复杂度 O(n)。
- 字符串:判断子串是否存在,时间复杂度平均 O(n)。
- 字典:判断键是否存在(不判断值),时间复杂度 O(1)。
- 集合:判断元素是否存在,时间复杂度 O(1)。
列表的 index() 方法:
L.index(x)返回 x 第一次出现的索引,找不到抛 ValueError。- 与 in 的区别:in 返回布尔值,index 返回位置。
【记忆锚点】“成员在不在,in 来探;contains、exist 是假冒,find 专管字符串。”
【易混对比】
| 表达式 | 适用类型 | 返回值 | 找不到时 |
|---|---|---|---|
| x in L | 列表、元组、字典、集合、字符串 | bool | False |
| L.index(x) | 列表、元组、字符串 | int(索引) | 抛 ValueError |
| str.find(sub) | 字符串 | int(索引)或 -1 | -1 |
| str.index(sub) | 字符串 | int(索引) | 抛 ValueError |
| dict.get(k) | 字典 | 值 或 None | None(不报错) |
换问法:判断字典 d = {"a": 1} 中值 1 是否存在,应怎么写?(答案:1 in d.values(),因为 1 in d 判断的是键。)
【自测】
L = [1, 2, [3]]
print(3 in L) # 输出?
print([3] in L) # 输出?答:
3 in L输出False,因为 L 中没有直接存放整数 3,而是存放了列表 [3];[3] in L输出True,因为 [3] 正是 L 的一个元素。 与第 P22 题连考。
【知识关联】
- 同库关联:与 P29/P30(集合)、P23–P28(字典);
in对不同结构复杂度不同。 - 实现层:list O(n);set/dict O(1) 均摊。
- 面试追问:① 为何推荐 set 查重?② 字符串 in 子串?
【拓展延伸】
- 变式问法:
"el" in "hello";1 in {"a":1}查的是 key。 - 版本差异:语义稳定。
- 工程注意点:热点成员判断先转 set;注意 dict in 判 key。
P22 · 知识点:引用与拷贝
【题目】a = [1, 2, 3]; b = a; b.append(4); 此时 a 的值是? A. [1, 2, 3] B. [1, 2, 3, 4] C. [1, 2, 3, 4, 4] D. 抛异常
答案:B
【考点】普通赋值是引用绑定,不产生新对象。
【结论】选 B。b = a 是引用赋值,a 与 b 指向同一列表对象,b 修改后 a 同步变化。
【推导过程】
a = [1, 2, 3]:在内存中创建一个列表对象[1, 2, 3],变量 a 持有其引用。b = a:变量 b 也指向 a 所引用的同一个列表对象,此时a is b为 True。
a ──→ [1, 2, 3] ←── bb.append(4):通过引用 b 找到该列表对象,就地追加元素 4,对象变为[1, 2, 3, 4]。a仍然指向该对象,因此a的值变为[1, 2, 3, 4]。
【逐项辨析】
- A. [1, 2, 3]——错误。这是假设 b = a 创建了独立副本,但普通赋值只是引用绑定。
- B. [1, 2, 3, 4]——正确。a 与 b 共享对象,b 的修改对 a 可见。
- C. [1, 2, 3, 4, 4]——错误。append(4) 只执行一次,不会重复添加。
- D. 抛异常——错误。列表的 append 操作完全合法。
【知识点】Python 中的赋值与拷贝机制:
- 引用赋值(=):变量绑定到对象引用,不创建新对象。
b = a后,a is b为 True。 - 浅拷贝(Shallow Copy):创建新容器对象,但元素引用与原容器共享。方式有:
b = a[:]b = list(a)b = copy.copy(a)b = a.copy()(列表专用)
- 深拷贝(Deep Copy):递归创建所有嵌套对象的新副本,完全独立。使用
copy.deepcopy(a)。
判断技巧:
- 修改后观察另一变量是否变化:变化则是引用或浅拷贝(元素共享);不变则是独立对象或深拷贝。
- 使用
id()或is判断是否为同一对象。
【记忆锚点】“等号不拷贝,共用一个碗;浅拷贝换碗不换菜,深拷贝碗菜全换完。”
【易混对比】
| 操作 | 新对象? | 元素共享? | 适用场景 |
|---|---|---|---|
| b = a | 否 | 是(同一对象) | 需要别名 |
| b = a[:] | 是 | 是(元素引用) | 一维列表独立 |
| b = list(a) | 是 | 是(元素引用) | 同上 |
| b = a.copy() | 是 | 是(元素引用) | 同上 |
| copy.deepcopy(a) | 是 | 否 | 嵌套结构完全独立 |
换问法:a = [1, [2]]; b = a[:]; b[1].append(3) 后 a 的值是多少?(答案:[1, [2, 3]],因为浅拷贝只复制外层列表,内层列表仍共享。)
【自测】
a = [1, 2, 3]
b = a[:]
b.append(4)
print(a is b) # 输出?
print(a) # 输出?答:
a is b输出False(切片创建了新对象);a输出[1, 2, 3](b 的修改不影响 a)。 与第 P21 题连考。
【知识关联】
- 同库关联:与 P67/P68/P69(拷贝三连)强相关;与 Java 引用语义对照。
- 实现层:赋值绑定名字;可变对象别名;不可变“改”即新建。
- 面试追问:① 传参是传值还是传引用(对象引用,P42)?② 如何断开别名?
【拓展延伸】
- 变式问法:
a=b=[]共享;a,b=[],[]独立。 - 版本差异:语义稳定。
- 工程注意点:函数默认参数可变陷阱见 P43;配置对象注意别名污染。