Python基础综合作业:函数、字典与异常处理的实战演练
最近帮我带的一位 Python 新人刚好写完基础阶段的第五次作业我顺手把这次作业涉及的知识点、踩坑过程和完整代码都整理了一遍。如果你也正在学 Python 基础或者刚开始带新人练手这份内容可以直接拿来当参考。这次的第五次作业题目量不算大但很有代表性它把前面学过的函数、字符串、列表、字典、循环和异常处理全部串到了一起还顺带加了一道文件读写的扩展题。换句话说前四次作业是“学会某一个知识点”第五次开始就是“综合运用”这两者的难度差一截。我会在下面把每一道题的思路、代码、易错点都拆开讲最后再汇总一份常见报错排查表方便你照着自查。这次作业的完整目标可以概括成三句话能写带返回值的函数能熟练操作字典和列表能读懂并处理常见运行时报错。接下来直接进入正题。1. 作业整体设计与思路拆解1.1 为什么第五次作业是分水岭很多教程会把函数安排在很靠后的章节但我带人入门时会刻意早点引入函数。原因是 Python 这门语言真正的生产力不在你写了多少行顺序代码而在你能不能把重复逻辑抽象成函数能不能用容器类型把数据组织好。第五次作业的定位就是让初学者从“照着例子敲”过渡到“自己设计逻辑”。这次作业一共五道题设计上分成了三个梯度基础巩固题回文串判断、水仙花数考察函数定义、字符串操作、循环和列表推导式。综合运用题单词频次统计、简易计算器考察字典、函数对象、异常处理。扩展挑战题把统计结果写入文件考察 with 语句、文件路径和编码人狗对战小游戏考察随机数、循环控制和函数合作。这样安排的逻辑是先保证基本语法不出错再练习容器类型之间的配合最后落到“程序能跟外部环境交互”这个真实需求上。文件读写那一题我特别建议认真做因为很多初学者学了半年 Python还是只会用 print 输出结果一旦要保存数据就卡住这是很明显的短板。1.2 作业范围与前置知识要求在动笔之前我建议你先检查自己是不是已经掌握下面这些内容。如果哪一项还模糊做作业时大概率会卡壳函数定义与返回值def 开头return 返回结果不能把 print 当 return 用。字符串常用操作lower()、split()、strip()、切片、isalnum()。列表与字典的增删改查列表推导式、字典的 get 方法、遍历 items()。循环控制for 循环遍历容器while 循环处理“不确定次数”的场景。异常处理基本结构try / except / else / finally 的简单使用。这里有一个通用原则可以记住练习题的意义不是“把题目做对”而是“你能不能用已学的知识把一道没见过的题拆解成熟悉的小步骤”。如果你做作业时发现自己每一步都认识但组合不起来那说明前面几个知识点的应用还不够熟可以回头把列表推导式和字典的 get 方法再练两遍。2. 核心知识点解析与避坑要点2.1 函数别在函数里 print 结果要 return初学函数时最常见的毛病是把 print 写在函数内部然后发现调用函数时看不到返回值或者想把结果再拿去跟其他数据比较时无从下手。我把原则强调得非常简单函数是“计算单元”不是“显示单元”。举个例子判断回文串这一题正确做法是函数返回布尔值def is_palindrome(s): s .join(ch for ch in s.lower() if ch.isalnum()) return s s[::-1] # 调用方来决定怎么用这个结果 print(is_palindrome(A man, a plan, a canal: Panama)) # True if is_palindrome(hello): print(是回文) else: print(不是回文)这样设计的好处是同一个函数既能用来直接打印也能放进 if 条件里判断还能放进列表推导式批量检测多个字符串。函数一旦返回了具体值调用方就拥有了“再加工”的自由这才是函数真正的价值。注意如果你把 print 写在函数里调试时会发现结果虽然能显示但函数返回的是 None。后面写项目时这种情况会导致一堆奇怪的逻辑错误排查起来很头疼。再补充一个作用域问题。很多新人在函数内部直接写count count 1如果 count 是在函数外定义的全局变量运行时会报 UnboundLocalError。原因是 Python 认为你在函数内给 count 赋了新值它就成了局部变量于是不再读取外部的全局变量。解决办法是用参数把值传进去、用返回值带出来而不是依赖全局变量。2.2 字典与列表一个记频率一个存结果第五次作业里单词频次统计是绝对的核心题。这道题表面上考字符串处理实际考的是字典的“计数”用法。几乎所有跟“统计”“次数”“频率”相关的需求到最后都是用字典解决的比如统计用户行为、统计订单状态、统计日志错误等等。掌握字典计数的关键是先搞懂dict.get(key, default)这个方法freq {} for word in words: freq[word] freq.get(word, 0) 1这行代码的意思是如果 word 已经在字典 freq 里就取出当前次数加 1如果不在就按初始值 0 加 1。这样写既简洁又安全不会因为第一次遇到某个键而触发 KeyError。列表推导式是另一条主线。水仙花数那题如果不用推导式得写三四行循环加 append用推导式一行就能出结果。我建议基础比较薄弱的同学第一次可以先写标准 for 循环跑通之后再改写推导式两步走既能理解流程又能掌握语法。2.3 异常处理程序崩溃不是最可怕的可怕的是你不知道怎么读报错第五次作业的简易计算器核心考点是处理用户输入可能引发的异常。完整的计算器可以这样写def add(a, b): return a b def subtract(a, b): return a - b def multiply(a, b): return a * b def divide(a, b): return a / b operators { : add, -: subtract, *: multiply, /: divide, } def main(): while True: user_input input(请输入算式例如 3 5输入 q 退出) if user_input.strip().lower() q: break parts user_input.split() if len(parts) ! 3: print(格式不正确必须用空格分隔例如3 5) continue a_str, op, b_str parts try: a float(a_str) b float(b_str) result operators[op](a, b) except ZeroDivisionError: print(除数不能为 0请重新输入) except KeyError: print(不支持该运算符支持 - * /) except ValueError: print(请输入数字) else: print(f结果{result}) if __name__ __main__: main()这里有几处设计值得展开讲用字典operators把运算符字符串映射到函数对象这是 Python 里“函数作为一等公民”的典型用法比写一长串 if/elif 清晰得多。split() 默认按空格切分所以要求用户输入时数之间必须带空格这是故意的目的是简化解析逻辑先保证核心运算正确。try 块里不要塞太多无关代码只把可能出错的运算、输入转换放进 try正常输出放到 else看起来更规整。在 main() 里用 while True break 实现“反复计算直到用户退出”这比只跑一次更贴近实际工具的感觉。实操心得我见过很多新人写计算器第一版能跑通加法就把代码停下不继续了等到被要求加上“连续计算”和“错误处理”时才发现代码结构一团乱。最好的做法是先把框架搭出来——主循环、函数映射、异常分支——然后一步步补功能。框架对了后面加需求只是往里填函数的事。3. 作业题目详解与完整代码实现3.1 回文串判断从题目语义到函数设计题目要求写一个函数判断给定字符串是否是回文回文指正着读和倒着读都一样且忽略大小写、空格和标点符号。这道题的坑不在算法而在“清理字符串”。英文里常见写法是只保留字母和数字然后统一转小写def is_palindrome(s): cleaned [] for ch in s.lower(): if ch.isalnum(): cleaned.append(ch) return cleaned cleaned[::-1]如果换成列表推导式就是def is_palindrome(s): cleaned [ch for ch in s.lower() if ch.isalnum()] return cleaned cleaned[::-1]这里我把 cleaned 定义为列表而不是字符串因为 list 直接支持[::-1]反转比较写起来更直观。如果你想看字符串版本也没问题def is_palindrome(s): s .join(ch for ch in s.lower() if ch.isalnum()) return s s[::-1]两种写法都可以但你要能解释每一步在干什么先转小写再过滤非字母数字再反转比较。注意isalnum() 对中文也是返回 True所以这个函数对“上海自来水来自海上”同样有效。不过中文分词不如英文直接练习阶段用英文串就好。3.2 水仙花数用列表推导式冲掉循环行数题目要求输出所有三位数中的水仙花数即一个三位数各位数字的立方和等于它本身。经典答案是 153、370、371、407。非推导式写法result [] for num in range(100, 1000): digits str(num) total 0 for d in digits: total int(d) ** 3 if total num: result.append(num) print(result)推导式写法result [num for num in range(100, 1000) if sum(int(d) ** 3 for d in str(num)) num] print(result)推导式看着炫但逻辑其实没变遍历三位数把每一位转 int 后求立方再求和判断是否等于原数。我建议你在动手前先自己在纸上写出sum(int(d) ** 3 for d in str(num))的求值过程确认理解无误再用它。细节提示范围一定要写 range(100, 1000)因为三位数是从 100 到 999。很多人写 range(1000) 也能跑但会把 0、1 等个位数也算进来虽然数字上不影响水仙花数结果逻辑上却不够严谨。3.3 单词频次统计字典 字符串清理的黄金组合题目要求给一段英文文本统计每个单词出现的次数并输出频次最高的 5 个词。先看完整代码def clean_text(text): # 把标点符号替换成空格再转小写 for ch in .,!?;:()[]: text text.replace(ch, ) return text.lower() def word_frequency(text): text clean_text(text) words text.split() freq {} for word in words: if word: freq[word] freq.get(word, 0) 1 return freq def top_n(freq_dict, n5): sorted_items sorted(freq_dict.items(), keylambda item: item[1], reverseTrue) return sorted_items[:n] if __name__ __main__: sample Python is powerful. Python is easy to learn. Python is popular, and Python is fun! freq word_frequency(sample) print(freq) print(Top 5:) for word, count in top_n(freq, 5): print(f{word}: {count})这里的关键步骤有三个清理文本把常见标点符号替换成空格再统一转小写。切分split() 是根据空白字符切分的所以前面的清理步骤必须把标点变成空格否则fun!会被当成一个词。计数用freq.get(word, 0) 1实现“有则加一无则初始化”。输出排序用了sorted(freq_dict.items(), keylambda item: item[1], reverseTrue)这一行解释一下items() 返回键值对元组key 指定按第二个元素次数排序reverseTrue 表示降序。实操心得很多初学者在这里会写sorted(freq_dict)结果只对 key 排序输出的全是单词而不是次数。这个问题我在别人代码里见过无数次关键是要意识到 sorted 默认遍历字典的键比如要遍历键值对必须用 items()。3.4 简易计算器从“能算”到“不怕出错”这个题的能力目标不是写出四则运算而是让程序在用户给错输入时不崩溃。我在上面 2.3 里给了完整代码这里再补充两个常见优化方向支持连续运算比如用户输入2 3 * 4时能不能按运算符优先级计算这需要用到“中缀表达式求值”的思路基础阶段可以先不实现但可以留作扩展。支持更多运算符比如%取余、**幂运算。扩展起来很简单只需要在 operators 字典里加一对映射就行。我建议你完成基础版之后至少给自己加一个“指数运算”体会一下“加功能加字典项”的爽快感。这也是函数映射设计的好处。3.5 扩展题一把结果保存到文件把单词频次结果写入文件是练习文件读写的好机会。完整代码def save_frequency(freq_dict, filepath): with open(filepath, w, encodingutf-8) as f: for word, count in sorted(freq_dict.items(), keylambda item: item[1], reverseTrue): f.write(f{word}: {count}\n) save_frequency(freq, word_frequency.txt)几个关键点必须显式指定encodingutf-8否则在中文 Windows 环境下open 默认编码可能是 gbk写中文就会报错。使用with语句管理文件Python 会在代码块结束后自动关闭文件哪怕中途异常也不会泄露句柄。写入时把排序放在写入循环里这样保存下来的文件就是按频次降序排列的方便直接阅读。实操心得如果只想快速看结果用 print 输出到屏幕没问题但只要数据量一多、需要保存中间结果就必须用文件。文件读写这个能力做项目时基本绕不开早晚要练。3.6 扩展题二人狗大作战小游戏这个扩展题来自“用函数模拟回合制战斗”的思路代码非常简单但能很好地练习“多个函数配合 while 循环条件 random 随机数”这几个知识点import random def attack(attacker, defender): damage random.randint(5, 15) defender[hp] - damage print(f{attacker[name]} 攻击 {defender[name]}造成 {damage} 点伤害) return defender[hp] 0 def main(): player {name: 你, hp: 100} dog {name: 大黄狗, hp: 80} round_count 0 while player[hp] 0 and dog[hp] 0: round_count 1 print(f\n--- 第 {round_count} 回合 ---) input(按回车键攻击...) if attack(player, dog): print(你赢了大黄狗倒下了。) break if attack(dog, player): print(你输了已经被大黄狗击败。) break print(f当前状态你的血量 {player[hp]}狗的剩余血量 {dog[hp]}) if __name__ __main__: main()这段代码里我把人和狗都表示成字典name 是名字hp 是生命值。攻击函数接收两个字典修改防守方的 hp然后返回“对方是否已经倒下”。这样主循环就可以用 while player[hp] 0 and dog[hp] 0 控制战斗继续一旦某一边 hp 归零循环自然结束。如果你想让游戏更有趣可以扩展出暴击、躲避、技能冷却这些机制每次扩展本质上都是往字典里加字段、往战斗函数里加逻辑。这个方向非常适合练习函数拆分也贴近游戏开发的基础套路。4. 常见错误与排查技巧实录4.1 最容易踩的五个运行时报错下面是我整理的基础阶段高频报错按出现频率从高到低排列报错信息出现原因快速解决方案NameError: name xxx is not defined变量名拼写错误或函数内使用了未定义变量检查是否少字母、大小写是否一致检查变量是否在作用域内先赋值IndexError: string index out of range访问字符串/列表时下标越界检查索引是否超过 len() 范围注意字符串最后一位索引是 len-1KeyError: xxx字典里没有这个键直接用了dict[key]改用dict.get(key, 默认值)或者先判断 if key in dictUnboundLocalError: local variable x referenced before assignment函数内变量与外部全局变量同名又给该变量赋值了不要依赖全局变量用参数传入、返回值带出ZeroDivisionError: division by zero用户输入除数为 0或者循环里出现了除以 0在除法前判断是否为 0或用 try/except 捕获这些报错信息本身并不难懂但新手一看到红字就慌。我的建议是先把最后一行报错复制到搜索引擎看明白是什么类型再回头看具体是哪一行触发的。别从头读一遍大片 Stack Trace那样效率很低。提示读报错信息时优先看两个位置——最后一行错误类型和报错信息里带File xxx.py, line N的那一行出错位置。这两条信息就足够定位大部分问题了。4.2 排查步骤五步定位法我教新人调代码时反复强调一套固定流程遇到任何报错都按这个步骤走读最后一行报错确认错误类型比如 KeyError 还是 TypeError。找到报错信息里指出的文件行号打开源码看那一行。在那一行前后各加 print把变量值打出来确认实际数据和预期是否一致。思考这行代码在什么数据条件下会报错反向构造“最小复现案例”。修改代码跑一次确认再顺手补充一次异常情况的测试。举个例子你写了freq[word] 1但报 KeyError。按流程走第 3 步打印 word发现第一次遇到新词时字典里根本没有这个键所以直接加会失败。改成freq[word] freq.get(word, 0) 1就能解决。整个过程不到一分钟但无头苍蝇式地乱试可能折腾半小时。4.3 语法正确但结果不对的隐藏 Bug比报错更隐蔽的是“程序能跑但结果不对”。这里列出我在批改第五次作业时常看到的三类问题字符串没有清理干净导致Python,和Python被当成两个词。解决方案是在切分前把标点统一替换成空格。函数内使用了全局变量导致多次调用结果互相污染。解决方案是把所有数据都通过参数传进函数返回值交给调用方处理。把 return 写在循环体内部导致只执行一次就退出。这里需要确认 return 的缩进层级如果想返回整个循环的累积结果return 必须放在循环外面。排查这类问题的方法依然是打印中间变量。如果你发现words里带有Python,就立刻明白标点没处理干净。如果是函数重复调用结果不对你就把函数内变量的当前值打出来看看是否被上一次调用影响。4.4 环境与工具层面的常见问题这次作业本身不依赖第三方库但如果你的运行环境还没配好也会卡住。下面几个问题我见得多在终端输入python --version提示“不是内部或外部命令”说明 Python 没有加入环境变量需要去安装时勾选“Add Python to PATH”或者手动配置环境变量。使用 VS Code 时右下角提示“没有选择解释器”运行代码报“Please select a valid Python interpreter”。解决方法是 CtrlShiftPMac 是 CmdShiftP输入Python: Select Interpreter选择你装的 Python 版本。文件路径里有中文或空格导致open(word_frequency.txt)找不到文件。解决方案是用相对路径确保当前工作目录就是代码所在目录或者打印os.getcwd()看当前路径。实操心得很多初学者遇到环境问题会误以为是自己代码写错了白白浪费大量时间。我的建议是只要报错信息里包含“No module named”“interpreter”“pipeline”先不要改代码优先排查环境。代码报错往往在“不可名状”的语法位置而环境报错通常很直接地说缺什么。收尾的一点个人经验带过几轮新人之后我发现第五次作业这个节点特别有意思。前四次作业大家都能按时交从第五次开始就会出现明显的分层一部分人开始理解“函数 数据结构”的组合威力另一部分人还在靠记忆样例代码硬拼。我个人的建议是这次作业别急着把所有题目都一次写完。你可以先独立完成前两题然后对照答案检查思路第三题开始尝试自己设计数据结构和函数拆解哪怕写得丑也行。写完以后故意把输入换几种极端情况比如空字符串、全标点文本、除数为 0看看程序会不会崩。会崩不要紧正好用上上面那套排查流程。最后再分享一个小技巧这次作业里每一道题你都可以在末尾加一个“运行一下”的注释块说明输入什么、期望输出什么。别小看这个习惯它能逼你把手里的代码变成可验证的小工具远比写完就跑一遍默认输入要实用得多。等到你做真实项目、需要维护旧代码时就会发现这个习惯有多值钱。