AI 技术博客
返回首页
Python 基础 23 分钟阅读

第六章综合测验:序列、映射与集合

Python列表字典综合测验复习

引言

第六章到此收官。本篇综合测验严格以第 1 至 9 篇为范围:列表的序列操作与常用方法(第 1 篇)、元组的不可变性与解包(第 2 篇)、字典的增删查改与遍历(第 3 篇)、集合的去重与运算(第 4 篇)、引用与深浅拷贝(第 5 篇)、嵌套容器的遍历(第 6 篇)、Counter 与 defaultdict(第 7 篇)、词频统计实战(第 8 篇)与排序筛选聚合实战(第 9 篇)。题目不引入第七章(文件与异常处理)及以后的任何内容;推导式只使用第 1、3 篇讲过的基础形式。

全篇分三个部分:10 道选择题按”记忆 → 理解 → 应用 → 分析”的难度梯度排列,每题标注难度并附逐题解析;3 道编程题给出可独立运行的参考解答与实测输出;最后是一个综合任务——合并两个成绩记录集合并去重、统计并输出报表,考察你把整章知识串起来解决真实问题的能力。建议先独立完成全部题目再对照解析;做错的题要回到对应篇章重新理解原理。文中所有运行结果均基于本机 CPython 3.12 实测,未做任何修饰。

选择题(共 10 题,每题只有一个正确答案)

第 1 题:列表方法的返回值【记忆】

lst = [3, 1, 2]
x = lst.sort()
y = lst.append(4)
print(x, y, lst)

输出是什么?

A. None None [1, 2, 3, 4] B. [1, 2, 3] None [1, 2, 3, 4] C. None None [3, 1, 2] D. [1, 2, 3] 4 [1, 2, 3, 4]

第 2 题:元组的不可变与解包【记忆】

t = (5, 6, 7, 8)
a, *b, c = t
print(a, b, c)

输出是什么?

A. 5 [6, 7] 8 B. 5 6 [7, 8] C. [5, 6] [7] 8 D. 报错:星号只能出现在解包的最左侧

第 3 题:嵌套列表的引用共享【理解】

grid = [[0] * 3] * 2
grid[0][1] = 9
print(grid)

输出是什么?

A. [[0, 9, 0], [0, 9, 0]] B. [[0, 9, 0], [0, 0, 0]] C. [[0, 1, 0], [0, 1, 0]] D. [[0, 0, 0], [0, 9, 0]]

第 4 题:字典的 get 与修改【记忆】

d = {'name': '张伟', 'score': 88}
print(d.get('age'))
print(d.get('age', 0))
d['score'] += 4
print(d)

输出是什么?

A. None、0、{'name': '张伟', 'score': 92} B. 0、0、{'name': '张伟', 'score': 92} C. None、None、{'name': '张伟', 'score': 88} D. 报错:键 'age' 不存在

第 5 题:集合的四种运算【理解】

a = {1, 2, 3, 4}
b = {2, 4, 6}
print(a & b)
print(a | b)
print(a - b)
print(a ^ b)

依次输出(共四行)的是什么?

A. {2, 4}、{1, 2, 3, 4, 6}、{1, 3}、{1, 3, 6} B. {2, 4}、{1, 2, 3, 4, 6}、{1, 3}、{1, 4} C. {2, 4}、{2, 4, 6}、{1, 3}、{1, 3, 6} D. {1, 2, 3, 4, 6}、{2, 4}、{1, 3}、{1, 3, 6}

第 6 题:浅拷贝与深拷贝【分析】

import copy
matrix = [[1, 2], [3, 4]]
shallow = copy.copy(matrix)
deep = copy.deepcopy(matrix)
shallow[0].append(5)
deep[0].append(6)
print(matrix)
print(shallow)
print(deep)

三行输出依次是什么?

A. [[1, 2, 5], [3, 4]]、[[1, 2, 5], [3, 4]]、[[1, 2, 6], [3, 4]] B. [[1, 2], [3, 4]]、[[1, 2, 5], [3, 4]]、[[1, 2, 6], [3, 4]] C. [[1, 2, 5], [3, 4]]、[[1, 2, 5], [3, 4]]、[[1, 2, 5], [3, 4]] D. [[1, 2, 6], [3, 4]]、[[1, 2, 5], [3, 4]]、[[1, 2, 6], [3, 4]]

第 7 题:嵌套容器与 key 函数【应用】

records = [
    {'name': '张伟', 'total': 265},
    {'name': '吴婷', 'total': 274},
    {'name': '郑浩', 'total': 189},
]
best = max(records, key=lambda r: r['total'])
print(best['name'], best['total'])

输出是什么?

A. 吴婷 274 B. 张伟 265 C. 郑浩 189 D. 274 吴婷

第 8 题:Counter 的计数【应用】

from collections import Counter
sales = ['手机', '电脑', '手机', '平板', '电脑', '手机']
c = Counter(sales)
print(c.most_common(2))

输出是什么?

A. [('手机', 3), ('电脑', 2)] B. [('电脑', 2), ('手机', 3)] C. {'手机': 3, '电脑': 2} D. [('手机', 3), ('平板', 1)]

第 9 题:defaultdict 的分组累加【应用】

from collections import defaultdict
orders = [('北京', 3), ('上海', 2), ('北京', 5), ('上海', 1), ('广州', 4)]
total = defaultdict(int)
for city, n in orders:
    total[city] += n
print(dict(total))

输出是什么?

A. {'北京': 8, '上海': 3, '广州': 4} B. {'北京': 3, '上海': 2, '广州': 4} C. {'北京': 5, '上海': 1, '广州': 4} D. 报错:键 '北京' 尚不存在

第 10 题:词频统计与多级排序键【分析】

words = ['python', 'go', 'java', 'python', 'go', 'python', 'java', 'java']
counts = {}
for w in words:
    counts[w] = counts.get(w, 0) + 1
print(sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])))

输出是什么?

A. [('java', 3), ('python', 3), ('go', 2)] B. [('python', 3), ('java', 3), ('go', 2)] C. [('go', 2), ('java', 3), ('python', 3)] D. {'java': 3, 'python': 3, 'go': 2}

点击查看答案

答案:A A A A A A A A A A

第 1 题(A):sort() 原地排序并返回 None,append(4) 同样返回 None,列表本身变成 [1, 2, 3, 4]。这是第 1 篇强调的规律:就地修改的方法大多返回 None(sort、append、extend、insert、remove、pop 的返回值另有意义),返回新对象的方法才是 sorted、list() 这类。选 D 说明混淆了 sort 与 sorted——第 9 篇的易错点 1 正是它。

第 2 题(A):星号解包(star unpacking)把中间元素收进列表:a 取首元素 5,c 取末元素 8,*b 收走中间的 [6, 7]。星号变量可以出现在解包列表的任意位置(首、中、尾),但不能有两个,且收走的是列表而不是元组。选 D 的同学需要重读第 2 篇的”解包的艺术”。

第 3 题(A):[[0] * 3] * 2 的外层复制是”引用复制”:两个子列表是同一个对象的两处引用。grid[0][1] = 9 修改的是共享的那个子列表,所以两行一起变。这正是第 5 篇”引用 vs 拷贝”的核心结论在嵌套容器上的表现,第 6 篇把它列为多维列表的第一大坑。想要独立的二维列表,必须用 [[0] * 3 for _ in range(2)]。

第 4 题(A):get 在键不存在时返回默认值(不传则为 None)而不报错,这是它和 d['age'] 的关键区别;+= 4 先把 'score' 的旧值读出来再加回去,得到 92。选 D 说明把 get 与下标访问混为一谈;d['age'] 才会抛 KeyError。

第 5 题(A):& 交集 {2, 4},| 并集 {1, 2, 3, 4, 6},- 差集(只在 a 中){1, 3},^ 对称差(只在一个集合中){1, 3, 6}。C 把 | 当成了 &;D 混淆了第一行与第二行的顺序;B 的 ^ 结果错了。四个运算符在集合运算里各司其职,建议按”交、并、差、对称差”四个词配对记忆(第 4 篇)。

