news 2026/10/20 18:36:14

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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(), key=lambda x: x[1], reverse=True) for char, cnt in sorted_items: print(f"{char}: {cnt}")

这里解释几个容易忽略的细节。

为什么用isalpha()?因为空格、逗号、句号、感叹号这些符号,统计出来没有业务意义,而且在输出时会让格式很乱。你直接用if char != ' '一个一个排除,效率低不说,还容易漏掉,、.、!这些符号。isalpha()一次判断解决所有问题。

为什么用dict.get(char, 0)而不是先判断if char in result?两种写法都行,但我更推荐get写法——少写一个 if,少一层缩进。这是一点点代码风格上的优化,但入门阶段养成“少写嵌套”的习惯,以后读代码会轻松很多。

排序时key=lambda x: x[1]的含义是什么?result.items()返回的是(key, value)的元组列表,x[0]是字符,x[1]是次数。如果不写 key,默认按元组第一个元素排,也就是按字符排,就完全不是题目要求的“按次数排”了。这里reverse=True是从大到小。

运行结果是:

l: 4 o: 4 h: 3 e: 3 ...

3.2 第二题:嵌套列表按成绩排序

给定数据:

students = [ ["张三", 78], ["李四", 92], ["王五", 85], ["赵六", 92], ]

要求按成绩从高到低排序,成绩相同的保持原顺序。

这道题的完整解法:

students = [ ["张三", 78], ["李四", 92], ["王五", 85], ["赵六", 92], ] sorted_students = sorted(students, key=lambda x: x[1], reverse=True) for rank, student in enumerate(sorted_students, start=1): 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(key=lambda 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(key=lambda 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", encoding="utf-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(), key=lambda x: x[1], reverse=True)[: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编码的文件,encoding="utf-8"打开会报UnicodeDecodeError。这个问题的排查技巧是:先看报错信息,如果报错和编码有关,一般就是编码不匹配,换成encoding="gbk"试试。如果有同学在读取文件时报了UnicodeDecodeError,八成就是这里的问题。更稳妥的办法是使用第三方库chardet检测文件编码,但这是后话了。

4. 常见报错与排查技巧实录

4.1 SyntaxError 系列

这应该是入门阶段出现频率最高的报错类型。我这次的作业里,最典型的是第二题:

sorted_students = sorted(students, key=lambda x: x[1] reverse=True)

少了一个逗号,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 AttributeError:'NoneType' object has no attribute 'xx'

出现这个报错,往往是你把“原地操作”和“返回新对象”混用了。比如有人写:

sorted_students = students.sort(key=lambda x: x[1], reverse=True)

然后print(sorted_students)得到None。因为list.sort()是原地排序,返回None;而sorted()是返回新列表。这个报错的排查方式很有代表性:先去查这个方法的返回值是什么,不要凭感觉猜。Python 的文档或者 IDE 的提示都会直接告诉你。

4.4 UnicodeDecodeError

第五题读取文件时的经典问题。Windows 环境下,如果文件是用gbk编码保存的,你用encoding="utf-8"读,就会报UnicodeDecodeError: 'utf-8' codec can't decode byte 0x...。这题我当时是这样排查的:

  1. 先看报错信息,能明确提到哪个位置解码失败。
  2. 再用chardet或直接尝试gbk编码打开。
  3. 平时养成习惯——写文件时指定encoding="utf-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 再做,远比硬啃报错快得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 18:36:03

红米11T Pro卡14%电量无法开机:锂电过放与BMS锁死修复指南

这台红米11T Pro是我上周接到的一台故障机&#xff1a;屏幕一直停在14%的电量图标&#xff0c;原装67W充电器插上十几分钟一点反应都没有&#xff0c;长按电源键也不能开机。用户第一反应是主板烧了&#xff0c;其实这个故障放到维修圈里&#xff0c;属于典型的“锂电过放BMS保…

作者头像 李华
网站建设 2026/10/20 18:34:19

三菱FX3U PLC与变频器Modbus RTU通讯实战:ADPRW指令与485-BD详解

做自动化改造这几年&#xff0c;PLC和变频器通讯是绕不开的活。尤其在老设备升级、小产线联网这类项目里&#xff0c;用三菱FX3U带着变频器走Modbus RTU&#xff0c;几乎是出场率最高的组合。前阵子我正好帮一个朋友把车间里几台独立启停的变频器改成触摸屏远程控制&#xff0c…

作者头像 李华
网站建设 2026/10/20 18:34:00

Flask与Vue开发记账系统:从数据建模到前后端联调实战

看到“基于flask的大学生记账管理系统的设计与实现-vue pycharm django”这个题目时&#xff0c;我第一反应是&#xff1a;又一个典型的课程设计/毕业设计标题。但仔细看会发现一个很有意思的现象——Flask和Django同时出现在了标题里。如果你也在做类似选题&#xff0c;说明你…

作者头像 李华
网站建设 2026/10/20 18:33:04

MinGW-w64 15.1.0:Nuitka打包Python C++扩展的UCRT+SEH确定性编译底座

简介&#xff1a;本资源是专为使用Nuitka打包Python程序而准备的MinGW-w64官方编译工具链完整安装包&#xff0c;适用于Windows平台下需将Python代码编译为独立可执行文件的开发者&#xff0c;尤其适合熟悉C底层、追求高性能分发方案的中高级Python工程师。压缩包为7z格式&…

作者头像 李华
网站建设 2026/10/20 18:33:03

手写并发文件传输服务:一次搞懂Linux文件IO、线程池与Socket

1. 项目底座&#xff1a;为什么用"并发文件传输服务"把这五块内容全串起来C语言学到一定程度&#xff0c;很多人都会卡在同一个地方&#xff1a;单独看每个知识点都觉得懂&#xff0c;文件IO会读写、进程线程能写出几个demo、Socket也能照书敲出一个回声服务器&#…

作者头像 李华
网站建设 2026/10/20 18:33:03

PyInstaller 3.2.1 可控打包实战:解决 PyQt5+OpenCV 缺模块与启动崩溃

简介&#xff1a;PyInstaller-3.2.1 是一款面向Python开发者的轻量级打包工具资源包&#xff0c;专为解决Python程序跨环境分发难题而设计&#xff0c;适用于需将脚本快速封装为独立可执行文件的初学者与中小型项目开发者。资源包含790个文件&#xff0c;主体为549个核心Python…

作者头像 李华