今天从一个空文件开始,逐次做出一份测试结果报告。我们只关心两个字段:状态status和耗时duration_ms。先处理一条记录,再处理四条;每增加一段代码,都先检查它解决了什么问题。
我是楠风。本文适合已经能运行Python文件,但看到几十行代码就不知道从哪里开始的同学。当前python沿用Day19准备的解释器。每个完整小例都可以单独保存、执行,不必先把后面的最终程序全部复制下来。
本课手写四条可信数据:passed/120、failed/850、skipped/0、passed/430。状态只有这三种,耗时是非负整数。先把输入限定清楚,注意力就能放在变量、判断和循环上;从外部接收未知数据时的字段检查留到后续学习。
第一次实现:两个名字保存一条结果
为什么需要变量?因为我们要先记住状态和耗时,随后再打印或计算。变量名让代码可以引用已经得到的值,而不必在每个位置重新写一个数字。
status = "passed"中,左边是名字,右边是值,单个等号表示赋值。引号说明passed是一段文字;120没有引号,是可以参加数值运算的整数。先运行这个完整小例:
status = "passed"
duration_ms = 120
print("状态", status, "耗时", duration_ms)
duration_ms = duration_ms + 30
print("更新后", duration_ms)
输出:
状态 passed 耗时 120
更新后 150
第一行让status表示passed,第二行让duration_ms表示120。print括号中的内容用逗号分开,会按顺序输出,默认在各项之间放一个空格。第四行先取右边的旧值120,算出120+30,再把150赋给左边的duration_ms。
这里的等号不是数学方程。如果把它理解成“两边永远相等”,就会困惑为什么一个数可以等于自己加三十;程序做的是一次更新。更新完成后,后面的代码读到新值150。
立即改一处:把第二行120改成200,其他不动。答案是第一行输出耗时200,最后输出230。若你预测330,就把“先取旧值、算一次、再赋回”写在纸上,不能把同一行重复执行两次。
再用一段独立代码检查类型差异:
print(120 + 30)
print("120" + "30")
输出分别是150和12030。相同的加号,数值输入做加法,文字输入做拼接。变量叫duration_ms也不能把字符串自动变成数字,值的类型才影响运算。本课统一使用不带引号的整数耗时。Python数值、文字与变量教程
第二次实现:只在通过时增加计数
现在的问题变成:这一条是否通过?判断需要比较,而不是重新赋值。写法是if 条件:,下一行缩进四个空格,表示条件成立时执行的动作。
status = "passed"
passed_count = 0
if status == "passed":
passed_count = passed_count + 1
print("通过", passed_count)
输出为通过 1。两个等号比较左右是否相等;冒号开启这一段判断。进入判断后,右边0+1算出1,再更新计数。最后一行没有缩进,因此无论条件是否成立,都要打印。
把第一行改成failed,输出变成通过 0。这解释了为什么初值要放在if前面:不符合条件的记录也应该有一个确定的计数零。如果只在分支内部创建计数变量,未进入该分支时,后面的打印就没有值可读。
做一次缩进练习:只把最后的print缩进四个空格,并保留failed输入。答案是没有输出,因为打印也成了“只有通过才执行”的动作。恢复原来的缩进后又会输出零。Python的缩进参与程序含义,不只是为了排版好看。
第三次实现:把同一条记录放进字典
两个分散变量还能处理一条结果,四条结果就难以配对了。我们需要明确“这个状态和这个耗时属于同一条”。字典用大括号保存键和值,冒号连接字段名与字段值,逗号分隔字段。
result = {"status": "passed", "duration_ms": 120}
print(result["status"])
print(result["duration_ms"] + 30)
输出是passed和150。第一对status是键,passed是值;result["status"]根据键取出对应值。第二次取出的120仍然是整数,所以可以加三十。键上的引号属于取值语法,不能把整个表达式误认为字符串。
接着改一处:把字典中的duration_ms改成430。答案是第一行仍为passed,第二行变成460。状态没有跟着耗时变化,因为这是两个不同字段。Python字典教程
一条字典有了,接下来用列表装多条。列表外面使用方括号,各项之间用逗号隔开;这里每一项都是一条字典。
records = [
{"status": "passed", "duration_ms": 120},
{"status": "failed", "duration_ms": 850},
{"status": "skipped", "duration_ms": 0},
{"status": "passed", "duration_ms": 430},
]
print(len(records))
print(records[0]["status"])
print(records[1]["duration_ms"])
输出依次为4、passed、850。len给出列表中有几项;下标从零开始,因此records[0]是第一条,records[1]是第二条。读最后一行时分两步:先按下标取第二条字典,再按键取850。
练习:不改数据,只让最后一行读取第四条的耗时。答案是print(records[3]["duration_ms"]),输出430。这是替换上一完整例子的最后一行,不是另开文件直接运行。
第四次实现:让for代替重复四次取值
如果手写四个下标相加,新增一条数据就得修改计算。for用于依次取得列表中的每一项:for record in records:把本轮那条字典交给record,缩进部分执行完,再取下一条。
records = [
{"status": "passed", "duration_ms": 120},
{"status": "failed", "duration_ms": 850},
{"status": "skipped", "duration_ms": 0},
{"status": "passed", "duration_ms": 430},
]
total_ms = 0
for record in records:
total_ms += record["duration_ms"]
print(record["status"], total_ms)
print("最终累计", total_ms)
完整输出:
passed 120
failed 970
skipped 970
passed 1400
最终累计 1400
for拿到的是一条字典,不是下标数字。这里的total_ms += record["duration_ms"]相当于先计算total_ms + record["duration_ms"],再赋回total_ms。第一轮零加120,第二轮用已经保存的120加850。
循环内的print展示每轮结果;循环外的print只在全部处理完成后执行一次。两种位置都有用途,调试时放在内部观察变化,最终报告放在外部。先看变化过程,再看最终答案,才能定位是哪一轮开始偏离预期。Python的if与for教程
填空练习不需要新语法:把更新累计的那一行改成长写法,右边应该填什么?答案是total_ms = total_ms + record["duration_ms"]。在刚才的完整代码中替换这一行,输出完全不变。能写出展开式,才说明理解了加等号。
第五次实现:同一轮既累计耗时,也统计状态
我们需要三个计数,所以在for前分别设成零。每一轮先累计耗时,再读取status,随后进入对应分支。if匹配passed,elif表示前一个没匹配时继续判断failed,else处理剩余情况。
先单独验证分支,使用第二条记录:
result = {"status": "failed", "duration_ms": 850}
status = result["status"]
passed_count = 0
failed_count = 0
skipped_count = 0
if status == "passed":
passed_count += 1
elif status == "failed":
failed_count += 1
else:
skipped_count += 1
print(passed_count, failed_count, skipped_count)
输出为0 1 0。第一个条件不成立,程序继续检查elif;它成立后增加失败计数,不再执行else。把输入状态改为skipped,两个比较都不成立,才会进入最后一支,输出0 0 1。改为passed则输出1 0 0。这三种输入各跑一次,比只背“多分支”更容易确认执行顺序。
本课输入只有passed、failed、skipped,因此else恰好对应skipped。不能把这个教学前提推广成“任何陌生状态都算跳过”。本阶段不再引入额外输入,只把这三个分支走清楚。
现在把分支放进for。下面是完整、可独立运行的阶段程序:四个初值都在for外,每轮的耗时更新和状态判断在for内,分支中的计数更新再缩进一级。
records = [
{"status": "passed", "duration_ms": 120},
{"status": "failed", "duration_ms": 850},
{"status": "skipped", "duration_ms": 0},
{"status": "passed", "duration_ms": 430},
]
passed_count = 0
failed_count = 0
skipped_count = 0
total_ms = 0
for record in records:
total_ms += record["duration_ms"]
status = record["status"]
if status == "passed":
passed_count += 1
elif status == "failed":
failed_count += 1
else:
# 本课手写样例只含 passed、failed、skipped 三种状态。
skipped_count += 1
print(passed_count, failed_count, skipped_count, total_ms)
每行依次是通过、失败、跳过和耗时累计,完整输出如下:
1 0 0 120
1 1 0 970
1 1 1 970
2 1 1 1400
先不用急着写最终报告,按照如下表追踪。每格是这一轮处理结束后的累计值,不是该条记录自己的值:
| 轮次 | 当前状态与耗时 | passed_count | failed_count | skipped_count | total_ms |
|---|---|---|---|---|---|
| 开始 | 尚未处理 | 0 | 0 | 0 | 0 |
| 1 | passed,120 | 1 | 0 | 0 | 120 |
| 2 | failed,850 | 1 | 1 | 0 | 970 |
| 3 | skipped,0 | 1 | 1 | 1 | 970 |
| 4 | passed,430 | 2 | 1 | 1 | 1400 |
检查表格时可以固定顺序:先抄上一轮的四个累计值,再更新耗时,最后只更新对应状态的那个计数。这样不会把“当前耗时850”和“累计耗时970”混为一谈。调试输出也遵循这个顺序,如果第二轮已经错了,就不用先研究百分比的显示。
第三轮累计不变,跳过计数却增加一,说明“耗时为零”与“没有处理记录”不是同一件事。第四轮passed_count从一变二,另外两个计数保留旧值;不匹配某个分支,不代表把那个分支的计数清零。
第六次实现:先决定分母,再决定显示
通过率在本课定义为通过次数除以通过加失败次数。跳过仍是一条执行记录,所以执行次数是四;它没有参加通过或失败的判定,所以有效执行数是三。不能直接把列表长度拿来做分母。
先运行一个只有计算与显示的小例:
passed_count = 2
failed_count = 1
decided_count = passed_count + failed_count
if decided_count == 0:
rate_text = "N/A"
else:
rate = passed_count / decided_count
rate_text = f"{rate:.2%}"
print("有效执行", decided_count)
print("通过率", rate_text)
输出是有效执行 3和通过率 66.67%。斜杠执行除法;f放在字符串前,花括号内取rate的值,冒号后的.2%让它按百分数显示并保留两位小数。先算出比例,再得到用于输出的文字,是两步操作。Python格式化字符串教程
改输入练习:通过零、失败一,答案为0.00%;通过零、失败零,答案为N/A。前者有一条有效执行却没有通过,后者没有可计算的分母。必须先判断分母,不能先执行除法再想着补显示。
把已经理解的各段接起来
下面是完整主例。保存为report.py后执行python report.py。不要把它当成新知识:数据、初始化、循环、判断、分母、格式化都已经单独运行过。
records = [
{"status": "passed", "duration_ms": 120},
{"status": "failed", "duration_ms": 850},
{"status": "skipped", "duration_ms": 0},
{"status": "passed", "duration_ms": 430},
]
passed_count = 0
failed_count = 0
skipped_count = 0
total_ms = 0
for record in records:
total_ms += record["duration_ms"]
status = record["status"]
if status == "passed":
passed_count += 1
elif status == "failed":
failed_count += 1
else:
# 本课手写样例只含 passed、failed、skipped 三种状态。
skipped_count += 1
decided_count = passed_count + failed_count
if decided_count == 0:
rate_text = "N/A"
else:
rate = passed_count / decided_count
rate_text = f"{rate:.2%}"
print("执行次数:", len(records))
print("通过:", passed_count, "失败:", failed_count, "跳过:", skipped_count)
print("累计耗时:", total_ms, "ms")
print("有效执行:", decided_count)
print("通过率:", rate_text)
完整输出:
执行次数: 4
通过: 2 失败: 1 跳过: 1
累计耗时: 1400 ms
有效执行: 3
通过率: 66.67%
重新阅读时,先找所有缩进层次。total_ms更新缩进四个空格,属于每一轮;三个计数的更新再多缩进四个空格,属于对应状态分支。decided_count退回最左边,表示全部记录处理完才计算分母。
调试练习:为什么最后只剩430
下面是故意写错的独立例子,它能运行,但没有正确累计:
records = [
{"status": "passed", "duration_ms": 120},
{"status": "failed", "duration_ms": 850},
{"status": "skipped", "duration_ms": 0},
{"status": "passed", "duration_ms": 430},
]
for record in records:
total_ms = 0
total_ms += record["duration_ms"]
print(total_ms)
输出为120、850、0、430,每轮先把旧值抹成零。参考修复是把total_ms = 0移到for前,取消该行缩进;其余不变。修复后四行应为120、970、970、1400,正是前面的累计轨迹。
如果要让AI帮忙,只给这段错误代码和预期1400,要求“指出第一次丢失累计值的位置,不提供重写版本”。你仍要自己移动那一行,并解释第二轮为何从120继续,而不是从零重新开始。
最后从空文件写一次:筛出较慢的有效记录
新要求:统计非skipped且耗时至少500ms的记录数量与总耗时。先补两个符号:!=表示不相等,>=表示大于或等于;and连接两个条件,两边都成立才进入if。
status = "failed"
duration_ms = 850
print(status != "skipped")
print(duration_ms >= 500)
print(status != "skipped" and duration_ms >= 500)
三行都是True,表示条件成立。把status改成skipped,三行变成False、True、False;耗时条件单独成立,也不足以通过组合条件。
这道题重新使用下面完整列出的四条原始数据,不沿用前题改过的状态或耗时。现在先遮住答案,用这四条数据手写。你需要列表、两个零初值、一个for和一个if,不需要新工具。参考答案如下:
records = [
{"status": "passed", "duration_ms": 120},
{"status": "failed", "duration_ms": 850},
{"status": "skipped", "duration_ms": 0},
{"status": "passed", "duration_ms": 430},
]
threshold_ms = 500
slow_count = 0
slow_ms = 0
for record in records:
if record["status"] != "skipped" and record["duration_ms"] >= threshold_ms:
slow_count += 1
slow_ms += record["duration_ms"]
print("符合数量", slow_count)
print("符合耗时", slow_ms)
输出为符合数量 1和符合耗时 850。只有第二条同时满足条件。把threshold_ms改成430后,第四条也满足,答案变为2和1280;等于430不能漏掉,因为比较符号带等号。
写完后倒查每个变量:哪一行给了初值,哪一行更新,哪一行只负责显示?如果某一项答不上来,就回到对应小例重做一次。今天真正需要交出的成果,是能独立写出最后这个变式,而不是收藏更多语法名称。
本文所有完整代码及替换练习在Python3.12.10实际运行,官方资料查阅于2026-10-19。下一篇:Python数据结构与函数:写出可复用的数据处理逻辑。