Skip to content

二、列表与元组(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 中最常用的内置序列类型之一,具备四大核心特征:

  1. 可变(Mutable):创建后可通过 append、extend、insert、pop、remove、sort、reverse 等方法修改其内容,也可通过索引赋值直接修改元素。
  2. 有序(Ordered):元素按插入顺序排列,每个元素都有固定的位置索引(从 0 开始),支持切片访问子序列。
  3. 可嵌套(Nestable):元素可以是任意对象,包括另一个列表、字典、自定义类的实例等,从而构造出多维数据结构。
  4. 异构(Heterogeneous):元素类型不要求一致,同一个列表中可以同时存放整数、字符串、布尔值、None、对象引用等。

与 C 语言数组的对比:C 数组在声明时即确定元素类型和长度,内存连续且类型单一;Python 列表底层通过动态数组(over-allocated array)存储指向 PyObject 的指针,因此类型灵活、长度动态。

【记忆锚点】“变序嵌杂”——可变、有序、可嵌、杂类型。或者记口诀:“列表像个筐,啥都往里装;能增又能删,顺序不会乱。”

【易混对比】

特性列表 list元组 tupleC 数组
可变是否固定长度,元素可改
有序是是是
元素类型可混搭可混搭必须一致
可哈希否是(元素均可哈希时)—
嵌套支持支持支持(多维数组)

换问法:以下哪个不是列表的特性?(答案:元素必须是同一类型。)

【自测】

python
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 对应的元素。

【推导过程】

  1. 原列表索引映射:
   值: 0   1   2   3   4   5
   索引:0   1   2   3   4   5
  1. start = 1 → 从索引 1(值 1)开始;
  2. stop = 5 → 到索引 5(值 5)之前结束,即索引 5 本身不被包含;
  3. step = 2 → 每隔 2 个索引取一个:
    • 第 1 个:索引 1(值 1)
    • 第 2 个:索引 1 + 2 = 3(值 3)
    • 第 3 个:索引 3 + 2 = 5,已等于 stop,超出边界,停止。
  4. 结果:[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;为负值时表示逆向切片。

关键规则:

  1. 含头不含尾:stop 是硬边界,永远不会被包含。
  2. 不抛边界异常:start 或 stop 超出有效范围时,Python 会自动截断到合法边界,不会报错。
  3. 空切片:当 start 与 stop 交叉且 step 方向不匹配时,返回空列表。
  4. 复制与逆序: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]。)

【自测】

python
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]。

【推导过程】

  1. 原列表索引双映射:
   正向索引: 0   1   2   3   4
   值:       0   1   2   3   4
   负向索引: -5  -4  -3  -2  -1
  1. L[-2:] 中 start = -2,stop 省略(默认到末尾),step 省略(默认 1);
  2. -2 对应正向索引 3(值 3);
  3. 从索引 3 开始顺序取到末尾:索引 3(值 3)、索引 4(值 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]。)

【自测】

python
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。

【知识点】列表的元素添加方法族:

  1. L.append(x):将对象 x 整体作为一个新元素追加到列表尾部。时间复杂度 O(1)(均摊)。
  2. L.extend(iterable):遍历 iterable,将其中的每个元素依次追加到列表尾部。时间复杂度 O(k),k 为可迭代对象长度。
  3. L.insert(i, x):在索引 i 处插入元素 x,后续元素后移。时间复杂度 O(n)。
  4. 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,因为只新增了一个列表元素。)

【自测】

python
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 中的两种实现:

  1. list.sort(key=None, reverse=False):

    • 是列表对象的方法,只能用于列表。
    • 采用 Timsort 算法(归并排序+插入排序的混合),时间复杂度 O(n log n),空间复杂度 O(n)。
    • 就地修改原列表,稳定排序,返回 None。
    • key 参数接收一个函数,用于提取排序依据(如 L.sort(key=len) 按长度排序)。
  2. 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 方法。)

【自测】

python
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 提供三种反转方式,行为差异极大:

  1. L.reverse():列表方法,就地反转,修改原列表,返回 None。
  2. reversed(L):内置函数,返回一个逆序迭代器,不修改原列表,需 list(reversed(L)) 才能得到列表。
  3. 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。)

【自测】

python
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() 物化为列表。

【推导过程】

  1. range(3) 生成整数序列:0, 1, 2(注意不含终点 3)。
  2. 生成器表达式 (x**2 for x in range(3)) 惰性地计算每个元素的平方:
    • x = 0 → 0**2 = 0
    • x = 1 → 1**2 = 1
    • x = 2 → 2**2 = 4
  3. list() 遍历生成器,将所有结果收集为新列表。
  4. 最终结果:[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() 返回的是列表。

【知识点】列表推导式与生成器表达式:

  1. 列表推导式(List Comprehension):[expr for var in iterable if cond],一次性构建完整列表,内存占用与结果长度成正比。
  2. 生成器表达式(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]。)

【自测】

python
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 的核心不可变序列类型:

  1. 不可变性:创建后无法修改长度和内容。这种设计使元组可以作为“常量集合”使用,保证数据不被意外篡改。
  2. 可哈希性:不可变是可哈希的必要条件(非充分)。若元组内所有元素都可哈希,则整个元组可哈希。可哈希对象可作为 dict 的键和 set 的元素。
  3. 性能优势:元组占用的内存比列表小,创建和遍历速度略快;编译器可对元组做某些优化。
  4. 解包与多元赋值:a, b = (1, 2) 或 a, b = 1, 2 是 Python 经典语法。
  5. 单元素元组:(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]),元组结构没变,但内部列表被修改。)

【自测】

python
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() 将可迭代对象转为列表。

【推导过程】

  1. tuple([1, 2, 3]):将列表作为可迭代对象传入 tuple(),逐元素收集,生成新元组 (1, 2, 3)。
  2. list((1, 2, 3)):将元组作为可迭代对象传入 list(),逐元素收集,生成新列表 [1, 2, 3]。
  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]]) 的结果是否可哈希?(答案:否,因为内部包含列表。)

【自测】

python
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) vs round(3.7)。
  • 版本差异:银行家舍入 round 规则注意(Py3)。
  • 工程注意点:序列索引用 __index__;财务舍入显式。

P20 · 知识点:字符串转列表 ​

【题目】list("abc") 的结果是? A. ['a', 'b', 'c'] B. ['abc'] C. 'abc' D. ('a', 'b', 'c')

答案:A

【考点】字符串是字符序列,list() 逐字符拆分。

【结论】选 A。字符串是可迭代对象,list() 会逐字符拆分并收集为列表。

【推导过程】

  1. 字符串 "abc" 是一个字符序列,迭代时依次产生 'a'、'b'、'c' 三个单字符字符串。
  2. list("abc") 内部调用字符串的 __iter__ 方法,遍历每个字符。
  3. 将 'a'、'b'、'c' 依次放入新列表。
  4. 结果:['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']。)

【自测】

python
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 的核心语法之一:

  1. x in container:判断 x 是否在容器中,返回布尔值。底层调用 container.__contains__(x)。
  2. 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列表、元组、字典、集合、字符串boolFalse
L.index(x)列表、元组、字符串int(索引)抛 ValueError
str.find(sub)字符串int(索引)或 -1-1
str.index(sub)字符串int(索引)抛 ValueError
dict.get(k)字典值 或 NoneNone(不报错)

换问法:判断字典 d = {"a": 1} 中值 1 是否存在,应怎么写?(答案:1 in d.values(),因为 1 in d 判断的是键。)

【自测】

python
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 同步变化。

【推导过程】

  1. a = [1, 2, 3]:在内存中创建一个列表对象 [1, 2, 3],变量 a 持有其引用。
  2. b = a:变量 b 也指向 a 所引用的同一个列表对象,此时 a is b 为 True。
   a ──→ [1, 2, 3] ←── b
  1. b.append(4):通过引用 b 找到该列表对象,就地追加元素 4,对象变为 [1, 2, 3, 4]。
  2. 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 中的赋值与拷贝机制:

  1. 引用赋值(=):变量绑定到对象引用,不创建新对象。b = a 后,a is b 为 True。
  2. 浅拷贝(Shallow Copy):创建新容器对象,但元素引用与原容器共享。方式有:
    • b = a[:]
    • b = list(a)
    • b = copy.copy(a)
    • b = a.copy()(列表专用)
  3. 深拷贝(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]],因为浅拷贝只复制外层列表,内层列表仍共享。)

【自测】

python
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;配置对象注意别名污染。

持续学习,持续积累。