打卡第5天。说实话,前四天我过得很轻松:环境装好、print() 打出第一行字、变量赋值、字符串拼接、if 判断,跟着视频敲一遍,运行通过就截图收工,当时觉得 Python 也不过如此。今天开始明显不对劲,函数和字典两个新东西同时进场,视频里每一行都能看懂,可一关掉视频自己动手写,手指就跟脑子闹矛盾。这种"眼睛会了手不会"的状态,基本标志着 Python 学习正式上强度了。
这篇东西就是第5天的完整复盘记录:学到哪、怎么练的、踩了哪些坑、打卡这个动作本身又该怎么调整。如果你也是自学 Python 刚到第5天附近,希望这份记录能帮你省掉一点摸索的时间。
1. 第5天的进度坐标:函数和字典同时进场
1.1 前4天的学习路线回看
先把我前几天的进度摆出来,你才好对照自己现在在哪。我的节奏不算快,但每一步都动过手:
- 第1天:装 Python、配置编辑器、第一次运行 print("hello"),搞明白什么是交互模式、什么是脚本文件。
- 第2天:变量、基本数据类型(整数、浮点数、字符串、布尔值)、简单的运算和拼接。
- 第3天:input() 输入、字符串的常用方法(比如用 strip() 去掉空白、用 split() 拆分),踩了"输入进来都是字符串"的坑。
- 第4天:if / elif / else 条件判断,还有 for 循环,做过一个小练习:循环打印 1 到 100 里的偶数。
到第5天为止,我已经能写出"从上往下顺序执行、遇到判断走分支、遇到循环重复执行"的脚本,但所有代码都是平铺在文件里的,写完了自己都嫌啰嗦。今天要解决的就是这个痛点:函数解决代码复用的问题,字典解决数据怎么组织得更合理的问题。
1.2 为什么第5天是一道分水岭
我自己的体会是:前4天学的都是"语法零件",第5天开始是在学"组装思路"。变量、判断、循环这些,本质是"程序怎么走";而函数和字典,开始触及"程序怎么设计"。同样是写10行代码,前4天你只要记对关键字和格式就行,第5天开始需要你在动笔之前先想清楚:这块逻辑要不要抽出来?这批数据用列表还是字典装?
这种转变最明显的信号就是:报错从"语法错误"变成了"逻辑问题"。语法错误好办,哪错了编译器直接告诉你;但逻辑问题往往不报错,代码能跑,结果却不对。能处理逻辑问题,才算真正在学编程。
1.3 第5天的时间分配参考
我建议如果你今天也练到函数和字典,别只盯着视频看,按这个比例来。我把可用的晚上两小时拆成了三块:
- 30分钟:把函数和字典的语法过一遍,重点是 def、return、{} 的写法。
- 60分钟:做一次完整的综合练习,今天做一个"班级成绩统计"的小工具,后面细说。
- 30分钟:把练习中踩的坑整理成笔记,顺手把报错信息抄下来。
压轴那60分钟才是今天真正的重头戏,前30分钟只是为了有工具可用。
2. 函数的本质:把重复的事情封装成"工具"
2.1 先看看没有函数时代码有多痛
很多人学函数最容易犯的错不是语法,而是"不知道为什么要学"。我第4天写循环打印成绩等级时,代码长这样:
scores = [92, 78, 65, 88] for score in scores: if score >= 85: print("优秀") elif score >= 70: print("良好") else: print("加油")看着还行,但如果程序里要判断三次呢?就得把这四行判断原样再抄两遍。抄一遍两遍还行,抄到五遍,维护起来就是灾难:比如你想把"优秀"的分数线从85改成90,得去所有复制过的地方逐一修改,漏改一处就是 bug。
函数的思路特别直白:把一段逻辑"起个名字包起来",以后每次要用,喊这个名字就行,参数不同,返回结果不同。生活里这就是个菜谱:同一个菜谱,今天放这条鱼、明天放那条鱼,做法不变,材料变,出锅的东西就跟着变。
2.2 def、参数和返回值的完整拆解
来看今天写的第一个函数:
def level(score): if score >= 85: return "优秀" elif score >= 70: return "良好" else: return "加油"拆开讲三个部分:
def是定义函数的关键字,后面跟函数名,函数名最好起得能说明用途,比如level就是"评等级"。- 括号里的
score是参数,相当于菜谱里的"主料"。调用函数时传进来的值,会被填进这个位置。 return是"产出":函数算完之后,把结果交回给调用它的地方。这个"优秀/良好/加油"字符串就是返回值。
调用就是一句话:
print(level(92)) # 输出:优秀 print(level(78)) # 输出:良好我第5天第一次写函数时,总是忘了在函数体后面留空行,或者把return和print混着用。先忍住这些细节冲动,把骨架记牢:def 函数名(参数):缩进写下函数体,最后用 return 交回结果。
2.3 return 和 print 的区别,新手必问
这个问题我今天在自己身上真实发生了,也是几乎所有初学者都会困惑的点:函数里写print和写return看起来都能在屏幕上显示内容,到底有什么不一样?
一句话回答:print只是把东西"显示"出来,显示完就没了;return是把结果"交还"给程序,后续代码还能接住继续用。看这段对比:
def show(score): print(score) def give(score): return score # 调用 show 直接显示数字 show(92) # 屏幕上出现 92,但 show(92) 这个式子本身是 None # 调用 give 得到数字,可以继续参与计算 result = give(92) + 8 # result 是 100关键的坑就在最后一行:如果give里写的是print(score),那give(92) + 8会直接报错,因为give(92)拿到的是None(空值),空值没法跟数字相加。我当时盯着报错看了快十分钟,才反应过来自己把 return 写成了 print。这个坑迟早要踩,早踩早踏实。
2.4 作用域的三个场景
学函数绕不开"作用域"这个词,听着吓人,其实就是一句话:函数内部定义的变量,外面默认看不见;外面定义的变量,函数内部可以读,但想改它就得另外商量。我整理了三个最常出现的场景:
- 场景一:函数内部创建变量,外面访问不到。这是设计好的隔离,避免程序太大时变量名互相污染。
- 场景二:函数内部读外面的变量,比如全局定义一个
pass_score = 60,函数里直接拿来比较,没问题。 - 场景三:函数内部想改外面的变量,直接改写会报
UnboundLocalError。我建议新手绕开这个用法:需要改的值,就通过返回值传出来,比如score = update_score(score),比在函数里塞global干净得多。
作用域不熟没关系,你先记住"内部变量不外露"这条底线,剩下遇到底再处理。
3. 字典入门:数据有了名字就不再靠序号硬找
3.1 列表的局限和字典的登场
第4天我只用列表存数字,比如成绩列表[92, 78, 65, 88]。要拿某个成绩只能靠下标:scores[0]是第一个,scores[1]是第二个。可现实问题往往是"我想找小明的成绩",你根本不知道小明是第几个。
字典干的就是这件事:它用"键"来存数据,而不是用位置。字节跳动一点说,字典就像通讯录,按人名查号码,不是从第一页翻起:
scores = {"小明": 92, "小红": 78, "小刚": 65, "小丽": 88} print(scores["小明"]) # 92,不用管小明排在第几个这里冒号左边是键(名字),右边是值(成绩)。一个字典里,键是唯一的,后面再写同名的键,新值会覆盖旧值。这一点和现实通讯录一样:同名的人你会备注区分,字典里则直接不让你有两条同键记录。
3.2 字典的增删改查基础操作
我第5天手抄了一遍这些操作,建议你也至少敲一遍:
# 新建空字典 d = {} # 增加或修改:键不存在就是增加,存在就是覆盖 d["语文"] = 90 d["数学"] = 85 d["语文"] = 95 # 覆盖,原 90 被替换 # 查询:直接用键取值,但键不存在会报 KeyError print(d["数学"]) # 85 # 推荐用 get(),找不到时返回 None 或者你自己给的默认值 print(d.get("英语")) # None,不报错 print(d.get("英语", 0)) # 0,自己指定的兜底值 # 删除 d.pop("数学") print(d) # {'语文': 95}几个细节值得敲黑板:
- 字典的键要求是"不可变"类型,整数、字符串、元组可以,列表不行。
d["不存在的键"]会直接抛 KeyError,程序就停了。所以我后面写代码统一用了.get(),宁可先拿个默认值,也不要程序中途倒下。- 字典在 Python 3.7 之后保证插入顺序,但我不依赖这个顺序写逻辑,遍历时顺序变不变都不影响正确性。
3.3 遍历字典的几种常用姿势
遍历字典是今天实战的核心动作。三种姿势对应三种需求:
scores = {"小明": 92, "小红": 78, "小刚": 65, "小丽": 88} # 只要键 for name in scores: print(name) # 只要值 for score in scores.values(): print(score) # 键和值都要,用 items() for name, score in scores.items(): print(name, score)最常用的是items(),一个循环同时拿到名字和成绩,后面做统计工具全靠它。注意这里for name, score in ...是拆包,等于把每个(键, 值)元组打开成两个变量。我当时第一次写这个循环总是忘写.items(),直接for name, score in scores:,结果报错"需要解包的值的数量不对",把scores里一个个键拿去拆包,拆不出两个值来。
4. 今日实战:班级成绩统计小工具
4.1 动手前先写伪代码
前几天的习惯是拿到需求就开敲,今天特意改了流程:先在纸上列步骤。我要做的工具需求很简单,三个统计功能:
- 算班级平均分
- 找出最高分是谁、多少分
- 每个学生按分数评等级
伪代码长这样(不用在意语法,只看思路):
定义函数:计算平均分 把所有分数加起来 除以人数 返回结果 定义函数:找出最高分 假设当前最高分是最小值 遍历每个人: 如果分数比当前最高分高: 更新最高分和名字 返回名字和分数 定义函数:评等级 如果分数 >= 85:返回"优秀" 否则如果分数 >= 70:返回"良好" 否则:返回"加油" 准备好成绩字典 调用上面的函数,打印结果这段伪代码花了我五分钟,但后来写代码几乎没有卡壳。这个习惯我从第5天开始保留了下来,后面学越复杂的东西越觉得值得:代码是给人看的,顺序想不清楚,写出来的代码也只是碰运气跑通。
4.2 完整代码与逐步讲解
下面是今天写出并运行通过的完整代码:
# 班级成绩统计:第5天练习 def compute_average(data): """计算平均分""" total = 0 for score in data.values(): total += score return total / len(data) def find_best(data): """找出最高分的学生,返回姓名和成绩""" best_name = "" best_score = -1 for name, score in data.items(): if score > best_score: best_score = score best_name = name return best_name, best_score def make_level(score): """根据分数返回等级""" if score >= 85: return "优秀" elif score >= 70: return "良好" else: return "加油" scores = {"小明": 92, "小红": 78, "小刚": 65, "小丽": 88} avg = compute_average(scores) best_name, best_score = find_best(scores) print("班级平均分:", round(avg, 1)) print("最高分:", best_name, best_score) for name, score in scores.items(): print(name, score, make_level(score))挑三个地方展开讲,因为这三个地方今天我都出了状况:
total / len(data):除法算出来的是浮点数,比如 323 / 4 = 80.75,我打印时用了round(avg, 1)让它保留一位小数,不然屏幕上会出现一长串尾巴。best_score = -1这个初始值:因为它比任何真实成绩都小,所以第一次循环必更新。这里如果用best_score = 0也说得过去,但万一成绩里允许负数,-1 更稳。return best_name, best_score:函数可以一次返回多个值,用逗号隔开,调用时也用逗号分别接住。这比我之前"先更新全局变量再在外面拼凑"的做法干净太多。
4.3 运行结果与扩展玩法
代码跑起来,屏幕输出:
班级平均分: 80.8 最高分: 小明 92 小明 92 优秀 小红 78 良好 小刚 65 加油 小丽 88 优秀看到这五行输出的时候,我终于把之前学的东西串起来了:字典存数据、for 循环 + items() 遍历、函数封装逻辑、if 判断等级、字符串打印输出。第5天这个项目不大,但它是我第一个"由多个函数拼起来"的完整程序。
如果你想练得更深,我给三个扩展方向,都是顺着今天代码往上叠的:
- 扩展一:支持从键盘逐个输入学生成绩,把
scores写成input()加循环动态构建。 - 扩展二:再加一个统计不及格人数的函数,这时候就能体会"把逻辑放进函数"的好处:主流程只加一行调用。
- 扩展三:把结果按分数从高到低排序,去查一查
sorted()和字典的结合用法。
5. 第5天高频踩坑实录与排查思路
5.1 三条最常见的报错及对策
今天一天内,我的编辑器里出现了不少红色报错,挑三个最常见的整理成表:
| 报错信息 | 出现原因 | 处理办法 |
|---|---|---|
| NameError: name 'xxx' is not defined | 变量名拼错、赋值之前就使用、或函数外的代码访问了函数内变量 | 检查拼写,检查变量是否在作用域内 |
| IndentationError: unexpected indent | 缩进不一致,比如同一段循环体里混用了空格和 Tab | 全选代码统一用4个空格,编辑器里设置缩进为空格 |
| TypeError: 'dict' object is not callable | 把字典或列表当函数调用了,比如scores(),这是因为用了小括号而不是中括号/花括号 | 检查是不是该用scores[key]或scores.items() |
这三个坑背后有个共同规律:报错信息里那一行提示,通常不是根因,而是第一个被波及的地方。比如 NameError 提示第10行出错,实际可能是第8行少写了赋值。所以我的习惯是先从报错行往前找,往上翻两三行看定义。
5.2 用 print 大法逐段定位逻辑错误
今天的"班级平均分"第一次算出来是 80.75,我验算了一下,总分是对的,说明这一段的逻辑没问题。真正让我卡住的是find_best:运行结果最高分居然是"小红78",明显不对。
这时候我做了个最笨也最有效的操作:把中间值打出来看。我在循环里临时加了print("当前比较:", name, score, "当前最高:", best_score),屏幕上立刻看到了问题所在——best_score每次循环都被重置成了 0,原来我把best_score = -1写进了循环体内部,导致每次比较前最高分都被清零。
这个"print 大法"不需要任何高级工具,本质上是在程序的关键位置放"监控探头",看变量的中间状态。排查完后,把这些临时 print 删掉即可。代码不报错但结果不对的时候,别无脑改来改去,先在可疑位置打点,看数据流在哪一步歪了。
5.3 照抄代码的三种错误姿势
打卡学习最典型的误区就是"照抄"。今天看到群里有人把 demo 代码抄了三遍还是不会自己写,我顺便复盘了自己的三种错误姿势:
- 姿势一:视频放着,暂停一次抄一行。这等于打字练习,手指过了脑子没过。
- 姿势二:抄完就运行,运行通过就翻篇。过两天这个函数语法早忘了。
- 姿势三:抄的时候改变量名却又没改全,导致 NameError,最后还被误认为是自己不仔细。
我现在改成"三遍法":第一遍看懂了把代码合上,尝试默写;第二遍对照着改错,标注自己哪里卡住;第三遍隔半小时后重新默写,不看资料。整个过程十分钟,记忆效果远超抄五遍。
6. 王者归来的自我总结:第5天这样打卡才有效
6.1 重学 Python 和第一次学的不同
"王者归来"这个标题不是梗,是我真实的状态:我并不是第一次学 Python,第一回学到函数就放弃了,断断续续又捡起来几次。这次重学到第5天,我能明显感觉到自己和之前的差别:
第一,心态变了。第一次学的时候,任何报错都让我觉得"我可能不适合写代码",现在我知道报错是常态,编辑器只是做了把我拦住的事。第二,方法变了。以前追求把视频全部看完,现在追求每看15分钟就停下来写点东西,哪怕是改一行参数看看变化。第三,目标变了。以前目标是"把课程刷完",现在目标变成"能做小工具",这就导致我在第5天愿意花一小时写成绩统计,而不是急着赶进度。
我在小本子上写了句话:进度是假的,手感是真的。与其追第20天的课程,不如把第5天的知识点练到能闭眼复现。
6.2 第5天的过关自检清单
打卡总得有个"过关"的验收标准。今天睡前我自己出了三个快速验证题,不看资料回答:
- 能不能在白纸上写出一个带参数、有返回值的函数,并说明它和 print 的区别?
- 能不能解释为什么"修改函数外面的大变量"不推荐,并说出更稳的替代写法?
- 能不能写一个 for 循环遍历字典,同时拿到键和值,并完成一个累加统计?
三题都能顺畅过关,我才把今天的打卡勾掉。如果哪题卡住,第二天早上第一件事就是补那块缺口。这样打卡才不会变成"打了卡但什么都没留下"的自欺欺人。
第5天就这么过去了。要说心里话,函数和字典给我的冲击不小,但也是从今天开始,我才觉得自己写的不是几行玩具代码,而是一个可以组合、可以复用的东西。明天准备把今天的小工具再扩展一下,试着加上排序和交互输入。如果你正好走到第5天附近,先别急着跟人比进度,把这三个自检题过了,比啥都强。