Python练习题怎么破?用pytest和异常处理搞定第8章
简介这是一份针对《Python编程实践》第8章的编程练习题配套解析资料面向正在学习Python函数、递归与算法基础的高校学生或自学读者。内容覆盖计算整数各位数字之和、递归公式求和、十进制向十六进制、八进制、二进制转换、回文素数筛选、任意多个数连乘及折半查找等典型习题并配有作者的实现思路与可运行代码示例能帮助读者理解作用域、递归边界与进制转换原理强化编程实践能力。压缩包内为1个PDF文档约224KB采用常用PDF格式便于阅读和打印清晰编排了各题答案、思路说明和示例程序适合作为课堂巩固或备考复习的参考资料。这份资源发布后已有1934人学习使用通过课后对照练习可快速定位自己的薄弱环节验证对函数、递归等知识点的掌握程度。1. 第8章练习题卡人的不是语法是输入输出翻到《python编程实践》第8章练习时很多题目的题干只有两三行读一个文件、处理字符串、返回计算结果。真正做起来才发现卡住的地方全在“边界”上文件编码不对、空行要不要跳过、返回值类型跟判题用例对不上。这种情况对刚学完基础语法的人尤其明显因为语法知识能帮你写出“看起来对”的代码练习题却要求你的代码在多种输入下都给出确定行为。这篇文章就围绕陈波、刘慧君这本教材第8章常见的练习方式来展开把函数封装、文件读写、异常处理这几类习题的解答骨架拆开讲并给出可直接套用的判题方法。适合正在刷章末习题的初学者也适合帮人改题、想要一套快速验证方案的同事。2. 做解答前先用 pytest 给第8章练习建好判题护栏2.1 为什么练习题要先出断言而不是先写 print练习题的常见误区是打开编辑器把函数写完后用print打印几个结果看着对就算通过。第8章开始题目往往带文件读写和返回值约定print只能确认“某一次输入”的结果一旦题目要求同时处理空数据、缺字段、异常输入这种验证方式就给不出答案了。我一般的做法是先写一个测试文件把题目要求变成具体断言再回头写实现。这样做有两个直接好处一是你不需要每次手动敲输入跑一条命令就能检查所有用例二是当你改代码时测试会提醒你哪些行为被改坏了。这里的核心思路是把“练习题”当成一个小型函数验收任务来对待先确定输入类型、输出类型、异常行为再谈实现。2.2 最小判题骨架tests/test_solution.py 与第一条 pytest 命令假设第8章练习里有一道这样的题写一个函数average_from_csv(path)读取一个两列 CSV 文件第一列是科目名第二列是分数返回平均分。最小测试骨架可以写成import pytest from chapter8 import average_from_csv def test_average_normal(tmp_path): data tmp_path / scores.csv data.write_text(语文,80\n数学,90\n, encodingutf-8) result average_from_csv(data) assert result 85.0逻辑说明tmp_path是 pytest 的内置 fixture它会在每次测试运行时生成一个临时目录测试结束后自动清理避免测试产生的临时文件污染你的工作目录。这里直接把tmp_path / scores.csv作为参数传给被测函数是因为函数接受路径对象而不是要求拼接字符串路径。参数说明chapter8是我给练习题模块起的名字实际使用时要改成你自己的模块文件名。data.write_text(...)的encodingutf-8必须写否则在 Windows 上可能因为默认编码不是 UTF-8 而直接报错。运行测试的命令python -m pytest -q-q表示安静模式只显示通过的用例数和失败信息。如果电脑上没有 pytest先执行pip install pytest再做环境准备。第一次跑通这条命令比多写两行print更值得。2.3 文件类习题的临时目录 fixturetmp_path练习题里凡是要“读一个文件”的我都会用tmp_path构造测试数据而不是在项目目录里写死一个input.txt。原因很简单多个测试用例如果共用同一个真实路径后一个用例可能被前一个用例留下的文件影响测试就不再独立了。def test_average_empty_file(tmp_path): data tmp_path / empty.csv data.write_text(, encodingutf-8) with pytest.raises(ValueError): average_from_csv(data)这段代码用pytest.raises断言“空文件时函数必须抛出 ValueError”。这里有个容易被忽略的点练习题的判题器通常不会告诉你它传入了什么文件只会告诉你“这个用例失败”。因此你要在写实现前先定义好空文件、只有表头、重复分数等边界行为而不是等提交后才被动排查。如果不使用tmp_path就得用tempfile.TemporaryDirectory手动创建和删除目录代码量会明显增加而且容易在异常分支里漏掉清理逻辑。pytest 自带这套管理练习题阶段没必要自己再造轮子。2.4 环境报错与解释器选择另一个常见问题是 VSCode 打开项目后测试总报ModuleNotFoundError或者cannot be resolved against python helper roots。这通常不是代码问题而是解释器选择错误。在 VSCode 里按CtrlShiftP执行Python: Select Interpreter选择当前虚拟环境对应的解释器路径。如果项目用 venv路径一般形如.venv/Scripts/python.exeWindows或.venv/bin/pythonLinux/macOS。cannot be resolved against python helper roots这个报错多数情况下是 Python 插件找不到解析用的根目录手动指定解释器并重载窗口即可解决。建议在项目根目录放一个.vscode/settings.json{ python.defaultInterpreterPath: .venv/bin/python, python.testing.pytestEnabled: true }配置后的说明python.testing.pytestEnabled让 VSCode 的测试面板识别 pytest 用例左侧测测试图标里可以直接看到哪些用例通过、哪些失败。这一步完成后环境不再是干扰项后面所有练习都在同一套验证流程下进行。3. 函数与文件类解答模板练习题的三种标准骨架3.1 成绩单统计DictReader 类型转换 写回文件第8章练习里最常见的一类题是“读成绩单统计后写回文件”。以 CSV 文件为例题目要求可能是按行读取计算平均分把最高分和最低分的姓名写进summary.txt。一个可复用的解答骨架如下import csv from pathlib import Path def analyze_scores(input_path, output_path): scores [] with open(input_path, newline, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: name row[姓名].strip() score float(row[分数]) scores.append((score, name)) if not scores: raise ValueError(empty score file) scores.sort(reverseTrue) highest scores[0] lowest scores[-1] average sum(s for s, _ in scores) / len(scores) output Path(output_path) output.write_text( f平均分: {average:.2f}\n最高: {highest[1]} {highest[0]:.2f}\n f最低: {lowest[1]} {lowest[0]:.2f}\n, encodingutf-8, ) return average逻辑说明csv.DictReader会把第一行当作列名后续每一行返回一个字典键就是表头。代码里先对字段做strip()处理避免“姓名后面多个空格”导致排序结果和预期不一致。float(row[分数])是必须的类型转换因为 CSV 读出来的内容全是字符串直接比较大小会把90排在100前面。参数说明newline在写入 CSV 时是必需的读取时不加通常也没问题但统一写成newline可以避免 Windows 下出现多余空行。{average:.2f}是格式化输出保留两位小数。sum(s for s, _ in scores)里的_用来忽略元组里的姓名部分这是 Python 语法里的常见写法表示“这个位置有值但我用不到”。3.2 频率统计Counter 与排序稳定性另一类高频练习是统计文本中单词出现次数。这类题的考点往往不是“数单词”本身而是排序规则出现次数从大到小次数相同时按字母顺序排列。from collections import Counter import re def word_frequency(text, top_n5): words re.findall(r[a-zA-Z], text.lower()) counter Counter(words) ordered sorted(counter.items(), keylambda item: (-item[1], item[0])) return ordered[:top_n]逻辑说明先统一转小写再用正则[a-zA-Z]切出单词避免标点和换行干扰计数。Counter本身是一个字典子类它的items()返回(单词, 次数)列表。排序的key写成(-item[1], item[0])意思是先按次数的负值排次数大的负值小、排前面次数相同时再按单词字母顺序。参数说明top_n5是可选参数练习题里如果要求“输出前 10”直接调用时传入word_frequency(text, 10)。这里有个值得注意的细节Counter.most_common(n)只按次数排不保证次数相同的时候按字母排所以不要用它替代这里的手写sorted。第一次写这类题的人最容易在这里丢分。3.3 文本可视化先画文本条形图再决定是否引入第三方库有些练习升级为“把统计结果可视化”。不要一上来就pip install matplotlib先看题目到底要求什么。如果只要求“直观展示次数差异”一个纯 Python 的文本条形图就够for word, count in ordered[:5]: bar # * count print(f{word:12} {bar} {count})输出类似python #### 4 list ### 3 dict ## 2逻辑说明# * count把数字转换成对应长度的字符串f{word:12}让单词左对齐并占用 12 个字符宽度。这段代码的意义是用最快方式确认数据分布再判断是否需要更复杂的绘图。如果题目明确要求输出图像文件再引入第三方库不迟。这也是一个典型的“python数据分析与可视化”的初级场景先用文本形式理解数据再决定用什么工具来呈现。练习阶段很多人被安装包和图形界面卡住反而忽略了我们第一步要验证的是统计逻辑本身。3.4 类型转换的三个隐蔽坑练习题里的类型转换往往比看起来复杂。以下三个坑在判题器里出现频率极高表达式结果说明float()ValueError空字符串无法转浮点int(3.9)3向零截断不是四舍五入round(2.5)2银行家舍入四舍六入五取偶int(3.9)得到3这是很多人第一次接触时没想到的题目如果要求“四舍五入到整数”要用round(3.9)或int(3.9 0.5)而不是直接int()。round(2.5)的结果是2也是经典坑点。Python 的round对.5这类值采用“取偶”规则round(3.5)反而等于4。如果练习题要求保留一位小数先确认它要的是”四舍五入“还是“round 行为”必要时用Decimal明确舍入方式否则同样的一份代码在不同 Python 版本下的表现可能不一致。类似的还有abs函数它只改变负数符号不会改变数值的精度不要指望abs来做舍入。4. 异常处理类解答的边界判题器不提示的扣分点4.1 空文件、缺字段、编码不一致先于功能处理练习题的判题器通常会跑多组输入但不会告诉你具体是哪组失败。排在第一位的隐藏扣分点是空文件。函数读到的文件可能一行内容都没有这时你的代码是返回一个空列表、返回0还是抛异常这个决定必须写清楚因为判题用例会把它当作预期行为来比对。第二位是缺字段。row[分数]如果遇到一行只有一列会抛出KeyError。第三位是编码不一致比如文件本身是 GBK 保存的你用encodingutf-8打开会抛UnicodeDecodeError。我处理这类问题时的顺序是先处理空输入再处理字段缺失最后处理编码。空输入用if not rows判断字段缺失用row.get(分数)配合一个哨兵值来识别编码问题则在打开文件时先用utf-8-sig兼容带 BOM 的 UTF-8 文件。utf-8-sig会剥离开头的 BOM 标记对大多数教材练习中的 CSV 文件来说比直接utf-8更稳。4.2 收集异常而不是让程序中断练习题的数据文件里可能混入几行脏数据。一种粗暴做法是遇到坏行就raise整个程序停掉但这通常不是判题器想要的行为。更实际的方案是记录坏行并继续处理最后把统计信息和错误数量一起返回def parse_scores(path): valid_count 0 total 0.0 errors [] with open(path, newline, encodingutf-8) as f: for line_number, line in enumerate(f, start1): try: score float(line.strip()) except ValueError as exc: errors.append((line_number, str(exc))) continue total score valid_count 1 return {total: total, count: valid_count, errors: errors}逻辑说明enumerate(f, start1)让坏行带有行号方便回看数据文件时定位问题。except ValueError是精确捕获它不会掩盖FileNotFoundError或MemoryError这类和单行数据无关的问题。errors列表收集每次转换失败的详情调用方可以根据题目要求决定是打印、写日志还是忽略。这里的关键是“别裸用except Exception”。练习题的判题器可能会故意传一个不存在的文件路径如果你的函数把FileNotFoundError也吞掉并返回空结果题目就要求你“区分文件不存在和数据为空”这个行为裸捕获会让两种场景混为一谈。4.3 练习题的异常设计表写练习题解答时我建议为每个函数明确三种情况下的行为正常输入、边界输入、错误输入。可以参考下面的表格来设计场景建议行为不建议行为文件不存在抛FileNotFoundError返回空列表空文件抛ValueError并给出说明返回0单行格式错误记录到错误列表直接中断程序字段缺失用dict.get判断直接访问下标这个表格本身就是一个可执行的检查清单。每写完一个函数对照它问一遍如果文件是空的我的函数会怎样如果第一行就是坏数据会怎样如果数字字段是负数会怎样大多数练习题的扣分点都在这些“如果”里而不是在正常路径上。5. 一道题一个 run 入口把第8章解答变成可复跑的个人题集5.1 给每个解答保留最小验证入口练习题做完了不代表结束。我会在每个解题文件底部保留一个if __name__ __main__入口方便随时手动验证。入口通常接收一个文件路径参数指向真实数据文件import argparse if __name__ __main__: parser argparse.ArgumentParser(descriptionRun answer for chapter 8) parser.add_argument(--input, requiredTrue, helppath to input file) parser.add_argument(--output, defaultresult.txt, helppath to output file) args parser.parse_args() result analyze_scores(args.input, args.output) print(faverage: {result:.2f})逻辑说明argparse把命令行参数转成 Python 对象--input和--output分别是输入和输出路径运行命令类似python answer_8_3.py --input data/scores.csv --output out.txt。这样做的好处是题目换一批数据文件时不需要改代码只需要换路径。5.2 日志打到文件回看错在哪个用例手动验证之外我把调试信息写到日志文件里。logging的常用配置是同时输出到控制台和文件import logging logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, filenamedebug.log, )这样可以保留每次运行的记录。练习题最讨厌的一点是“刚才还好好的改了别处就坏了”有了日志文件直接对照最后一次的运行记录就能定位是哪次改动引发的问题。5.3 一个可长期使用的收尾命令最终我会把验证统一成一条命令把所有测试和样例数据一起检查python -m pytest -q --tbshort python answer_8_3.py --input data/scores.csv --output out.txt--tbshort控制失败时的堆栈长度保留最关键信息。整个流程是先跑自动化用例再跑真实数据入口两者都通过才算这道题结束。后续再做其他练习时延续这套结构积累下来的就不是零散代码而是一个可以随时回归验证的个人题集。本文还有配套的精品资源点击获取