资讯详情

资讯详情

暑期实训Python第二次课:列表字典、循环函数与成绩管理项目实战

1. 暑期实训第二次课到底在练什么暑期实训的Python课第一次通常是环境搭建加基础语法扫盲第二次课才是真正开始“写点像样东西”的分水岭。我带过几期实训也跟不少同学聊过大家普遍的反馈是第一次课听完觉得Python不过如此第二次课结束就开始怀疑自己到底会不会编程。这个落差非常正常因为第二次课的核心任务是把变量、数据类型、流程控制、函数这几块看似零散的知识点串成一个能跑起来的小项目。这篇文章面向的是正在参加暑期实训的在校同学或者自学Python到第二周、感觉知识点都会但一写就卡壳的初学者。我会把第二次课最常出现的几个核心模块拆开讲透包括列表与字典的操作逻辑、循环与条件判断的嵌套写法、函数的参数传递机制以及一个完整的课堂练习项目从零到跑通的全部过程。每个部分我都会补充课堂上老师不一定展开讲的底层原理和实操避坑点让你不仅能把作业交上去还能真正理解为什么这么写。先给一个整体判断第二次课的内容量大概是第一次课的2.5倍但难度曲线最陡的地方不在语法本身而在于思维方式从“写一行看一行”切换到“先设计再实现”。很多同学卡住不是因为不会写for循环而是不知道该把哪个逻辑放进循环里、哪个逻辑抽成函数。这个切换过程我见过最快的人用了三天最慢的两周关键看你有没有刻意去练“拆解问题”这个动作。下面我按课堂实际推进的顺序把每个环节的核心细节和实操要点逐一拆开。你可以对照自己的课堂笔记看看哪些地方当时没听懂哪些地方听懂了但没动手写过。2. 核心知识模块的底层逻辑拆解2.1 列表和字典为什么是第二次课的重头戏第一次课讲完int、float、str、bool之后第二次课几乎一定会进入列表list和字典dict。原因很简单没有这两个结构你只能写一些玩具级的代码一旦要处理一组数据或者描述一个对象的多个属性立刻就抓瞎。列表的本质是一个有序的可变序列你可以把它想象成一排带编号的储物柜每个柜子可以放任意类型的东西编号从0开始。字典的本质是键值对的无序集合更像是一个通讯录你通过名字键直接找到电话值不需要知道它排在第几位。课堂上老师通常会演示这几个操作# 列表的基本操作 scores [88, 92, 76, 95, 60] scores.append(78) # 末尾追加 scores.insert(1, 100) # 在索引1处插入 scores.remove(60) # 删除第一个值为60的元素 print(scores[2]) # 通过索引访问 print(scores[-1]) # 负数索引从末尾算起 # 字典的基本操作 student {name: A同学, age: 20, major: 计算机} student[score] 88 # 新增键值对 print(student.get(name)) # 安全获取键不存在返回None print(student.keys()) # 获取所有键但这里有几个课堂上经常一笔带过、实际写代码时却频繁踩坑的点我逐个说清楚。第一个坑是列表的浅拷贝问题。如果你写list_b list_a然后修改list_blist_a也会跟着变。因为这两个变量指向的是同一个内存对象。正确的复制方式是list_b list_a.copy()或者list_b list_a[:]。这个坑在实训作业里出现的频率极高尤其是当你需要保留原始数据做对比的时候。第二个坑是字典的键必须是不可变类型。用列表当键会直接报TypeError: unhashable type: list。很多同学想用{[1,2]: value}这种写法结果报错后完全不知道发生了什么。记住只有int、float、str、tuple这些不可变类型才能当字典的键。第三个坑是遍历时修改列表或字典。比如你想在遍历列表的过程中删除某些元素直接写for x in list_a: if x 60: list_a.remove(x)会导致跳过元素甚至索引越界。安全的做法是遍历副本或者用列表推导式生成新列表。实操心得每次要对列表或字典做“边遍历边修改”的操作时先问自己一句——我是不是可以生成一个新列表来代替修改原列表十次里有八次这个思路更安全也更简洁。2.2 循环与条件判断的嵌套从能跑到跑对第二次课的另一大块是for循环、while循环和if-elif-else的嵌套使用。单独看每个语法都不难但一旦嵌套超过两层很多同学就开始晕了。我总结了一个判断标准如果你写出来的嵌套超过三层大概率是逻辑设计有问题应该考虑抽函数或者换数据结构。课堂上最经典的练习是“打印九九乘法表”或者“判断素数”。以九九乘法表为例标准写法是两层for循环for i in range(1, 10): for j in range(1, i 1): print(f{j}x{i}{i*j}, end\t) print() # 每行结束后换行这段代码里外层循环控制行数内层循环控制每行的列数。关键点在于range(1, i 1)这个写法——内层循环的上限是动态变化的跟着外层变量走。很多同学第一次写的时候会写成range(1, 10)结果打印出一个矩形而不是三角形。再往深一层循环里的else子句是Python特有的语法也是第二次课容易忽略的知识点。for...else的结构是如果循环正常结束没有被break中断就执行else块。这个特性在“查找元素”场景下特别好用numbers [2, 4, 6, 8, 10] target 7 for num in numbers: if num target: print(f找到了{target}) break else: print(f列表中没有{target})如果用其他语言的习惯来写你可能需要额外定义一个布尔变量来标记是否找到。Python的for-else直接省掉了这个变量代码更干净。但这个语法很多同学到实训结束都没用过因为课堂上往往只提一句就过了。2.3 函数参数传递值传递还是引用传递第二次课通常会引入函数定义和调用。语法本身很简单def开头冒号结尾缩进写函数体。但真正让同学们困惑的是为什么我把列表传进函数函数里改了外面的列表也跟着变了这个问题的答案涉及Python的参数传递机制。准确地说Python采用的是**“传对象引用”**的方式。当你把一个列表传给函数时传的是这个列表对象的引用可以理解为内存地址函数内部通过这个引用修改列表内容外部的列表自然也会变。但如果你在函数内部重新赋值一个新的列表给参数名外部的列表不受影响。def modify_list(lst): lst.append(100) # 修改原列表外部可见 def reassign_list(lst): lst [1, 2, 3] # 重新赋值外部不可见 my_list [1, 2, 3] modify_list(my_list) print(my_list) # 输出 [1, 2, 3, 100] reassign_list(my_list) print(my_list) # 仍然输出 [1, 2, 3, 100]理解这个区别的关键在于区分**“修改对象内容”和“让变量指向新对象”**。前者会影响外部后者不会。这个知识点在后续学习可变默认参数、闭包等概念时还会反复出现第二次课打好基础非常重要。另外函数的返回值也是第二次课的重点。一个函数可以返回多个值本质上是返回了一个元组def get_min_max(numbers): return min(numbers), max(numbers) low, high get_min_max([3, 1, 4, 1, 5, 9]) print(low, high) # 输出 1 9这种写法在实训项目中非常实用比如同时返回统计结果的最大值和最小值、或者返回操作是否成功以及相关信息。3. 课堂练习项目的完整实现过程3.1 项目需求拆解与设计思路第二次课通常会安排一个综合练习把前面讲的列表、字典、循环、函数全部用上。最常见的题目是**“学生成绩管理系统”的简化版**需求大概是这样用一个列表存储多个学生的信息每个学生是一个字典包含姓名和三门课成绩计算每个学生的总分和平均分找出平均分最高的学生统计每门课的全班平均分把所有学生按平均分从高到低排序输出拿到题目后不要急着写代码。我建议你先在纸上或者注释里把数据结构定下来students [ {name: A同学, scores: [88, 92, 76]}, {name: B同学, scores: [95, 88, 91]}, {name: C同学, scores: [70, 85, 80]}, ]为什么用“列表套字典”而不是“字典套列表”因为学生是有顺序的需要排序所以外层用列表每个学生的属性是固定的几个字段所以内层用字典。这个选择不是随意的而是根据数据的使用方式倒推出来的。如果你需要频繁按姓名查找学生那外层用字典以姓名为键会更合适。3.2 分步骤实现与关键代码解析第一步计算每个学生的总分和平均分。这里可以写一个函数def calc_total_avg(scores): total sum(scores) avg total / len(scores) return total, avg注意len(scores)在成绩列表为空时会报ZeroDivisionError。虽然课堂练习里不太可能出现空列表但养成边界检查的习惯很重要。可以加一行判断def calc_total_avg(scores): if not scores: return 0, 0 total sum(scores) return total, total / len(scores)第二步遍历所有学生把计算结果存回字典for stu in students: total, avg calc_total_avg(stu[scores]) stu[total] total stu[avg] avg这里直接在原字典上新增键而不是创建新字典是因为后续排序和查找都需要用到这些计算值。如果你不想修改原数据可以创建一个新的列表来存结果。第三步找平均分最高的学生。可以用循环加一个临时变量top_student students[0] for stu in students: if stu[avg] top_student[avg]: top_student stu print(f平均分最高{top_student[name]}平均分{top_student[avg]:.1f})也可以用内置函数max()配合key参数一行搞定top_student max(students, keylambda s: s[avg])两种写法都要会。循环写法帮助你理解逻辑max写法在实际开发中更简洁。课堂上老师一般会先讲循环写法再补充内置函数写法。第四步统计每门课的全班平均分。这里需要按科目维度来聚合可以用一个列表来存每门课的总分num_subjects len(students[0][scores]) subject_totals [0] * num_subjects for stu in students: for i, score in enumerate(stu[scores]): subject_totals[i] score subject_avgs [total / len(students) for total in subject_totals] print(各科平均分, [f{avg:.1f} for avg in subject_avgs])enumerate()这个内置函数在第二次课经常出现它同时返回索引和值比手动维护一个计数器变量方便得多。第五步按平均分排序。用列表的sort()方法配合key参数students.sort(keylambda s: s[avg], reverseTrue) for stu in students: print(f{stu[name]}: 总分{stu[total]}, 平均分{stu[avg]:.1f})lambda表达式在第二次课可能只是简单提一下但它在排序场景下几乎是标配。如果你对lambda不熟悉也可以先定义一个普通函数def get_avg(stu): return stu[avg] students.sort(keyget_avg, reverseTrue)两种写法效果完全一样但lambda更紧凑。我的建议是先写普通函数确保逻辑正确再改写成lambda熟悉语法。3.3 代码整合与运行结果验证把上面的步骤整合成一个完整的脚本加上必要的注释和输出格式化students [ {name: A同学, scores: [88, 92, 76]}, {name: B同学, scores: [95, 88, 91]}, {name: C同学, scores: [70, 85, 80]}, ] def calc_total_avg(scores): if not scores: return 0, 0 total sum(scores) return total, total / len(scores) # 计算每个学生的总分和平均分 for stu in students: total, avg calc_total_avg(stu[scores]) stu[total] total stu[avg] avg # 找平均分最高的学生 top_student max(students, keylambda s: s[avg]) print(f平均分最高{top_student[name]}平均分{top_student[avg]:.1f}) # 统计各科平均分 num_subjects len(students[0][scores]) subject_totals [0] * num_subjects for stu in students: for i, score in enumerate(stu[scores]): subject_totals[i] score subject_avgs [total / len(students) for total in subject_totals] print(各科平均分, [f{avg:.1f} for avg in subject_avgs]) # 按平均分排序输出 students.sort(keylambda s: s[avg], reverseTrue) print(\n按平均分排序) for stu in students: print(f{stu[name]}: 总分{stu[total]}, 平均分{stu[avg]:.1f})运行结果应该是平均分最高B同学平均分91.3 各科平均分 [84.3, 88.3, 82.3] 按平均分排序 B同学: 总分274, 平均分91.3 A同学: 总分256, 平均分85.3 C同学: 总分235, 平均分78.3验证结果时重点检查三个地方最高平均分是否对应正确的学生、各科平均分的计算是否用了正确的分母学生人数而不是成绩个数、排序是否稳定且方向正确。我见过有同学把各科平均分的分母写成了len(subject_totals)结果算出来是总分除以科目数完全错了。4. 常见报错与排查技巧实录4.1 第二次课最高频的五个报错及解决方法实训课上同学们遇到的报错高度集中。我整理了一个速查表你可以对照自己的报错信息快速定位报错信息常见原因解决方法IndentationError: expected an indented block冒号后没有缩进或者缩进用了Tab和空格混用统一用4个空格缩进检查冒号下一行TypeError: int object is not iterable对整数用了for循环比如for x in 5:确认循环对象是列表、字典、字符串等可迭代对象KeyError: xxx字典中不存在该键直接用了dict[xxx]改用dict.get(xxx)或先判断if xxx in dictIndexError: list index out of range索引超出了列表长度检查索引范围或用len()确认长度NameError: name xxx is not defined变量名拼写错误或函数定义在调用之后检查拼写确保函数先定义后调用其中IndentationError是第二次课出现频率最高的因为第一次课代码短缩进问题不明显第二次课嵌套一多缩进层级就容易乱。我的建议是写代码时先把所有缩进层级用注释标出来填完逻辑再删注释。比如# 第一层 for i in range(5): # 第二层 if i % 2 0: # 第三层 print(i)4.2 逻辑错误排查代码能跑但结果不对怎么办比报错更麻烦的是代码不报错但结果不对。这类问题在第二次课的练习里非常普遍因为涉及多层循环和条件判断逻辑稍微偏一点结果就全错了。排查逻辑错误我推荐**“缩小数据规模打印中间变量”**的方法。比如你的排序结果不对先把学生数量从5个减到2个然后在排序前后分别打印列表内容看看是哪一步出了问题。再比如各科平均分算错了先把每门课的总分打印出来确认总分对不对再检查分母。还有一个特别实用的技巧用纸笔手动模拟一遍代码的执行过程。拿一张纸画出列表和字典的结构然后一行一行“执行”每执行一行就更新纸上的数据。这个方法听起来很笨但对于理解循环嵌套和变量变化非常有效。我带过的同学里凡是愿意动手模拟的逻辑错误的排查速度至少快一倍。注意事项不要一看到结果不对就到处改代码。先定位问题出在哪一步再针对性地改。盲目改代码只会引入新的bug最后连原本正确的部分也被改坏了。4.3 代码风格与调试习惯的养成第二次课是养成代码风格的关键期。我见过太多同学因为变量名随意比如a、b、c、temp1、temp2过两天自己都看不懂自己写的代码。变量命名要能表达含义student_list比sl好average_score比avg好当然avg在局部范围内可以接受。调试方面print()是最直接的工具但不要满屏乱打。我建议在关键节点打印并且给打印内容加上标记print( 排序前 ) print(students) students.sort(keylambda s: s[avg], reverseTrue) print( 排序后 ) print(students)这样输出结果一目了然不会跟其他打印混在一起。等以后学了logging模块或者断点调试可以逐步替换掉这些print但在第二次课阶段print就是最好的朋友。5. 从课堂练习到自主扩展的进阶路径5.1 把练习项目改造成更实用的版本课堂练习跑通之后不要就此停下。我建议你在这个基础上做三个扩展每个扩展都能让你对第二次课的知识理解更深一层。第一个扩展是增加数据输入功能。把硬编码的学生列表改成从用户输入读取students [] while True: name input(请输入学生姓名输入q结束) if name q: break scores [] for i in range(3): score float(input(f请输入第{i1}门课成绩)) scores.append(score) students.append({name: name, scores: scores})这个改动会逼你处理输入验证比如用户输入了非数字怎么办和循环终止条件是对第二次课知识的综合运用。第二个扩展是增加异常处理。用try-except包裹可能出错的代码try: score float(input(请输入成绩)) except ValueError: print(输入的不是有效数字请重新输入)异常处理在第二次课可能只是提了一句但它是让程序从“玩具”变成“工具”的关键一步。第三个扩展是把功能拆成多个函数。课堂练习里我们写了一个calc_total_avg函数你可以继续拆input_students()、print_students()、sort_students()、calc_subject_avgs()。每个函数只做一件事主程序只负责调用。这样代码结构清晰也方便后续修改。5.2 第二次课之后应该补哪些基础第二次课结束后有几个基础知识点建议你尽快补上它们会在后续课程和项目中反复出现字符串的常用方法split()、join()、strip()、replace()、startswith()、endswith()。这些在处理文本数据时几乎天天用到。列表推导式[x*2 for x in range(10) if x % 2 0]。它能把很多循环加append的代码压缩成一行可读性也更好。元组的解包a, b 1, 2和for key, value in dict.items()。这个语法在遍历字典时特别常用。文件读写基础open()、read()、write()、close()以及with open() as f的写法。第二次课的练习数据是硬编码的真实项目的数据通常来自文件。这些内容不需要一次学完但每学一个你都应该回头看看第二次课的练习代码想想能不能用新知识改写。这种“回头改写”的练习比一直往前学新东西的效果更好因为它强迫你建立知识之间的联系。5.3 给不同基础同学的学习建议如果你是完全零基础第二次课结束后最重要的是把课堂练习独立重写一遍不看笔记、不抄代码从空白文件开始写。写不出来的地方就是你的薄弱点针对性补。如果你有一点编程基础比如学过C或Java第二次课的重点应该放在Python特有的写法上列表推导式、字典的get方法、for-else、lambda、解包赋值。这些在其他语言里没有或者写法不同但恰恰是Python代码简洁的原因。如果你已经能独立完成课堂练习那可以尝试用不同的数据结构实现同一个功能。比如学生管理系统用列表套字典实现一遍再用字典套字典实现一遍比较两种方案的优缺点。这种对比练习能让你真正理解数据结构的选择逻辑而不是死记硬背语法。我在实际带实训的过程中发现第二次课结束后能主动做扩展练习的同学到第四次课的时候已经能独立写小项目了而只完成课堂作业的同学往往到课程结束还在纠结基础语法。差距不是智商就是课后有没有多写那几百行代码。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →