P02第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

值、容器与控制流程

值、容器与控制流程 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

这一课用一批真实形态的学习记录练手:会读 list/dict 组成的数据,会用条件、循环和推导式做清洗统计,并且能预判 None 和可变性的坑。

问题场景

假设你从本站导出一批学习记录,形如:

records = [
    {"course": "P07", "minutes": 25, "done": True},
    {"course": "P08", "minutes": None, "done": False},   # 没学完,时长为空
    {"course": "p07", "minutes": 25, "done": True},      # 编码不规范,大小写混了
]

需求很简单:总学习时长多少、每门课学了几次、没写时长的记录怎么算。真上手就会发现全是细节:None 怎么判、字典缺键怎么办、同名课程算几次。

核心解释

先钉三个值层面的行为,后面所有清洗都绕不开:

  1. 空值判断用 is None。minutes is None 判断「没记录」,if not minutes 会把 0 也误伤——学了 0 分钟和没记录是两回事。JS 的 == null 宽松比较在这里没有对应物,Python 判空要明确。
  2. 容器分可变与不可变。list 和 dict 可变(原对象被改,所有引用都看得到);str、tuple、int 不可变(只能换新对象)。sorted(records) 返回新列表,records.sort() 原地修改,名字相近行为完全不同。
  3. 函数默认参数在定义时求值一次。def f(items=[]) 的空列表是全局共享的,多次调用会越积越多;默认值该写 None 再在函数体内新建。

循环与统计的惯用写法:

def total_minutes(records: list) -> int:
    total = 0
    for r in records:
        minutes = r.get("minutes")          # 缺键返回 None,不抛 KeyError
        if isinstance(minutes, int):
            total += minutes
    return total

def count_by_course(records: list) -> dict:
    counts: dict = {}
    for r in records:
        key = r["course"].upper()           # 先归一化再统计
        counts[key] = counts.get(key, 0) + 1
    return counts

unfinished = [r["course"] for r in records if not r["done"]]  # 推导式:筛选+投影

dict.get(key, default) 是处理缺字段的正解;counts.get(key, 0) + 1 是计数累加的经典模式,比先判断再赋值少一个分支。

示例拆解

把上面拼成完整程序验证:

# main.py
records = [
    {"course": "P07", "minutes": 25, "done": True},
    {"course": "P08", "minutes": None, "done": False},
    {"course": "p07", "minutes": 25, "done": True},
]

print(total_minutes(records))       # 50,None 被跳过
print(count_by_course(records))     # {'P07': 2, 'P08': 1},大小写归一
print(unfinished)                   # ['P08']

预期结果写在注释里,跑一遍对照。关键是能解释每个数字怎么来的:total_minutes 里 isinstance(minutes, int) 把 None 和字符串都挡在外面;count_by_course 先 upper() 归一化,所以 P07 计两次。这类统计逻辑将来会以同样的形状出现在 P19 的统计接口里,现在练的就是那课的地基。

动手任务

本课实验区有可直接运行的版本(标准库,本机运行输出结果):

  1. 加一个函数 max_course(records),返回学习时长最多的课程编号,全空时返回 None。
  2. 处理「时长为字符串」的脏数据:"25" 这类能安全转 int 的就转,转不动跳过。
  3. 用推导式写 clean(records),剔除缺少 course 键的记录,返回新列表。

常见错误

  • 用 if value: 判断有效值:0、空串、空列表全被当假,统计悄悄少数据;先分清「为空」和「为假」。
  • 循环里边遍历边删列表元素:跳元素的经典事故;要么遍历副本,要么用推导式重建。
  • 可变默认参数:def f(items=[]),多次调用共享同一个列表,换成默认 None。
  • copy 想当然:rows[:] 只浅拷贝,嵌套 dict 还是指向原对象,改内层照样互相影响。

检查与复盘

自查清单:

  • 看到 dict 数据先想缺键、空值、类型不对三种情况再写逻辑
  • 能说清 sorted 与 sort、is 与 == 的区别
  • 计数、累加、筛选三个模式能脱手写出

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1多选简单(多选,全部选对才算正确)

下列「数据结构选型」正确的有哪些?(多选,Python 3.11+)

Q2多选中等(多选,全部选对才算正确)

阅读代码(Python 3.11+,独立脚本可直接运行)。执行后关于 b、c、d 各元素的说法,正确的有哪些?(多选)

import copy

a = [1, [2, 3]]
b = a[:]            # 切片拷贝
c = copy.copy(a)    # 浅拷贝
d = a               # 引用赋值

a[0] = 100
a[1].append(4)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...