第 6 题(A):copy.copy 只复制最外层列表,内层子列表仍是共享引用,所以 shallow[0].append(5) 会同时影响 matrix 和 shallow;copy.deepcopy 递归复制所有层级,deep[0].append(6) 只影响自身。三行输出正是矩阵回显了浅拷贝的共享、深拷贝的隔离。这是第 5 篇最需要动手验证的知识点:“浅拷贝之后改内层,原对象会跟着变”。

第 7 题(A):max 在没有 key 时比较整个元素(字典之间比较会报错),给了 key=lambda r: r['total'] 后按总分找最大,返回的是整个字典。best['name'] 与 best['total'] 配合输出 吴婷 274。这就是第 9 篇”从嵌套容器里取出冠军记录”的标准姿势:max/min/sorted + key 三件套。

第 8 题(A):Counter 是”元素 → 次数”的计数专用字典,most_common(2) 按次数降序返回前两名组成的元组列表:手机 3 次、电脑 2 次。C 错在形态——most_common 返回列表不是字典;B 顺序错——手机次数多优先;D 的平板只有 1 次进不了前二。第 7 篇的 Counter 是词频统计(第 8 篇)的主角,务必熟练。

第 9 题(A):defaultdict(int) 使不存在的键自动初始化为 0,+= n 因此等价于”从 0 开始累加”:北京 3+5=8、上海 2+1=3、广州 4。B 是”只取每组第一个值”的误解,C 是”后值覆盖前值”的误解(那是没有默认值工厂时才会发生的行为),D 忽略了解 defaultdict 的存在意义。

第 10 题(A):先 counts.get(w, 0) + 1 完成词频统计(第 8 篇),再按元组键 (-kv[1], kv[0]) 排序:次数降序优先,同次数(java 与 python 都是 3)再按单词升序,java 排在 python 前。这正是第 9 篇”多级排序 + 单级反转”的考点:-kv[1] 反转的是次数这一级,reverse=True 则会连单词顺序一起反转,得不到此结果。

编程题(共 3 题,附可运行参考解答)

编程题 1:词频统计与多级排序报表

题目:给定一个含重复元素的单词列表,用普通字典统计每个单词出现次数(不引入 Counter),然后按”次数降序、次数相同按单词升序”排序,用 f-string 左对齐/右对齐输出”单词 次数”两列报表。

参考解答:

words = ['python', 'java', 'python', 'c', 'java', 'go', 'python', 'go', 'c', 'java']
counts = {}
for w in words:
    counts[w] = counts.get(w, 0) + 1

for w, c in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])):
    print(f'{w:<8}{c:>2}')

运行结果:

java     3
python   3
c        2
go       2

关键点:counts.get(w, 0) + 1 是字典计数的最小惯用式(第 3 篇),排序键 (-kv[1], kv[0]) 是”主键降序、次键升序”的组合(第 9 篇),{w:<8}{c:>2} 是第五章的对齐格式化。三个知识点分别来自三章,串起来就是一个微型报表工具。

编程题 2:元组返回值与解包

题目:写一个函数 stats(nums),一次返回最大值、最小值、平均分三个值(以元组形式),调用方用解包同时接收,并格式化输出。

参考解答:

def stats(nums):
    return max(nums), min(nums), sum(nums) / len(nums)

scores = [88, 76, 92, 65, 84, 70]
hi, lo, avg = stats(scores)
print(f'最高 {hi},最低 {lo},平均 {avg:.1f}')

运行结果:

最高 92,最低 65,平均 79.2

关键点:return a, b, c 的语法糖是”打包成元组再返回”(第 2 篇),hi, lo, avg = stats(scores) 用解包一次收下三个值;sum(scores) / len(scores) 得到浮点平均分,配合 :.1f 保留一位小数(第五章)。注意平均分是 79.1666...,显示为 79.2 是格式化舍入,不是计算错误。

编程题 3:集合去重与共同成员

题目:两门课的报名名单都是列表,且 A 课名单内部有重复报名。用集合完成三件事:统计 A 课去重后的人数、输出按码点排序的 A 课名单、找出两课都选的人。

参考解答:

course_a = ['张伟', '李娜', '王强', '张伟', '赵磊', '李娜']
course_b = ['王强', '张伟', '赵磊', '周明', '孙丽']

unique_a = set(course_a)
print('A 课去重后人数:', len(unique_a))
print('A 课名单:', sorted(unique_a))
print('两课都选:', sorted(unique_a & set(course_b)))

运行结果:

A 课去重后人数: 4
A 课名单: ['张伟', '李娜', '王强', '赵磊']
两课都选: ['张伟', '王强', '赵磊']

关键点:set(list) 一行去重(第 4 篇),& 是交集运算,sorted 让集合的乱序输出变成可读的列表。1 次招生名单清洗 + 1 次交叉核对,全程没有循环,集合运算的威力正在于”把成员测试的复杂度从 O(n) 降到 O(1),把逻辑从循环变成表达式”。

综合任务:合并两个记录集合并去重、统计并输出报表

任务描述:两个小型成绩单 scores_a 与 scores_b 来自两次录入,存在两类脏数据:同一学号出现多条(以后录入的为准,视为补考更新);两份名单有重叠。请完成:①按学号合并去重(同号保留后一条);②按班级汇总人数、平均分、最高最低分;③用 Counter 统计每班人数;④按分数降序输出排名报表(姓名、学号、班级、分数四列,正确处理中文显示宽度)。

考察点:嵌套容器的遍历与字典键操作(第 3、6 篇)、defaultdict 分组聚合(第 7 篇)、Counter 计数、sorted 与 key 排序(第 9 篇)、按显示宽度对齐的中文报表(第 9 篇)。

参考实现:

import unicodedata
from collections import Counter, defaultdict

def display_width(text):
    return sum(2 if unicodedata.east_asian_width(ch) in 'WF' else 1 for ch in str(text))

def cell(text, width, align='left'):
    text = str(text)
    padding = ' ' * max(width - display_width(text), 0)
    return padding + text if align == 'right' else text + padding

def row(cells):
    return '  '.join(cells)

scores_a = [
    {'sid': '001', 'name': '张伟', 'cls': '一班', 'score': 88},
    {'sid': '002', 'name': '李娜', 'cls': '一班', 'score': 76},
    {'sid': '001', 'name': '张伟', 'cls': '一班', 'score': 92},   # 补考后更新
    {'sid': '003', 'name': '王强', 'cls': '一班', 'score': 65},
]

scores_b = [
    {'sid': '003', 'name': '王强', 'cls': '一班', 'score': 71},   # 补考后更新
    {'sid': '004', 'name': '赵磊', 'cls': '二班', 'score': 84},
    {'sid': '005', 'name': '孙丽', 'cls': '二班', 'score': 90},
    {'sid': '002', 'name': '李娜', 'cls': '一班', 'score': 80},   # 补考后更新
]

merged = {}
for r in scores_a + scores_b:
    merged[r['sid']] = r

print(f'去重合并后共 {len(merged)} 条记录(同号取后一条)')
for sid in sorted(merged):
    record = merged[sid]
    print(f'  {sid} {record["name"]} {record["cls"]} {record["score"]}')

print()
print('按班级汇总:')
cls_scores = defaultdict(list)
for r in merged.values():
    cls_scores[r['cls']].append(r['score'])

for cls, scores in sorted(cls_scores.items()):
    print(f'  {cls}  人数 {len(scores)}  平均 {sum(scores) / len(scores):.1f}  '
          f'最高 {max(scores)}  最低 {min(scores)}')

print()
group_counter = Counter(r['cls'] for r in merged.values())
print('按班级人数:', dict(group_counter))

print()
print(row([cell('排名', 4, 'right'), cell('姓名', 6), cell('学号', 6), cell('班级', 6), cell('分数', 4, 'right')]))
for rank, r in enumerate(sorted(merged.values(), key=lambda x: -x['score']), 1):
    print(row([cell(rank, 4, 'right'), cell(r['name'], 6), cell(r['sid'], 6),
               cell(r['cls'], 6), cell(r['score'], 4, 'right')]))

运行结果:

去重合并后共 5 条记录(同号取后一条)
  001 张伟 一班 92
  002 李娜 一班 80
  003 王强 一班 71
  004 赵磊 二班 84
  005 孙丽 二班 90

按班级汇总:
  一班  人数 3  平均 81.0  最高 92  最低 71
  二班  人数 2  平均 87.0  最高 90  最低 84

按班级人数: {'一班': 3, '二班': 2}

排名  姓名    学号    班级    分数
   1  张伟    001     一班      92
   2  孙丽    005     二班      90
   3  赵磊    004     二班      84
   4  李娜    002     一班      80
   5  王强    003     一班      71

逐项核对:merged[r['sid']] = r 用”学号当字典键、整条记录当值”完成去重,后写入的同号记录天然覆盖先写入的,001 的 92 覆盖 88、002 的 80 覆盖 76、003 的 71 覆盖 65,最终 5 人;defaultdict(list) 按班级收分后,平均分用 sum/len 计算并以 :.1f 显示;Counter 一行统计每班人数;排名表按分数降序,cell 助手处理了中文宽度让四列整齐对齐。整个任务只用了字典、列表、集合思想与三个标准库构件,没有引入任何新知识。

扩展思考:把”学号去重”换成”姓名去重”再跑一次,思考为什么同名的两个人会被错误合并、需要额外引入什么键才能正确区分;试试只靠本章知识能否给报表增加一列”班级内名次”(提示:第 9 篇的多级排序 + 枚举序号就够)。

学习自检清单

完成本章学习后,请逐项确认你能独立做到:

  • 说出 append、extend、insert、remove、pop、sort 的作用与返回值,知道哪些是就地修改哪些返回新对象。
  • 用切片对列表取子集、倒序、按步长取样,并解释切片返回新列表、原地修改与引用共享的区别。
  • 用元组打包多返回值并用解包接收,正确写出 a, *b, c = t 这类含星号的解包,并说明为什么 t[0] = 1 会报错。
  • 完成字典的增、删、查、改与遍历,区分 d[k]、d.get(k)、d.setdefault(k, v)、d.items() 的语义与返回值。
  • 用集合去重、判断成员(in),并说出 &、|、-、^ 四种运算的结果含义。
  • 解释”变量存的是引用”这句话,能预判 list1 = list2、copy.copy 与 copy.deepcopy 在嵌套结构上的行为差异。
  • 遍历嵌套容器(列表套字典、字典套列表),用 key 参数对嵌套结构做 max/min/sorted。
  • 在”计数”场景选用 Counter(most_common),在”分组收集”场景选用 defaultdict(list),并用纯字典完成同等功能。
  • 独立完成词频统计,并按次数、字母等多级规则输出排序报表(第 8、9 篇)。
  • 独立完成本篇综合任务,能解释”学号当键、记录当值”的合并去重为什么天然保留最新记录。

如果某一项无法打勾,请回到对应篇章重新练习。第六章的核心思想是:容器本身只是组织数据的骨架,排序、筛选、聚合才是让数据产生价值的动作;初学阶段最容易忽略的是”返回值形态”与”引用还是拷贝”这两个老问题——每个方法返回什么、修改了谁,决定了下一条语句怎么写、原数据会不会被意外改动。带着这套基本功,下一章进入第七章”文件与异常处理”:你会发现自己已经能用嵌套容器建模真实数据,第七章要解决的是”数据怎么落盘、程序出错怎么兜底”,而第六章养成的”先看返回值、再写下一行”的调试习惯,将继续帮你稳稳跨过每一道坎。测验不是终点,它只是你掌握程度的体检报告。