Python第五次作业全解析:字符串处理、嵌套列表与函数封装

发布时间:2026/10/20 18:36:39
Python第五次作业全解析:字符串处理、嵌套列表与函数封装 1. 作业全貌这回到底考了什么先交代背景这份“python第五次作业”来自某高校计算机基础课程的阶段训练整体难度比前四次上了一个台阶。前面几次基本还在讲变量、类型转换、if/else、for 循环这些地基到第五次直接进入两个新领域字符串的批量处理和列表的嵌套操作外加一道函数封装的小题。也就是说从第五次开始代码不再是“从上到下念一遍”而要考虑“这段逻辑我能不能拆成一个函数”“这个列表里的每个元素又是个列表我该怎么取数”。我做这份作业时最大的感受是前三道题用 30 分钟能写完第四道题嵌套列表去重合并卡了我快两小时。卡点不是语法不熟而是思维没转过来。前四次作业写的都是“一维”的数据第五次突然给你一个列表里面的元素还是一个列表你要先想清楚“我现在拿到的是什么类型”再去想“我想对它做什么”。这份作业适合两类人参考一类是正在学 Python 基础、刚接触字符串和嵌套列表的同学可以拿它当“阶段自测题”另一类是已经学完函数和列表、想找点综合练习的初学者。它的价值不在题本身多难而在于这几道题几乎覆盖了“入门期最容易混淆的概念”字符串是不可变类型、列表是可变类型、函数传参传的是引用还是值、切片能不能修改原序列。做完这五道题再去学字典和文件读写会顺很多。2. 题型设计与思路拆解为什么要这么安排2.1 前两道题的“陷阱式”设计第一题是给定一段字符串要求统计其中每个字符出现的次数并按出现次数降序输出。第二题是给定一个包含学生姓名和成绩的嵌套列表要求按成绩从高到低输出学生名次。这两题放一起我当时觉得“这不是一个考点吗”后来仔细看才发现第一题考的是字符串遍历 字典计数第二题考的是嵌套列表的索引 排序。表面都是“排序输出”实际训练的能力完全不同。第一题如果你用字符串.count()来数每个字符会发现效率很低——count 每次都要遍历整个字符串如果字符串很长时间复杂度是 O(n²)。正确做法是遍历一次用字典边读边记。第二题的关键则在于嵌套列表里的每个子列表是[姓名, 成绩]这样的结构排序时要通过key参数指定按第二个元素排。我最初直接用sorted(students)结果按姓名字典序排了成绩完全没起作用。这个坑踩了才发现sorted 默认按子列表的第一个元素排不是按你想的那个字段排。这题的价值就是逼你在排序时主动思考“我要根据哪个键排”而不是想当然拖过整个列表。2.2 第三题函数封装的设计意图第三题要求把一段重复出现的“判断闰年并返回天数”逻辑封装成函数。表面上是考闰年判断的 if 嵌套实际上考的是**“什么时候该写函数”**。入门阶段最容易犯的毛病是整个程序从头写到尾中间有一段代码用到了三次就直接复制粘贴三次。这样不是不能运行问题是第一如果逻辑要改你得改三处漏改一处就出 bug第二代码可读性差别人读你的代码要在三个地方看到一模一样的内容会忍不住皱眉。这题合格的标准不只是“写一个函数”而是“这个函数能不能被复用”。函数名要起得清楚参数要只传必要的数据返回值要明确——是返回True/False还是返回天数我写的时候选的是“输入年份返回该年的天数”这样函数内部先判断闰年再返回 365 或 366。这样设计的话后续加“某个月有多少天”的功能可以直接复用这个函数。2.3 第四题嵌套列表的“地狱级”开始第四题是给定一个嵌套列表里面部分子列表有重复比如[[1, 2], [2, 3], [1, 2], [4, 5]]要求去重并合并相邻区间比如[1, 2]和[2, 3]合并成[1, 3]。这道题是我个人认为整份作业最有价值的一题因为它把去重、排序、区间合并三个考点捏在了一起。如果你一上来就写代码大概率会乱。正确顺序应该是先去重用set或遍历判断再排序按子列表第一个元素排最后合并遍历比较当前区间的起点和上一个区间的终点是否接得上。每一步单独拆开都不难但组合在一起“先做什么后做什么”就决定了代码是 30 行还是 80 行。我见过有同学一上来就合并结果重复元素导致合并逻辑完全错乱排错了序又接错了区间最后 debug 到崩溃。这类题的通用解法口诀就是先排序后合并去重尽量往前放。2.4 第五题看似简单实则综合第五题是读取一个文本文件统计里面的单词数量并输出出现次数最多的 5 个词。这题只要学过文件操作就能做但真正做的时候会发现文件编码utf-8还是gbk、单词分隔符空格、标点、换行全都有吗、大小写要统一Python和python算不算同一个词——这些问题作业文档里一个都没说。也就是说这题考的不只是技术还有需求澄清能力当需求有歧义时你作为开发者要主动做合理假设并在代码注释里写清楚你的假设是什么。我当时的假设是全部转小写、按非字母字符切割、空字符串不计入。后来跟同学对答案发现大家在这三个假设上各不相同跑出来的结果当然全不一样。从教学设计角度看这其实是在模拟真实工作中的场景——需求文档永远有缝隙你要自己把它填补上。3. 实操过程与核心代码实现完整跑通第五次作业3.1 第一题字符串统计从 O(n²) 到 O(n)题目给了这么一段文本hello python, this is the fifth homework. hello world!要求统计每个字符出现的次数不区分大小写忽略空格和标点按次数降序输出。我的第一版代码长这样text hello python, this is the fifth homework. hello world! text text.lower() result {} for char in text: if char.isalpha(): result[char] result.get(char, 0) 1 sorted_items sorted(result.items(), keylambda x: x[1], reverseTrue) for char, cnt in sorted_items: print(f{char}: {cnt})这里解释几个容易忽略的细节。为什么用isalpha()因为空格、逗号、句号、感叹号这些符号统计出来没有业务意义而且在输出时会让格式很乱。你直接用if char ! 一个一个排除效率低不说还容易漏掉,、.、!这些符号。isalpha()一次判断解决所有问题。为什么用dict.get(char, 0)而不是先判断if char in result两种写法都行但我更推荐get写法——少写一个 if少一层缩进。这是一点点代码风格上的优化但入门阶段养成“少写嵌套”的习惯以后读代码会轻松很多。排序时keylambda x: x[1]的含义是什么result.items()返回的是(key, value)的元组列表x[0]是字符x[1]是次数。如果不写 key默认按元组第一个元素排也就是按字符排就完全不是题目要求的“按次数排”了。这里reverseTrue是从大到小。运行结果是l: 4 o: 4 h: 3 e: 3 ...3.2 第二题嵌套列表按成绩排序给定数据students [ [张三, 78], [李四, 92], [王五, 85], [赵六, 92], ]要求按成绩从高到低排序成绩相同的保持原顺序。这道题的完整解法students [ [张三, 78], [李四, 92], [王五, 85], [赵六, 92], ] sorted_students sorted(students, keylambda x: x[1], reverseTrue) for rank, student in enumerate(sorted_students, start1): print(f第{rank}名{student[0]}成绩 {student[1]})输出结果第1名李四成绩 92 第2名赵六成绩 92 第3名王五成绩 85 第4名张三成绩 78这里有两个值得多说一句的点。一是成绩并列时怎么处理名次。我的做法是直接用enumerate给 1、2、3、4 的排名但在真实场景里这种并列成绩应该显示并列名次。不过作业没有这个要求我就按最简单的处理。有时候我也犯过这个毛病明明题目没说统一名次我却花了很多时间研究“名次跳跃”的逻辑。在项目开发里这叫过度设计先满足当前需求再考虑扩展。二是 sorted 和 sort 的区别。这题用sorted返回新列表不改原列表如果用students.sort(...)是原地排序原列表顺序会变。有一个实际案例如果后面还要用原来的“按姓名录入顺序”输出一遍你用了sort原列表已经变了数据就找不回来了。所以我习惯在“先排序、后面可能还要用原数据”的场景下优先用sorted。3.3 第三题闰年判断与函数封装题目要求写一个函数days_in_year(year)输入年份返回这一年的天数。def days_in_year(year): if (year % 4 0 and year % 100 ! 0) or (year % 400 0): return 366 return 365很多人会写成def days_in_year(year): if year % 4 0: if year % 100 0: if year % 400 0: return 366 else: return 365 else: return 366 else: return 365这个写法不能说错但显然没有第一种简洁。闰年的完整判断规则是能被 400 整除的是闰年能被 4 整除但不能被 100 整除的是闰年其他不是。直接把这个规则翻译成(year % 4 0 and year % 100 ! 0) or (year % 400 0)反而比嵌套 if 更容易看懂。写代码这一行考的不是你会不会写 if而是你能不能把“规则”直接转成“表达式”。这个函数的测试建议重点关注几个边界年份2000能被 400 整除闰年、1900能被 4 和 100 整除但不能被 400 整除不是闰年、2024闰年、2023平年。我当年的排查步骤就是把这些年份逐个验证一遍保证days_in_year(2000)返回 366、days_in_year(1900)返回 365。这种“用边界值验证”的习惯到后面做更复杂的算法题会非常有用。3.4 第四题嵌套列表去重与区间合并题目给的示例数据intervals [[1, 2], [2, 3], [1, 2], [4, 5], [5, 6], [8, 10]]要求去掉重复的区间再把能合并的区间合并最终输出合并后的区间列表。写这道题一定要分步不要试图一次到位。第一步去重。因为列表元素是[1, 2]这种直接用set(intervals)会报TypeError: unhashable type: list。列表是不可哈希的不能放进 set。处理方法有两种一是把每个子列表转成元组tuple(x)再去重二是手写循环判断。我用的第一种unique_intervals list(set(tuple(x) for x in intervals)) unique_intervals [list(x) for x in unique_intervals]第二步排序。按每个子列表的第一个元素升序排。为什么要先排序因为区间合并只有“按起点从小到大处理”才不容易漏掉重叠情况。unique_intervals.sort(keylambda x: x[0])第三步合并。我习惯维护一个“当前区间”的变量current遍历后面的区间判断下一个区间的起点是否小于等于当前区间的终点。如果是就说明两个区间有重叠合并后的终点取两者较大值如果不是就把当前区间加入结果列表然后当前区间更新为新区间。merged [] current unique_intervals[0] for interval in unique_intervals[1:]: if interval[0] current[1]: current[1] max(current[1], interval[1]) else: merged.append(current) current interval merged.append(current)完整代码如下intervals [[1, 2], [2, 3], [1, 2], [4, 5], [5, 6], [8, 10]] unique_intervals list(set(tuple(x) for x in intervals)) unique_intervals [list(x) for x in unique_intervals] unique_intervals.sort(keylambda x: x[0]) merged [] current unique_intervals[0] for interval in unique_intervals[1:]: if interval[0] current[1]: current[1] max(current[1], interval[1]) else: merged.append(current) current interval merged.append(current) print(merged)输出结果[[1, 3], [4, 6], [8, 10]]这里有个很关键的操作要点合并过程中一旦更新current[1]原变量对应的内存是同一个对象。如果你在循环里写了current [current[0], interval[1]]这种“重新创建列表”的写法后面的迭代引用就会断。我遇到过同学把合并写成了“每合并一次就 append 一次”结果输出了一堆中间状态。合并的逻辑核心是“当前区间”一直延续直到遇到断开的区间才 append 并重置。3.5 第五题文本文件单词统计题目要求读取一份本地文本文件article.txt统计其中每个单词出现的次数输出出现最多的 5 个词。我的实现with open(article.txt, r, encodingutf-8) as f: content f.read() words content.lower().split() word_count {} for word in words: word_count[word] word_count.get(word, 0) 1 top5 sorted(word_count.items(), keylambda x: x[1], reverseTrue)[:5] for word, cnt in top5: print(f{word}: {cnt})这段代码能用但有两个明显的问题。问题一content.lower().split()会把标点跟着单词一起切进去。比如 “hello,” 会被当成一个词和 “hello” 分开统计。需要做数据清洗把所有非字母字符替换成空格再 split。import re cleaned re.sub(r[^a-zA-Z\s], , content.lower()) words cleaned.split()问题二文件路径和编码问题。作业里没说文件路径我默认把article.txt放在和代码同一个目录下。如果你放到别的地方要用绝对路径或相对路径。编码方面Windows 下经常遇到gbk编码的文件encodingutf-8打开会报UnicodeDecodeError。这个问题的排查技巧是先看报错信息如果报错和编码有关一般就是编码不匹配换成encodinggbk试试。如果有同学在读取文件时报了UnicodeDecodeError八成就是这里的问题。更稳妥的办法是使用第三方库chardet检测文件编码但这是后话了。4. 常见报错与排查技巧实录4.1 SyntaxError 系列这应该是入门阶段出现频率最高的报错类型。我这次的作业里最典型的是第二题sorted_students sorted(students, keylambda x: x[1] reverseTrue)少了一个逗号SyntaxError: invalid syntax。这行报错提示会直接指向出错位置一般就在少了逗号的地方附近。排查方式就是看报错提示的那一行找找是不是漏了逗号、冒号、括号这类标点。这类错有时候费眼睛尤其是代码写得很长的时候。我应对的办法是让 IDE 的语法高亮帮人眼排查——如果某个地方的颜色变了那大概率就是那里断了语法。4.2 IndexError 与嵌套列表索引问题第四题如果你没注意子列表长度极容易遇到。current unique_intervals[0] # 如果 unique_intervals 是空列表这里直接 IndexError: list index out of range这段代码的前提是unique_intervals至少有一个元素。如果题目给的 intervals 是空列表这段代码第一行就崩。所以实操时我习惯加上一个空列表判断if not intervals: print([]) exit()4.3 AttributeErrorNoneType object has no attribute xx出现这个报错往往是你把“原地操作”和“返回新对象”混用了。比如有人写sorted_students students.sort(keylambda x: x[1], reverseTrue)然后print(sorted_students)得到None。因为list.sort()是原地排序返回None而sorted()是返回新列表。这个报错的排查方式很有代表性先去查这个方法的返回值是什么不要凭感觉猜。Python 的文档或者 IDE 的提示都会直接告诉你。4.4 UnicodeDecodeError第五题读取文件时的经典问题。Windows 环境下如果文件是用gbk编码保存的你用encodingutf-8读就会报UnicodeDecodeError: utf-8 codec cant decode byte 0x...。这题我当时是这样排查的先看报错信息能明确提到哪个位置解码失败。再用chardet或直接尝试gbk编码打开。平时养成习惯——写文件时指定encodingutf-8读文件时先确认文件编码。4.5 报错排查速查表报错信息常见原因排查方向SyntaxError漏标点、括号不匹配检查报错行的附近找漏掉的逗号冒号IndexError: list index out of range列表下标越界检查是否对空列表取元素或索引值是否超过长度TypeError: unhashable type: list试图把列表放入集合或作为字典键先转成元组再去重或哈希AttributeError: NoneType object has no attribute ...把原地操作的返回值当成正常结果用检查方法是否返回None改用正确写法UnicodeDecodeError文件编码不匹配确认文件编码使用对应encoding参数TypeError: not supported between instances of list and int排序 key 返回了列表类型检查key是否取了明确的单个字段5. 调试心得与作业之外的经验延伸5.1 Print 大法依然是最有效的调试手段做这份作业时我用得最多的调试手段不是 IDE 的断点而是print()。第四题合并区间时我一开始看不明白“为什么结果多了一个[1, 3]”于是我在循环里加了两行打印for interval in unique_intervals[1:]: print(当前处理, current, 下一个, interval) ...打印结果一目了然排序后的列表是[[1, 2], [1, 2], [2, 3], [4, 5], [5, 6], [8, 10]]因为去了重但[1, 2]还和[2, 3]连得上合并成一个[1, 3]逻辑本身没问题。很多时候你“以为”的数据结构和你“实际”的数据结构不一致print 出来看一眼就清楚了。5.2 先写伪代码再写 Python第四题我卡了一个多小时后的转机是停下来用自然语言把步骤写了一遍1. 去掉重复区间 2. 按区间起点升序排列 3. 拿一个当前区间 4. 逐个看下一个区间能接上就合并接不上就存下来 5. 最后存一次当前区间再把伪代码翻译成 Python半小时就写完了。这个流程真的很推荐面对复杂逻辑时先用中文/简笔画把流程理顺写代码只是把想法翻译成语法。先动手敲代码、边敲边想很容易陷入“代码很长但逻辑是错的”的泥潭。5.3 函数不只是“能跑”还要“好测”第三题的函数封装是最容易体现“工程素养”的地方。我的建议是写的时候顺手加一个docstring注明函数的作用、参数和返回值。def days_in_year(year): 根据年份返回该年的天数闰年返回366平年返回365。这是很小的一件事但对后续维护的帮助非常大。尤其是第五次作业之后的课程——多半会开始教模块和类的概念如果你的函数都带注释代码的可读性能撑到那一刻。6. 总结的替代品我的六个实操建议没有总结只有几个建议都是这次作业实打实踩出来的。第一不要跳步写代码。尤其嵌套列表题先想清楚数据结构长什么样再用print验证。第二多用sorted()而不是.sort()。除非你明确要修改原列表否则sorted更安全可读性也更好。第三文件操作尽量用with open(...) as f。忘了f.close()是一个常见问题with自动帮你收拾干净。第四字符串处理想清楚要不要统一大小写和去标点。第五题的争议点就出在这里提前作出假设并注释出来。第五善用边界测试。闰年的 1900/2000、区间列表的空列表、成绩列表的同分情况一个都别放过。程序跑通一个样本不是合格边界不出错才算。第六做完再看一遍自己的代码。看看有没有比原来的写法更短更清晰的方式哪怕只是把三个 if 合并成一个复合条件也是对思维的一次升级。这份第五次作业做完我最大的收获是意识到编程入门不是背语法而是练“把问题拆小、逐个击破”的思考方式。前四次作业我写代码都是“想到哪写到哪”从这一次开始我强迫自己先写伪代码再动手速度反而快了不少。希望我的这些踩坑记录能帮你少走几段弯路。最后分享一个无关题目的小技巧写 Python 作业时在代码文件头部加上# -*- coding: utf-8 -*-是个好习惯好多编码问题能提前挡掉。如果做第五题读文件时发现乱码手动把文件另存为 UTF-8 再做远比硬啃报错快得多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询