Python基础语法:变量、类型、判断和循环

3 阅读13分钟

今天从一个空文件开始,逐次做出一份测试结果报告。我们只关心两个字段:状态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_countfailed_countskipped_counttotal_ms
开始尚未处理0000
1passed,120100120
2failed,850110970
3skipped,0111970
4passed,4302111400

检查表格时可以固定顺序:先抄上一轮的四个累计值,再更新耗时,最后只更新对应状态的那个计数。这样不会把“当前耗时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数据结构与函数:写出可复用的数据处理逻辑。