欢迎光临
我们一直在努力

《太空探索任务控制台》Python 教程

《太空探索任务控制台》Python 教程

项目主题:地面控制中心给太空任务写一套"小工具集"。 目标:让讲师只看这一份文档就能独立写出全部 15 章的代码并完成讲解。


0. 课程理念:零跳跃学习

传统路线问题本路线(A→B→C→D 顺势推导)
知识点 A → 知识点 C(中途发现需要 B,回头补) A → B(A 的自然延伸)→ C(B 的必然发展)→ D(C 的合理进阶)
突然冒出 lambda / *args / dataclass 等高级语法 每一节只引入当前必须的语法,禁止"伏笔"

设计准则:

  • 每章只解决一类问题,并把这一类问题一次讲透。
  • 每章末尾的小练习只用到本章已学知识,绝不超纲。
  • 代码真实可运行,用 Python 3.14。所有用到的语法在 3.14 中都属于"现代首选",无废弃 API。

  • 1. 项目初始化

    mkdir python_code && cd python_code
    uv init –python 3.14 –name python_code –no-readme
    mkdir chapters docs

    环境确认:

    uv python list # 应能看到 cpython-3.14
    uv run python -V # Python 3.14.x


    2. 章节速览(15 章 · 由浅入深)

    章文件标题核心知识点(一句话)
    01 chapter01_greetings.py 起飞前的对话 print + 字符串 + f-string + input
    02 chapter02_variables.py 任务变量 变量、动态类型、类型转换、命名规范
    03 chapter03_orbit_math.py 计算轨道参数 算术运算符 + math 模块 + 物理公式
    04 chapter04_decisions.py 发射窗口判断 布尔/比较/逻辑 + if-elif-else + match-case
    05 chapter05_loops.py 循环发令 while + for + range + break/continue + enumerate/zip
    06 chapter06_collections.py 数据打包箱 list + tuple + 解包 + 嵌套 + 排序
    07 chapter07_dictionaries.py 字典通讯录 dict + 嵌套 + 推导式 + Counter / defaultdict
    08 chapter08_functions.py 可重用的任务模块 def + 参数 + return + 默认值 + *args/**kwargs + lambda
    09 chapter09_errors.py 错误处理 异常 + try-except-else-finally + raise + 自定义异常 + warnings
    10 chapter10_files.py 文件与日志 pathlib + with + JSON + logging
    11 chapter11_oop.py 面向对象:航天器类 class + __init__ + 继承 + @dataclass + Enum
    12 chapter12_iterators.py 迭代器与生成器 迭代协议 + yield + 生成器表达式 + itertools
    13 chapter13_typing.py 类型提示与契约 类型提示 + typing + Protocol + TypeGuard
    14 chapter14_concurrency.py 并发任务 asyncio + async/await + gather + TaskGroup
    15 chapter15_project.py 综合实战项目 dataclass + asyncio + argparse + CLI 完整流程

    每章实战都紧扣太空探索主题,不出现学生/学校相关示例。


    3. 备课方法论

  • 先讲"为什么"再讲"怎么写":每章开头用 1 句话点出真实场景(任务控制台里为什么需要它)。
  • 每段代码运行验证:本项目所有 15 章均已在 Python 3.14 下 exit 0 验证。
  • 课堂节奏建议(90 分钟课):
    • 5 分钟讲解本章解决的问题
    • 15 分钟按代码段逐步讲解(边讲边让学生跟着敲)
    • 20 分钟学生独立完成"小练习"
    • 10 分钟答疑 + 总结 + 预告下一章
  • 常见坑预警:每个章节末尾的「讲师笔记」会列出学生最容易卡住的地方与应对话术。

  • 4. 全部章节代码(与仓库 chapters/*.py 一一对应)

    以下每章都给出完整代码,可直接复制运行。 注释中 ~ 行表示可选讲解;课堂可酌情跳过。

    目录

    • 第 01 章 · 起飞前的对话
    • 第 01 章 · 起飞前的对话
    • 第 02 章 · 任务变量
    • 第 03 章 · 计算轨道参数
    • 第 04 章 · 发射窗口判断
    • 第 05 章 · 循环发令
    • 第 06 章 · 数据打包箱
    • 第 07 章 · 字典通讯录
    • 第 08 章 · 可重用的任务模块
    • 第 09 章 · 错误处理
    • 第 10 章 · 文件与日志
    • 第 11 章 · 面向对象:航天器类
    • 第 12 章 · 迭代器与生成器
    • 第 13 章 · 类型提示与契约
    • 第 14 章 · 并发任务
    • 第 15 章 · 综合实战项目

    第 01 章–起飞前的对话

    知识点:print / 字符串 / f-string / input 实战主题:起飞前的对话

    """第 01 章:起飞前的对话。

    本章学习目标(零跳跃路径):
    print() 输出 → 字符串字面量 → f-string 格式化 → input() 交互

    所有示例围绕"任务控制台欢迎语"展开,避免出现尚未学过的概念。
    """
    # —- 演示用的容错输入:交互环境等待用户输入;非交互环境直接用默认值 —-
    import sys as _sys
    def prompt(question, default):
    """演示用的容错输入:拿到 EOF 时返回 default,便于无人值守运行。"""
    try:
    return input(question)
    except EOFError:
    print(question + f"[default: {default}]")
    return default

    # ———————————————————————-
    # 1.1 最简单的输出:print 是 Python 内置的"喊话器"
    # ———————————————————————-
    print("== Mission Control Online ==") # print() 把括号里的内容原样显示在屏幕上
    print("Welcome, Commander.") # 每调用一次 print 默认换行

    # ———————————————————————-
    # 1.2 字符串字面量:单引号 / 双引号 / 三引号
    # ———————————————————————-
    single_quoted = 'T-minus 10 seconds' # 单引号字符串:适合不含单引号的内容
    double_quoted = "Rocket status: GO" # 双引号字符串:和单引号等价,按习惯选用
    triple_quoted = """All systems nominal.
    Awaiting final clearance."""
    # 三引号可以跨行,常用于多行文本

    print(single_quoted) # 把变量里的字符串显示出来
    print(double_quoted)
    print(triple_quoted)

    # ———————————————————————-
    # 1.3 字符串拼接:把几段文字拼成一句
    # ———————————————————————-
    rocket_name = "Artemis" # 火箭代号(变量先记住,第 02 章会深入)
    mission_tag = "VII" # 任务编号
    greeting = "Hello, " + rocket_name + "-" + mission_tag + "!" # 用 + 把字符串粘起来
    print(greeting) # 结果:Hello, Artemis-VII!

    # ———————————————————————-
    # 1.4 f-string:Python 3.6+ 推荐的现代写法(PEP 498)
    # 在字符串前加 f,把变量名放进 {} 里,运行时会自动替换成变量的值。
    # ———————————————————————-
    crew_count = 4 # 机组人数
    duration_days = 7 # 任务天数
    status_line = f"Rocket: {rocket_name}{mission_tag} | Crew: {crew_count} | Duration: {duration_days} days"
    print(status_line) # 一行里同时展示多个变量的值

    # ———————————————————————-
    # 1.5 f-string 中的表达式:{} 里不仅能放变量,还能放计算式
    # ———————————————————————-
    print(f"Mission ends in {duration_days * 24} hours.") # 表达式会在运行时求值
    print(f"Crew per day of mission: {crew_count / duration_days:.2f}") # :.2f 表示保留 2 位小数

    # ———————————————————————-
    # 1.6 input():让程序等用户在终端输入一行文字
    # ———————————————————————-
    print("\\n– Identity Check –") # \\n 是换行符,让输出更整齐
    callsign = prompt("Enter your callsign: ", default="Aurora") # 程序暂停,光标等待用户输入
    print(f"Access granted. Welcome aboard, {callsign}.") # 把用户输入嵌入欢迎语

    # ———————————————————————-
    # 1.7 小练习(去掉下面的三引号注释即可运行)
    # ———————————————————————-
    """
    name = prompt("Astronaut name: ", default="Lyra")
    favorite_planet = prompt("Favorite planet: ", default="Mars")
    print(f"Commander {name} is heading to {favorite_planet}. Godspeed!")
    """

    # —- 演示用的容错输入:交互环境等待用户输入;非交互环境直接用默认值 —-
    def prompt(question, default):
    """演示用的容错输入:拿到 EOF 时返回 default,便于无人值守运行。"""
    try:
    return input(question)
    except EOFError:
    print(question + f"[default: {default}]")
    return default

    课堂建议:把代码贴到 chapters/chapter01_greetings.py,运行验证后再讲解每段作用。


    第 02 章–任务变量

    知识点:变量 / 类型 / 类型转换 / 命名规范 实战主题:任务变量

    """第 02 章:任务变量。

    学习路径(零跳跃):
    变量赋值 → 动态类型 → type() 检查 → 类型转换 → 命名规范

    围绕"火箭代号与载荷清单"展开:先建立"变量就是带名字的盒子"的心智模型。
    """

    # ———————————————————————-
    # 2.1 变量赋值:把值贴上名字,方便后面反复使用
    # ———————————————————————-
    rocket_name = "Artemis-VII" # 字符串:火箭任务代号
    crew_size = 4 # 整数 int:机组成员数量
    fuel_mass_tons = 2670.5 # 浮点数 float:燃料质量(吨)
    is_go_for_launch = True # 布尔 bool:是否可以发射

    print(rocket_name, crew_size, fuel_mass_tons, is_go_for_launch)

    # ———————————————————————-
    # 2.2 动态类型:Python 不需要在声明时指定类型,运行时会自动推断
    # ———————————————————————-
    sample = 42 # 现在 sample 是 int
    print(type(sample)) # <class 'int'>:type() 用来查看值的类型
    sample = "forty-two" # 又可以变成 str:同一名字可以贴不同类型的值
    print(type(sample)) # <class 'str'>

    # ———————————————————————-
    # 2.3 常见内置类型一览(本章先把名字认熟)
    # ———————————————————————-
    print(type(123)) # int:整数
    print(type(3.14)) # float:浮点数
    print(type(True)) # bool:布尔值(注意首字母大写)
    print(type("hello")) # str:字符串
    # 第 06 章会学 list,第 07 章会学 dict,本章先不出现

    # ———————————————————————-
    # 2.4 类型转换:在不同类型之间显式转换
    # ———————————————————————-
    crew_count_text = "4" # 来自配置文件或用户输入,通常是字符串
    crew_count_number = int(crew_count_text) # int() 把字符串转为整数
    print(crew_count_number + 1) # 5:可以参与数值运算了

    fuel_tons = float("2670.5") # float() 把字符串转为浮点数
    print(fuel_tons * 2) # 5341.0

    score = 98
    print("Score: " + str(score)) # str() 把数值转回字符串以便拼接

    # ———————————————————————-
    # 2.5 命名规范:让代码一眼就能读懂
    # ———————————————————————-
    # 合法但糟糕的命名(避免在真实项目中使用):
    # x = 4 # 含义不明
    # FuelMass = 2670.5 # 混合大小写风格不统一

    # 推荐遵循 PEP 8 风格:
    MAX_PAYLOAD_KG = 6000 # 全大写下划线:模块级常量
    crew_callsign = "Aurora" # 小写+下划线(snake_case):变量与函数名
    PrimaryMission = "Lunar Orbit" # 帕斯卡命名(PascalCase):留给第 11 章的"类"使用

    print(MAX_PAYLOAD_KG, crew_callsign, PrimaryMission)

    # ———————————————————————-
    # 2.6 input() 返回的永远是字符串,需要时立刻转换
    # ———————————————————————-
    # payload_text = input("Payload mass (kg): ") # 取消注释即可交互
    # payload_kg = float(payload_text) # 转为浮点数
    # print(f"Within limit: {payload_kg <= MAX_PAYLOAD_KG}") # 比较运算符第 04 章细讲

    # ———————————————————————-
    # 2.7 多重赋值与解包(Python 语法糖,先体验,第 06 章会正式讲解包)
    # ———————————————————————-
    x, y, z = 10, 20, 30 # 一次给三个变量赋值
    print(x, y, z)

    a = b = c = 0 # 链式赋值:三个变量都指向同一个值 0
    print(a, b, c)

    # ———————————————————————-
    # 2.8 实战小脚本:载荷清单(融合本章全部知识点)
    # ———————————————————————-
    satellite_mass = 4800 # 卫星质量 kg
    propellant_mass = 1200 # 推进剂质量 kg
    total_mass = satellite_mass + propellant_mass # 总质量 = 两者相加
    print(f"Payload: satellite={satellite_mass}kg, propellant={propellant_mass}kg, total={total_mass}kg")
    print("Type of total_mass:", type(total_mass).__name__) # 用 __name__ 只取类型名

    课堂建议:把代码贴到 chapters/chapter02_variables.py,运行验证后再讲解每段作用。


    第 03 章–计算轨道参数

    知识点:算术 / math / 物理常量 / 格式化 实战主题:计算轨道参数

    """第 03 章:计算轨道参数。

    学习路径(零跳跃):
    算术运算符 → math 模块 → 物理常量 → 格式化输出 → f-string 进阶

    实战:圆形轨道速度计算器。
    圆形轨道速度公式 v = sqrt(GM / r)
    """
    # —- 演示用的容错输入:交互环境等待用户输入;非交互环境直接用默认值 —-
    import sys as _sys
    def prompt(question, default):
    """演示用的容错输入:拿到 EOF 时返回 default,便于无人值守运行。"""
    try:
    return input(question)
    except EOFError:
    print(question + f"[default: {default}]")
    return default

    # ———————————————————————-
    # 3.1 基础算术运算符
    # ———————————————————————-
    print(2 + 3) # 加
    print(10 4) # 减
    print(6 * 7) # 乘
    print(20 / 6) # 除:结果总是 float,3.333…
    print(20 // 6) # 整除:丢掉小数部分,3
    print(20 % 6) # 取余:除不尽的部分,2
    print(2 ** 10) # 幂:2 的 10 次方 = 1024

    # ———————————————————————-
    # 3.2 复合赋值运算符
    # ———————————————————————-
    countdown = 10
    countdown -= 1 # 等价于 countdown = countdown – 1
    print(countdown) # 9
    countdown *= 2 # 18
    print(countdown)

    # ———————————————————————-
    # 3.3 引入 math 模块:Python 自带的数学工具箱
    # ———————————————————————-

    # —- 演示用的容错输入:交互环境等待用户输入;非交互环境直接用默认值 —-
    def prompt(question, default):
    """演示用的容错输入:拿到 EOF 时返回 default,便于无人值守运行。"""
    try:
    return input(question)
    except EOFError:
    print(question + f"[default: {default}]")
    return default
    import math # import 是"导入",把 math 这个工具箱拿来用

    print(math.sqrt(144)) # sqrt = square root,开平方 → 12.0
    print(math.pi) # 圆周率 π
    print(math.sin(math.pi / 2)) # sin(π/2) = 1.0

    # ———————————————————————-
    # 3.4 圆形轨道速度计算
    # 公式:v = sqrt(GM / r)
    # GM 是地球引力参数(标准值) ≈ 3.986e14 m^3/s^2
    # r = 地球半径 + 轨道高度
    # ———————————————————————-
    EARTH_RADIUS_M = 6.371e6 # 地球半径,单位米
    GM_EARTH = 3.986e14 # 地球引力参数 m^3/s^2

    altitude_km = float(prompt("Orbit altitude (km): ", default="540")) # 用户输入轨道高度
    altitude_m = altitude_km * 1000 # km 转 m
    radius_m = EARTH_RADIUS_M + altitude_m # 圆轨道半径

    orbital_speed = math.sqrt(GM_EARTH / radius_m) # 圆形轨道速度 m/s
    period_seconds = 2 * math.pi * radius_m / orbital_speed # 轨道周期 T = 2πr / v

    # ———————————————————————-
    # 3.5 f-string 格式化输出:控制小数位与对齐
    # ———————————————————————-
    # :>12 表示右对齐占 12 列;:.2f 表示保留 2 位小数;:.1f 表示 1 位小数
    print(f"Orbit radius : {radius_m:>15,.0f} m")
    print(f"Orbit speed : {orbital_speed:>15.2f} m/s")
    print(f"Orbit period : {period_seconds:>15.2f} s ({period_seconds / 60:.2f} min)")

    # ———————————————————————-
    # 3.6 综合:地面控制快速评估
    # ———————————————————————-
    escape_speed = math.sqrt(2 * GM_EARTH / radius_m) # 第二宇宙速度(脱离速度)
    print(f"Local escape speed : {escape_speed:.2f} m/s")
    ratio = orbital_speed / escape_speed # 当前轨道速度占逃逸速度的比例
    print(f"Speed / escape : {ratio:.3f} (< 1/sqrt(2) ≈ 0.707 才能维持圆轨道)")
    print(f"Circular orbit OK : {ratio < 1 / math.sqrt(2)}")

    课堂建议:把代码贴到 chapters/chapter03_orbit_math.py,运行验证后再讲解每段作用。


    第 04 章–发射窗口判断

    知识点:布尔 / 比较 / 逻辑 / if-elif-else / match-case 实战主题:发射窗口判断

    """第 04 章:发射窗口判断。

    学习路径(零跳跃):
    布尔值 → 比较运算符 → 逻辑运算符 (and/or/not) → if / elif / else → match-case

    实战:根据天气、风速、能见度判断是否可以发射。
    """
    # —- 演示用的容错输入:交互环境等待用户输入;非交互环境直接用默认值 —-
    import sys as _sys
    def prompt(question, default):
    """演示用的容错输入:拿到 EOF 时返回 default,便于无人值守运行。"""
    try:
    return input(question)
    except EOFError:
    print(question + f"[default: {default}]")
    return default

    # ———————————————————————-
    # 4.1 布尔值:只有 True 和 False 两个取值(注意首字母大写)
    # ———————————————————————-
    launch_authorised = True
    print(launch_authorised) # True
    print(int(launch_authorised)) # 1:True 可以当整数 1 用
    print(int(False)) # 0

    # ———————————————————————-
    # 4.2 比较运算符:得到一个布尔值
    # ———————————————————————-
    print(10 > 5) # True
    print(10 == 10) # True,注意是两个等号"等于"
    print(10 != 9) # True,不等于
    print(7 <= 7) # True
    print("a" < "b") # True,字符串按字典序比较

    # ———————————————————————-
    # 4.3 逻辑运算符:组合多个布尔条件
    # ———————————————————————-
    wind_speed_kmh = 18 # 风速
    visibility_km = 12 # 能见度
    cloud_cover_pct = 15 # 云量百分比
    is_daylight = True # 是否白天

    # 全部条件都满足才能发射
    go_launch = (
    wind_speed_kmh < 30 # 风速上限
    and visibility_km >= 8 # 最低能见度
    and cloud_cover_pct < 50 # 云量上限
    and is_daylight # 必须是白天
    )
    print(f"All launch criteria met: {go_launch}")

    # ———————————————————————-
    # 4.4 if / elif / else:让程序在不同条件下走不同分支
    # ———————————————————————-
    if go_launch:
    print(">>> LAUNCH AUTHORISED <<<")
    elif wind_speed_kmh >= 30:
    print("Hold: wind speed too high.")
    elif visibility_km < 8:
    print("Hold: visibility below minimum.")
    elif cloud_cover_pct >= 50:
    print("Hold: cloud cover too thick.")
    else:
    print("Hold: awaiting daylight.")

    # ———————————————————————-
    # 4.5 嵌套条件与"早返回"写法(让代码更扁平)
    # ———————————————————————-
    thunderstorm_alert = False
    range_safety_clear = True

    # 嵌套写法:可读但缩进会变深
    if range_safety_clear:
    if not thunderstorm_alert:
    print("Range safety: clear and no thunderstorm.")
    else:
    print("Hold: thunderstorm alert active.")
    else:
    print("Hold: range safety not confirmed.")

    # ———————————————————————-
    # 4.6 match-case(Python 3.10+ 引入,3.14 推荐使用)
    # 适合对同一个变量做"枚举式"判断
    # ———————————————————————-
    mission_phase = prompt("Current phase (prelaunch/ascent/orbit/landing): ", default="orbit").strip().lower()

    match mission_phase:
    case "prelaunch":
    print("Final checks. Countdown active.")
    case "ascent":
    print("Vehicle climbing. Monitor Max-Q.")
    case "orbit":
    print("Coast phase. Payload operations allowed.")
    case "landing" | "recovery": # 用 | 匹配多个值
    print("Descent burn start.")
    case _: # 默认分支,类似 switch 的 default
    print("Unknown phase. Standby.")

    # ———————————————————————-
    # 4.7 真值测试(Truthiness):除了 False,以下值也视为"假"
    # 0, 0.0, "", [], {}, None
    # 其余对象都被视为"真"
    # ———————————————————————-
    battery_level = 0
    if not battery_level:
    print("Battery empty!")

    checklist = ["oxygen", "hud", "comms"]
    if checklist:
    print(f"Checklist has {len(checklist)} items.") # len() 第 06 章讲
    else:
    print("Checklist empty.")

    # —- 演示用的容错输入:交互环境等待用户输入;非交互环境直接用默认值 —-
    def prompt(question, default):
    """演示用的容错输入:拿到 EOF 时返回 default,便于无人值守运行。"""
    try:
    return input(question)
    except EOFError:
    print(question + f"[default: {default}]")
    return default

    课堂建议:把代码贴到 chapters/chapter04_decisions.py,运行验证后再讲解每段作用。


    第 05 章–循环发令

    知识点:while / for / range / break-continue / enumerate / zip 实战主题:循环发令

    """第 05 章:循环发令。

    学习路径(零跳跃):
    while 循环 → for 循环 → range() → break / continue → enumerate → zip

    实战:发射倒计时与多星状态轮询。
    """

    # ———————————————————————-
    # 5.1 while 循环:当条件满足时重复执行
    # ———————————————————————-
    countdown = 10
    while countdown > 0: # 只要 countdown > 0 就继续
    print(f"T-{countdown:02d} seconds") # :02d 表示至少占 2 位,不足补 0
    countdown -= 1 # 自减;别忘了,否则会死循环
    print("Liftoff!")

    # ———————————————————————-
    # 5.2 for 循环:遍历"序列"中的每一项
    # ———————————————————————-
    crew = ["Aurora", "Orion", "Vega", "Lyra"]
    for callsign in crew: # 把列表里的元素逐个取出
    print(f"Crew on board: {callsign}")

    # ———————————————————————-
    # 5.3 range():生成整数序列,常和 for 配合
    # ———————————————————————-
    for n in range(5): # 0,1,2,3,4
    print("Stage check", n)

    for n in range(2, 10, 2): # 从 2 开始,<10,步长 2 → 2,4,6,8
    print("Even tick:", n)

    for n in range(5, 0, 1): # 倒着数:5,4,3,2,1
    print("Reverse:", n)

    # ———————————————————————-
    # 5.4 break 与 continue
    # ———————————————————————-
    # break:立即跳出整个循环
    # continue:跳过本轮剩余代码,进入下一轮
    print("\\n– Anomaly scan –")
    readings = [98.6, 99.1, 105.3, 99.0, 102.7]
    for r in readings:
    if r >= 105.0:
    print(f"Critical reading {r}! Aborting scan.")
    break # 发现严重异常就停止
    if r < 100.0:
    continue # 正常读数不报告
    print(f"Elevated reading: {r}")

    # ———————————————————————-
    # 5.5 enumerate():同时拿到"索引 + 值"
    # ———————————————————————-
    print("\\n– Stage ignition order –")
    stages = ["Booster", "Core", "Upper"]
    for index, stage in enumerate(stages, start=1): # start=1 让编号从 1 开始
    print(f"Stage {index}: {stage} ignite")

    # ———————————————————————-
    # 5.6 zip():把多个序列并行遍历
    # ———————————————————————-
    print("\\n– Telemetry pairing –")
    sensors = ["temp_c", "pressure_kpa", "voltage"]
    values = [22.4, 101.3, 28.1]
    for name, val in zip(sensors, values):
    print(f"{name} = {val}")

    # ———————————————————————-
    # 5.7 循环里的 else 子句:循环"正常结束"才会执行
    # (用 break 中断则不会执行 else)
    # ———————————————————————-
    print("\\n– Retry loop –")
    attempts = 0
    while attempts < 3:
    attempts += 1
    print(f"Attempt {attempts}")
    if attempts == 2:
    break # 中断
    else:
    print("All attempts succeeded.") # 不会执行,因为循环被 break 了

    # ———————————————————————-
    # 5.8 实战:实时模拟遥测流(不会真正阻塞,只是演示)
    # ———————————————————————-
    import time # time.sleep 用来暂停,第 12 章再深入
    import random # random 用来生成随机数,本章只用一次

    print("\\n– Live telemetry (press Ctrl+C to stop) –")
    for tick in range(3):
    altitude = 100 + tick * 50 + random.uniform(5, 5)
    velocity = 50 + tick * 10
    print(f"t={tick}s alt={altitude:6.1f}km v={velocity:5.1f}m/s")
    time.sleep(0.1) # 暂停 0.1 秒模拟间隔

    课堂建议:把代码贴到 chapters/chapter05_loops.py,运行验证后再讲解每段作用。


    第 06 章–数据打包箱

    知识点:list / tuple / 解包 / 嵌套 / 排序 实战主题:数据打包箱

    """第 06 章:数据打包箱。

    学习路径(零跳跃):
    列表 list → 索引与切片 → 增删改查 → 元组 tuple → 解包 → 嵌套 → 排序

    实战:管理一个卫星编队的轨道集合。
    """

    # ———————————————————————-
    # 6.1 列表 list:可以装任意类型的有序容器,用 [] 定义
    # ———————————————————————-
    orbits = [400, 550, 600, 800] # 4 颗卫星的轨道高度 (km)
    print(orbits) # [400, 550, 600, 800]
    print(len(orbits)) # 4,len() 返回元素个数

    mixed = ["Artemis", 4, 95.5, True] # 列表可以混装不同类型(不推荐但合法)
    print(mixed)

    # ———————————————————————-
    # 6.2 索引与切片
    # ———————————————————————-
    print(orbits[0]) # 第一个元素,索引从 0 开始
    print(orbits[1]) # 最后一个元素
    print(orbits[1:3]) # 切片:索引 1 到 2(不含 3)→ [550, 600]
    print(orbits[:2]) # 从头到索引 1 → [400, 550]
    print(orbits[2:]) # 从索引 2 到末尾 → [600, 800]
    print(orbits[::1]) # 反转整个列表

    # ———————————————————————-
    # 6.3 修改、添加、删除
    # ———————————————————————-
    orbits[0] = 410 # 修改指定索引的元素
    print(orbits)

    orbits.append(1200) # 末尾追加一个
    orbits.insert(1, 450) # 在索引 1 处插入
    print(orbits)

    last = orbits.pop() # 弹出最后一个元素,并返回它
    print("Removed:", last, "Remaining:", orbits)

    orbits.remove(550) # 按值删除(只删第一个匹配的)
    print(orbits)

    del orbits[0] # 按索引删除
    print(orbits)

    # ———————————————————————-
    # 6.4 列表的常用方法
    # ———————————————————————-
    samples = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
    print("count of 5:", samples.count(5)) # 统计某个值出现次数
    print("index of 9:", samples.index(9)) # 找到值的第一个索引
    samples.sort() # 原地升序排序
    print("sorted:", samples)
    samples.reverse() # 原地反转
    print("reversed:", samples)

    # 想保留原列表?用 sorted() 函数返回新列表
    original = [5, 2, 9, 1]
    new_sorted = sorted(original)
    print("original:", original, "new:", new_sorted)

    # ———————————————————————-
    # 6.5 元组 tuple:用 () 定义,**不可变**
    # ———————————————————————-
    mission_window = ("2026-08-12", "2026-08-19") # 发射窗口起止
    print(mission_window[0]) # 仍可索引
    # mission_window[0] = "2026-09-01" # 这行会报错,元组不可改

    lat, lon = (39.0, 120.0) # 元组解包:左右数量要一致
    print(f"Launch site: lat={lat}, lon={lon}")

    # 用 * 收集多余元素
    first, *middle, last = [10, 20, 30, 40, 50]
    print(first, middle, last)

    # ———————————————————————-
    # 6.6 嵌套:列表套列表 / 元组
    # ———————————————————————-
    satellites = [
    ("Hubble-Echo", 540, "imaging"),
    ("CommSat-1", 800, "comms"),
    ("Radar-Eye", 700, "sar"),
    ]
    for name, alt, purpose in satellites: # 自动按元组解包
    print(f"{name:<12} alt={alt:>4}km purpose={purpose}")

    # ———————————————————————-
    # 6.7 实战:把卫星编队按高度排序并打印表格
    # ———————————————————————-
    satellites.append(("Weather-9", 360, "weather"))
    satellites.append(("GPS-III", 20200, "nav"))

    satellites_sorted = sorted(satellites, key=lambda s: s[1]) # 按高度排序
    print("\\nFleet roster (sorted by altitude):")
    for name, alt, purpose in satellites_sorted:
    print(f"{name:<12} {alt:>6} km {purpose}")

    课堂建议:把代码贴到 chapters/chapter06_collections.py,运行验证后再讲解每段作用。


    第 07 章–字典通讯录

    知识点:dict / 嵌套 / 字典推导式 / Counter / defaultdict 实战主题:字典通讯录

    """第 07 章:字典通讯录。

    学习路径(零跳跃):
    字典 dict → 键值对操作 → 嵌套 → 字典推导式 → collections.Counter / defaultdict

    实战:每颗卫星维护一份遥测字典。
    """

    # ———————————————————————-
    # 7.1 字典 dict:键值对 (key: value) 的映射,用 {} 定义
    # ———————————————————————-
    satellite = {
    "name": "Hubble-Echo",
    "altitude_km": 540,
    "inclination_deg": 28.5,
    "operational": True,
    }
    print(satellite)

    # ———————————————————————-
    # 7.2 访问与修改
    # ———————————————————————-
    print(satellite["name"]) # 通过键取值,键不存在会 KeyError
    print(satellite.get("mass_kg", "unknown")) # get 不存在时返回默认值,更安全

    satellite["altitude_km"] = 545 # 修改值
    satellite["launch_date"] = "2024-03-14" # 新增键值对
    print(satellite)

    # ———————————————————————-
    # 7.3 遍历字典
    # ———————————————————————-
    for key in satellite: # 默认遍历键
    print(f"key: {key}")

    for key, value in satellite.items(): # 同时拿键和值
    print(f"{key:<14} = {value}")

    for value in satellite.values(): # 只遍历值
    print("value:", value)

    # ———————————————————————-
    # 7.4 嵌套字典:一颗卫星里再装子系统的遥测
    # ———————————————————————-
    telemetry = {
    "sat_id": "SAT-001",
    "position": {"lat": 12.34, "lon": 45.67, "alt_km": 540.0},
    "systems": {
    "power": {"voltage": 28.1, "current": 12.4},
    "thermal": {"temp_c": 15.2, "heaters_on": True},
    },
    }
    print(telemetry["systems"]["power"]["voltage"]) # 28.1
    telemetry["systems"]["power"]["voltage"] = 28.3
    print(telemetry["systems"]["power"]["voltage"])

    # ———————————————————————-
    # 7.5 字典推导式:快速生成字典
    # ———————————————————————-
    altitudes = {"Hubble-Echo": 540, "CommSat-1": 800, "Radar-Eye": 700}
    altitude_m = {name: alt * 1000 for name, alt in altitudes.items()}
    print(altitude_m) # 单位从 km 转为 m

    # 加条件过滤
    high_orbit = {k: v for k, v in altitudes.items() if v >= 700}
    print(high_orbit)

    # ———————————————————————-
    # 7.6 collections 模块:defaultdict 与 Counter
    # ———————————————————————-
    from collections import defaultdict, Counter

    # defaultdict:访问不存在的键时,自动生成默认值
    events_by_day = defaultdict(list) # 默认值是空列表
    events_by_day["2026-08-12"].append("launch")
    events_by_day["2026-08-12"].append("orbit-insert")
    events_by_day["2026-08-13"].append("payload-deploy")
    print(dict(events_by_day))

    # Counter:快速统计每个值出现多少次
    events = ["launch", "orbit-insert", "comms-check", "launch", "comms-check", "launch"]
    counts = Counter(events)
    print(counts) # Counter({'launch': 3, 'comms-check': 2, 'orbit-insert': 1})
    print(counts.most_common(2)) # 出现次数最多的前 2 项

    # ———————————————————————-
    # 7.7 实战:建立一份完整的卫星遥测档案
    # ———————————————————————-
    fleet = {
    "SAT-001": {"alt_km": 540, "voltage": 28.1, "temp_c": 15.0},
    "SAT-002": {"alt_km": 800, "voltage": 27.9, "temp_c": 22.4},
    "SAT-003": {"alt_km": 360, "voltage": 28.4, "temp_c": 10.1},
    }

    # 找出电压最低的那颗
    lowest_sat = min(fleet, key=lambda sid: fleet[sid]["voltage"])
    print(f"\\nLowest voltage: {lowest_sat} -> {fleet[lowest_sat]['voltage']}V")

    # 计算平均温度
    avg_temp = sum(s["temp_c"] for s in fleet.values()) / len(fleet)
    print(f"Average temperature: {avg_temp:.2f} °C")

    课堂建议:把代码贴到 chapters/chapter07_dictionaries.py,运行验证后再讲解每段作用。


    第 08 章–可重用的任务模块

    知识点:def / 参数 / return / 默认值 / *args / lambda 实战主题:可重用的任务模块

    """第 08 章:可重用的任务模块(函数)。

    学习路径(零跳跃):
    def 定义 → 位置参数 / 关键字参数 → 默认值 → return → *args / **kwargs
    → 作用域 → 文档字符串 → lambda

    实战:通用推进力与 Δv(速度增量)计算函数。
    """

    # ———————————————————————-
    # 8.1 最简单的函数
    # ———————————————————————-
    def greet(): # def 关键字 + 函数名 + () + :
    print("Mission Control: all systems go.") # 函数体要缩进

    greet() # 调用

    # ———————————————————————-
    # 8.2 参数与返回值
    # ———————————————————————-
    def orbital_period(radius_m: float, gm: float) > float: # 类型提示(hint),运行时不强制
    """根据半径 r 与引力参数 gm 计算轨道周期。"""
    import math
    speed = math.sqrt(gm / radius_m)
    return 2 * math.pi * radius_m / speed

    period = orbital_period(radius_m=7.0e6, gm=3.986e14)
    print(f"Period ≈ {period:.1f} s ({period/60:.2f} min)")

    # ———————————————————————-
    # 8.3 默认参数与关键字参数
    # ———————————————————————-
    def report(phase: str, *, verbose: bool = False, prefix: str = "STATUS") > None:
    """用 * 强制后续参数必须以关键字形式传。"""
    msg = f"[{prefix}] Phase = {phase}"
    if verbose:
    msg += " (verbose mode)"
    print(msg)

    report("orbit") # 用默认 verbose=False
    report("ascent", verbose=True) # 关键字参数
    # report("ascent", True) # 报错:verbose 必须是关键字

    # ———————————————————————-
    # 8.4 可变参数 *args / **kwargs
    # ———————————————————————-
    def total_payload(*masses_kg: float) > float:
    """把任意多个载荷质量相加。"""
    return sum(masses_kg)

    print(total_payload(120, 250, 75)) # 445.0
    print(total_payload(500)) # 500.0

    def build_satellite(name: str, **attrs: object) > dict:
    """用关键字参数自由扩展属性。"""
    sat = {"name": name}
    sat.update(attrs)
    return sat

    print(build_satellite("CommSat-1", mass_kg=480, alt_km=800, purpose="comms"))

    # ———————————————————————-
    # 8.5 返回多个值:实际是返回一个元组
    # ———————————————————————-
    def burn_budget(initial: float, exhaust_v: float, mass_flow: float, burn_time: float):
    """齐奥尔科夫斯基火箭方程:Δv = v_e * ln(m0 / m1)。"""
    import math
    final_mass = initial mass_flow * burn_time
    delta_v = exhaust_v * math.log(initial / final_mass)
    return delta_v, final_mass

    dv, mf = burn_budget(initial=500_000, exhaust_v=320, mass_flow=1200, burn_time=120)
    print(f"Δv = {dv:.1f} m/s, final mass = {mf:.0f} kg")

    # ———————————————————————-
    # 8.6 作用域:局部 vs 全局
    # ———————————————————————-
    GROUND_LEVEL_PRESSURE = 101.3 # 全局常量

    def cabin_pressure_sim(altitude_km: float) > float:
    pressure_at_alt = GROUND_LEVEL_PRESSURE * (1 2.2558e-5 * altitude_km) ** 5.2559
    return pressure_at_alt # 局部变量用完即丢

    print(cabin_pressure_sim(5))

    # ———————————————————————-
    # 8.7 文档字符串与 help
    # ———————————————————————-
    def escape_velocity(mass_kg: float, radius_m: float) > float:
    """计算逃逸速度 v = sqrt(2GM/r)。

    Args:
    mass_kg: 天体质量(千克)。
    radius_m: 天体半径(米)。

    Returns:
    逃逸速度(米/秒)。
    """
    G = 6.67430e-11
    return (2 * G * mass_kg / radius_m) ** 0.5

    print(help(escape_velocity)) # help() 会显示文档字符串

    # ———————————————————————-
    # 8.8 lambda:单行匿名函数,常用于简短转换
    # ———————————————————————-
    altitudes_km = [400, 550, 800, 1200]
    to_meters = lambda km: km * 1000
    print(list(map(to_meters, altitudes_km))) # [400000, 550000, …]

    # sorted 配合 key 使用最常见
    altitudes_sorted = sorted(altitudes_km, key=lambda x: x)
    print(altitudes_sorted)

    课堂建议:把代码贴到 chapters/chapter08_functions.py,运行验证后再讲解每段作用。


    第 09 章–错误处理

    知识点:异常 / try-except / raise / 自定义异常 / warnings 实战主题:错误处理

    """第 09 章:错误处理。

    学习路径(零跳跃):
    异常类型认知 → try / except / else / finally → 主动抛出 raise
    → 自定义异常 → except 链 → 警告 warnings

    实战:遥测数据校验模块。
    """

    # ———————————————————————-
    # 9.1 常见内置异常
    # ———————————————————————-
    # 这些先"认识名字",看到能猜到大致问题即可:
    # ZeroDivisionError 除以 0
    # ValueError 值类型正确但内容不合适
    # TypeError 类型错误
    # KeyError / IndexError 取值不存在
    # FileNotFoundError 文件未找到

    # 故意触发一次看看效果(注释掉防止运行报错)
    # 1 / 0

    # ———————————————————————-
    # 9.2 try / except:捕获并处理异常
    # ———————————————————————-
    def parse_altitude(text: str) > float:
    try:
    return float(text)
    except ValueError as e:
    print(f"无法解析 '{text}': {e}")
    return float("nan") # NaN = Not a Number,标记无效

    print(parse_altitude("540.0")) # 540.0
    print(parse_altitude("not-a-number")) # 触发 except

    # ———————————————————————-
    # 9.3 多个 except 与"裸 except"
    # ———————————————————————-
    def safe_div(a, b):
    try:
    return a / b
    except ZeroDivisionError:
    print("Cannot divide by zero.")
    return float("inf")
    except TypeError:
    print("Both operands must be numbers.")
    return float("nan")

    print(safe_div(10, 2)) # 5.0
    print(safe_div(10, 0)) # inf
    print(safe_div(10, "x")) # NaN

    # ———————————————————————-
    # 9.4 else 与 finally
    # else:try 块**没有**抛异常时执行
    # finally:无论是否抛异常,最后一定会执行(适合清理资源)
    # ———————————————————————-
    def read_telemetry_line(line: str) > float:
    try:
    value = float(line)
    except ValueError:
    print(f"Bad line: {line!r}") # !r 让字符串带上引号显示
    raise # 把异常原样重新抛出
    else:
    print(f"Parsed OK: {value}") # 没出错才执行
    return value
    finally:
    print("— telemetry read attempt done —") # 总执行

    try:
    read_telemetry_line("22.4")
    read_telemetry_line("oops")
    except ValueError:
    pass # 外层已经记录过,这里静默

    # ———————————————————————-
    # 9.5 主动抛出 raise:把问题暴露给上层
    # ———————————————————————-
    def set_throttle(percent: float) > None:
    if not 0 <= percent <= 100:
    raise ValueError(f"throttle must be 0-100, got {percent}")
    print(f"Throttle set to {percent:.0f}%")

    try:
    set_throttle(120)
    except ValueError as e:
    print("Caught:", e)

    # ———————————————————————-
    # 9.6 自定义异常
    # ———————————————————————-
    class TelemetryError(Exception): # 继承 Python 内置的 Exception
    """遥测数据相关的异常基类。"""

    class OutOfRangeError(TelemetryError):
    """数值越界。"""

    class SensorTimeoutError(TelemetryError):
    """传感器超时。"""

    def check_temperature(celsius: float) > None:
    if celsius < 100 or celsius > 150:
    raise OutOfRangeError(f"Temperature {celsius}°C out of safe range")
    print("Temperature nominal.")

    try:
    check_temperature(250)
    except TelemetryError as e: # 捕获整个家族的异常
    print(f"Telemetry issue: {type(e).__name__}: {e}")

    # ———————————————————————-
    # 9.7 except 链:raise … from …
    # ———————————————————————-
    def load_config(path: str) > dict:
    try:
    with open(path, encoding="utf-8") as f:
    return eval(f.read()) # 仅作演示,真实场景应使用 json
    except FileNotFoundError as e:
    raise TelemetryError(f"Config missing: {path}") from e

    try:
    load_config("/no/such/file.cfg")
    except TelemetryError as e:
    print(f"Top-level: {e}")
    print(f"Caused by: {type(e.__cause__).__name__}: {e.__cause__}")

    # ———————————————————————-
    # 9.8 warnings 模块:发"提醒"而不中断
    # ———————————————————————-
    import warnings

    def low_fuel_alert(level: float) > None:
    """燃料过低时发出 RuntimeWarning(业务提醒),不中断流程。

    注意:DeprecationWarning 是给"已废弃 API"用的,不适合业务告警。
    """
    if level < 10:
    warnings.warn("Fuel level below 10%", RuntimeWarning, stacklevel=2)

    low_fuel_alert(5) # 会输出警告,但默认只显示一次
    print("Continuing despite warning.")

    # 让 RuntimeWarning 默认总是显示(演示场景)
    warnings.simplefilter("always", RuntimeWarning)
    low_fuel_alert(3) # 第二次仍然会输出
    print("Continuing despite warning.")

    课堂建议:把代码贴到 chapters/chapter09_errors.py,运行验证后再讲解每段作用。


    第 10 章–文件与日志

    知识点:pathlib / with / JSON / logging 实战主题:文件与日志

    """第 10 章:文件与日志。

    学习路径(零跳跃):
    pathlib 路径 → 文本读写 → with 语句 → JSON 持久化 → logging 模块

    实战:把任务事件写入本地日志文件。
    """

    # ———————————————————————-
    # 10.1 pathlib:面向对象的路径处理(取代 os.path)
    # ———————————————————————-
    from pathlib import Path

    base_dir = Path(__file__).resolve().parent # 当前脚本所在目录
    log_file = base_dir / "mission.log" # 用 / 拼接路径,跨平台
    data_file = base_dir / "telemetry.json"
    print("Log file will be at:", log_file)

    # ———————————————————————-
    # 10.2 写文本文件(with 语句自动关闭)
    # ———————————————————————-
    with log_file.open("w", encoding="utf-8") as f:
    f.write("2026-08-12T09:00:00 LAUNCH OK\\n")
    f.write("2026-08-12T09:05:30 ORBIT INSERT\\n")
    f.write("2026-08-12T09:10:00 PAYLOAD DEPLOY\\n")

    # ———————————————————————-
    # 10.3 读文本文件
    # ———————————————————————-
    with log_file.open(encoding="utf-8") as f:
    contents = f.read()
    print(contents)

    # 按行读取
    with log_file.open(encoding="utf-8") as f:
    for line in f:
    print("LINE:", line.rstrip()) # rstrip() 去掉行尾换行

    # ———————————————————————-
    # 10.4 追加模式 "a" 与二进制模式 "wb"/"rb"
    # ———————————————————————-
    with log_file.open("a", encoding="utf-8") as f:
    f.write("2026-08-12T10:00:00 COMMS CHECK OK\\n")

    # 二进制:用 pickle 简单演示
    import pickle
    blob = {"sat_id": "SAT-001", "alt_km": 540.0}
    bin_file = base_dir / "snapshot.bin"
    with bin_file.open("wb") as f:
    pickle.dump(blob, f)
    with bin_file.open("rb") as f:
    restored = pickle.load(f)
    print("Restored:", restored)

    # ———————————————————————-
    # 10.5 JSON:跨语言通用的数据格式
    # ———————————————————————-
    import json

    telemetry = {
    "sat_id": "SAT-001",
    "samples": [
    {"t": 0, "alt_km": 540.0, "voltage": 28.1},
    {"t": 1, "alt_km": 540.1, "voltage": 28.0},
    {"t": 2, "alt_km": 540.3, "voltage": 27.9},
    ],
    }

    with data_file.open("w", encoding="utf-8") as f:
    json.dump(telemetry, f, indent=2) # indent 让文件更易读

    with data_file.open(encoding="utf-8") as f:
    restored_json = json.load(f)
    print("First sample:", restored_json["samples"][0])

    # ———————————————————————-
    # 10.6 logging:标准日志库(比 print 更专业)
    # ———————————————————————-
    import logging

    logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    filename=log_file,
    filemode="a", # 追加
    encoding="utf-8",
    )
    log = logging.getLogger("mission")

    log.debug("debug-level detail") # 默认等级 INFO 不会显示
    log.info("Mission clock started")
    log.warning("Battery below 30%")
    log.error("Lost contact with SAT-002")
    log.critical("Range safety violated")

    # ———————————————————————-
    # 10.7 实战:把遥测样本写成 JSON 报告
    # ———————————————————————-
    report = {
    "mission": "Artemis-VII",
    "generated_at": "2026-08-12T09:30:00",
    "events": ["launch", "orbit-insert", "payload-deploy", "comms-check"],
    }
    report_file = base_dir / "report.json"
    with report_file.open("w", encoding="utf-8") as f:
    json.dump(report, f, indent=2)

    print("Wrote:", report_file)
    print("Done. Cleaning up generated files:")
    for p in (log_file, data_file, bin_file, report_file):
    p.unlink(missing_ok=True) # missing_ok=True 让不存在的文件不报错

    课堂建议:把代码贴到 chapters/chapter10_files.py,运行验证后再讲解每段作用。


    第 11 章–面向对象:航天器类

    知识点:class / init / 继承 / dataclass / Enum 实战主题:面向对象:航天器类

    """第 11 章:面向对象:航天器类。

    学习路径(零跳跃):
    class 定义 → __init__ 构造 → 实例方法 → 实例属性 vs 类属性
    → 继承 → 方法重写 → @dataclass → 枚举 Enum

    实战:Spacecraft 家族(卫星、火箭、探测器)。
    """

    # ———————————————————————-
    # 11.1 最简单的类
    # ———————————————————————-
    class Spacecraft:
    """所有航天器的基类。"""

    species = "spacecraft" # 类属性:所有实例共享

    def __init__(self, name: str, mass_kg: float):
    self.name = name # 实例属性:每个实例独有
    self.mass_kg = mass_kg
    self.operational = True

    def status(self) > str:
    state = "operational" if self.operational else "offline"
    return f"{self.name} [{state}] mass={self.mass_kg}kg"

    def deactivate(self) > None:
    self.operational = False

    sat = Spacecraft("Hubble-Echo", 12000)
    print(sat.status())
    print("Species:", sat.species)
    sat.deactivate()
    print(sat.status())

    # ———————————————————————-
    # 11.2 __repr__ / __str__:让打印结果可读
    # ———————————————————————-
    class Satellite(Spacecraft):
    def __init__(self, name: str, mass_kg: float, alt_km: float):
    super().__init__(name, mass_kg) # 调用父类 __init__
    self.alt_km = alt_km

    def __repr__(self) > str: # 给开发者的精确表示
    return f"Satellite(name={self.name!r}, mass={self.mass_kg}, alt={self.alt_km})"

    def __str__(self) > str: # 给用户的可读文本
    return f"Satellite {self.name} @ {self.alt_km} km"

    comm = Satellite("CommSat-1", 480, 800)
    print(repr(comm))
    print(str(comm))

    # ———————————————————————-
    # 11.3 继承与方法重写
    # ———————————————————————-
    class Rocket(Spacecraft):
    """有推力的航天器。"""
    def __init__(self, name: str, mass_kg: float, thrust_kn: float):
    super().__init__(name, mass_kg)
    self.thrust_kn = thrust_kn

    def status(self) > str: # 重写父类方法
    base = super().status()
    return f"{base} thrust={self.thrust_kn}kN"

    artemis = Rocket("Artemis-VII", 2670500, 39000)
    print(artemis.status())

    # ———————————————————————-
    # 11.4 鸭子类型(Duck Typing)
    # Python 不严格检查类型,只要对象有需要的属性/方法即可使用。
    # ———————————————————————-
    def report_status(obj) > None:
    print(obj.status()) # 不管 obj 是什么类型,只要它有 .status()

    report_status(comm)
    report_status(artemis)

    # ———————————————————————-
    # 11.5 @dataclass:减少样板代码(PEP 557,Python 3.7+)
    # ———————————————————————-
    from dataclasses import dataclass, field

    @dataclass
    class Probe:
    name: str
    target: str
    payload_kg: float = 0.0
    instruments: list[str] = field(default_factory=list) # 可变默认值要这样写

    voyager = Probe("Voyager-3", "Europa", payload_kg=850)
    voyager.instruments.append("spectrometer")
    print(voyager)

    # 自动生成的 __eq__ 让对象可以直接比较
    print(voyager == Probe("Voyager-3", "Europa", payload_kg=850))

    # ———————————————————————-
    # 11.6 Enum:用名字代替"魔法数字"
    # ———————————————————————-
    from enum import Enum, auto

    class MissionPhase(Enum):
    PRELAUNCH = auto()
    ASCENT = auto()
    ORBIT = auto()
    LANDING = auto()

    print(MissionPhase.ORBIT) # MissionPhase.ORBIT
    print(MissionPhase.ORBIT.name, MissionPhase.ORBIT.value)

    current = MissionPhase.ORBIT
    match current:
    case MissionPhase.PRELAUNCH:
    print("Final checks")
    case MissionPhase.ASCENT:
    print("Climbing")
    case MissionPhase.ORBIT:
    print("Coast")
    case MissionPhase.LANDING:
    print("Descent")

    # ———————————————————————-
    # 11.7 实战:把第 08 章的物理公式封装成方法
    # ———————————————————————-
    import math

    @dataclass
    class Orbiter(Spacecraft):
    name: str
    mass_kg: float
    alt_km: float

    @property
    def orbital_speed(self) > float: # @property 让方法像属性一样访问
    GM = 3.986e14
    r = 6.371e6 + self.alt_km * 1000
    return math.sqrt(GM / r)

    orb = Orbiter("SAT-001", 480, 540)
    print(f"{orb.name} speed = {orb.orbital_speed:.0f} m/s")

    课堂建议:把代码贴到 chapters/chapter11_oop.py,运行验证后再讲解每段作用。


    第 12 章–迭代器与生成器

    知识点:迭代协议 / yield / 生成器表达式 / itertools 实战主题:迭代器与生成器

    """第 12 章:迭代器与生成器。

    学习路径(零跳跃):
    迭代协议 → iter() / next() → 生成器函数 yield → 生成器表达式
    → itertools 工具 → 实时数据流模拟

    实战:用生成器模拟卫星实时遥测流。
    """

    # ———————————————————————-
    # 12.1 迭代协议:能被 for 循环遍历的对象都实现了 __iter__
    # ———————————————————————-
    # for x in obj 等价于:
    # it = iter(obj)
    # while True:
    # try:
    # x = next(it)
    # except StopIteration:
    # break

    nums = [10, 20, 30]
    it = iter(nums)
    print(next(it)) # 10
    print(next(it)) # 20
    print(next(it)) # 30
    # print(next(it)) # 触发 StopIteration

    # ———————————————————————-
    # 12.2 生成器函数:用 yield 一次返回一个值
    # ———————————————————————-
    def countdown(n: int):
    while n > 0:
    yield n # 暂停在此,把 n 返回给调用者
    n -= 1

    for x in countdown(5):
    print("count:", x)

    # ———————————————————————-
    # 12.3 实战:实时遥测流
    # ———————————————————————-
    import random, math, time

    def telemetry_stream(sat_id: str, base_alt_km: float = 540.0):
    """无限产出 (timestamp, alt_km, voltage) 元组的生成器。"""
    t = 0
    while True:
    alt = base_alt_km + 5 * math.sin(t / 10.0) + random.uniform(0.5, 0.5)
    voltage = 28.0 + random.uniform(0.3, 0.3)
    yield t, sat_id, alt, voltage
    t += 1

    stream = telemetry_stream("SAT-001") # 创建生成器
    for _ in range(3): # 拉取 3 次
    t, sid, alt, v = next(stream)
    print(f"t={t}s {sid} alt={alt:.2f}km v={v:.2f}V")

    # ———————————————————————-
    # 12.4 生成器表达式:像列表推导式,但用 () 包裹且惰性求值
    # ———————————————————————-
    # 列表推导式:立刻生成全部元素,耗内存
    sq_list = [x * x for x in range(5)]
    print(sq_list)

    # 生成器表达式:按需生成
    sq_gen = (x * x for x in range(5))
    print(next(sq_gen), next(sq_gen)) # 0 1
    print(sum(sq_gen)) # 0+1+4+9+16=30,已消耗两项
    # 注:上面的 sum 会接着从 2*2=4 开始

    # ———————————————————————-
    # 12.5 itertools:标准库的迭代工具箱
    # ———————————————————————-
    import itertools

    # count:无限计数器
    counter = itertools.count(start=100, step=2)
    print("first 5 from count:", [next(counter) for _ in range(5)])

    # cycle:无限循环一个序列
    cycler = itertools.cycle(["GO", "HOLD"])
    print("first 4 from cycle:", [next(cycler) for _ in range(4)])

    # chain:把多个迭代器串起来
    combined = itertools.chain([1, 2], (3, 4), "ab")
    print("chain:", list(combined))

    # islice:切片一个无限迭代器
    print("islice count 5:", list(itertools.islice(itertools.count(), 5)))

    # batched(Python 3.13+):按批次取
    for batch in itertools.batched(range(10), 3):
    print("batch:", batch)

    # ———————————————————————-
    # 12.6 实战:从遥测流里只保留告警项
    # ———————————————————————-
    def filter_alerts(stream, voltage_low: float = 27.9):
    """消耗上游生成器,只把电压过低的样本透传出去。"""
    for sample in stream:
    t, sid, alt, v = sample
    if v < voltage_low:
    yield t, sid, v

    # 重新创建一个新流(前面的 stream 已推进过)
    fresh_stream = telemetry_stream("SAT-002", base_alt_km=600)
    alert_pipe = filter_alerts(fresh_stream, voltage_low=27.9)
    # islice 让无限生成器只取前若干项,避免挂死
    import itertools
    print("ALERT samples (first 5 checked):")
    for item in itertools.islice(alert_pipe, 5):
    print("ALERT:", item)
    print("(可能 0 条:取决于随机数)")

    # ———————————————————————-
    # 12.7 send / close:进阶用法(向生成器回传值 / 关闭生成器)
    # ———————————————————————-
    def accumulator():
    total = 0
    while True:
    value = yield total # 既能产出 total,又能接收外部 send 的值
    if value is None:
    break
    total += value

    acc = accumulator()
    next(acc) # 启动生成器,跑到第一个 yield
    print("acc send 10:", acc.send(10)) # total=10
    print("acc send 5 :", acc.send(5)) # total=15
    acc.close() # 关闭生成器

    课堂建议:把代码贴到 chapters/chapter12_iterators.py,运行验证后再讲解每段作用。


    第 13 章–类型提示与契约

    知识点:类型提示 / typing / Protocol / TypeGuard 实战主题:类型提示与契约

    """第 13 章:类型提示与契约。

    学习路径(零跳跃):
    内置类型提示 → PEP 604 联合类型 → TypeAlias → Generic[T]
    → Protocol 协议 → TypeGuard → runtime 自检

    实战:为遥测模块加上严格的类型契约。
    """

    # ———————————————————————-
    # 13.1 内置类型提示(PEP 585:Python 3.9+ 可直接用 list[int])
    # ———————————————————————-
    from typing import TypeAlias # 仅留 TypeAlias;X | None 用 PEP 604

    def mean(values: list[float]) > float:
    return sum(values) / len(values)

    print(mean([1.0, 2.0, 3.0]))

    # ———————————————————————-
    # 13.2 PEP 604 联合类型:X | None 表示"可能是 None"(Python 3.10+)
    # 注:typing.Optional[X] 与 Union[X, None] 完全等价,但 PEP 604 更简洁。
    # ———————————————————————-
    def find_satellite(fleet: dict[str, dict], sat_id: str) > dict | None:
    return fleet.get(sat_id) # .get 不存在时返回 None

    fleet = {"SAT-001": {"alt_km": 540}}
    print(find_satellite(fleet, "SAT-001"))
    print(find_satellite(fleet, "MISSING"))

    # ———————————————————————-
    # 13.3 TypeAlias:让复杂类型有名字(PEP 613)
    # ———————————————————————-
    Telemetry: TypeAlias = dict[str, float | int | str] # PEP 604 联合写法
    Fleet: TypeAlias = dict[str, Telemetry]

    def average_altitude(f: Fleet) > float:
    return sum(s["alt_km"] for s in f.values()) / len(f)

    # ———————————————————————-
    # 13.4 泛型函数(typing.Generic,PEP 484)
    # ———————————————————————-
    from typing import TypeVar, Generic

    T = TypeVar("T") # 类型变量

    def first(items: list[T]) > T | None:
    return items[0] if items else None

    print(first([10, 20])) # int
    print(first(["a", "b"])) # str

    # ———————————————————————-
    # 13.5 Protocol:结构化子类型(鸭子类型的"官方版")
    # ———————————————————————-
    from typing import Protocol, runtime_checkable

    class TelemetrySource(Protocol):
    """任何定义了 read() -> dict 的对象都满足这个协议。"""
    def read(self) > dict: ...

    @runtime_checkable
    class Readable(Protocol):
    def read(self) > dict: ...

    class MockSensor:
    def read(self) > dict:
    return {"voltage": 28.1}

    mock = MockSensor()
    print(isinstance(mock, Readable)) # True,结构匹配即可

    # ———————————————————————-
    # 13.6 TypeGuard:自定义类型守卫
    # ———————————————————————-
    from typing import TypeGuard

    def is_altitude_dict(obj: object) > TypeGuard[dict[str, float]]:
    return (
    isinstance(obj, dict)
    and "alt_km" in obj
    and isinstance(obj["alt_km"], (int, float))
    )

    def describe(obj: object) > None:
    if is_altitude_dict(obj):
    # 在这个分支里 obj 被收窄为 dict[str, float]
    print(f"altitude = {obj['alt_km']:.1f} km")

    describe({"alt_km": 540.0})
    describe("not a dict")

    # ———————————————————————-
    # 13.7 运行时类型自检:typing.get_type_hints
    # ———————————————————————-
    import typing

    def burn_budget(initial: float, exhaust_v: float, mass_flow: float, burn_time: float) > float:
    import math
    final_mass = initial mass_flow * burn_time
    return exhaust_v * math.log(initial / final_mass)

    print(typing.get_type_hints(burn_budget))
    # {'initial': <class 'float'>, 'exhaust_v': <class 'float'>, …

    # ———————————————————————-
    # 13.8 实战:把第 11 章的 Orbiter 加上完整类型契约
    # ———————————————————————-
    from dataclasses import dataclass

    @dataclass
    class TypedOrbiter:
    name: str
    mass_kg: float
    alt_km: float

    def orbital_speed(self) > float:
    import math
    GM = 3.986e14
    r = 6.371e6 + self.alt_km * 1000
    return math.sqrt(GM / r)

    def summary(self) > str:
    return f"{self.name}: alt={self.alt_km}km, v={self.orbital_speed():.0f}m/s"

    orb = TypedOrbiter("SAT-X", 480, 540)
    print(orb.summary())

    课堂建议:把代码贴到 chapters/chapter13_typing.py,运行验证后再讲解每段作用。


    第 14 章–并发任务

    知识点:asyncio / async-await / gather / TaskGroup 实战主题:并发任务

    """第 14 章:并发任务。

    学习路径(零跳跃):
    并发模型对比 → threading → asyncio 基础 → async/await → Task 编排

    实战:并发监控多颗卫星的遥测。
    """

    # ———————————————————————-
    # 14.1 并发模型对比(先有概念,再写代码)
    # ———————————————————————-
    # 多线程 (threading) :I/O 密集;GIL 让 CPU 密集无法真正并行
    # 多进程 (multiprocessing):CPU 密集;绕过 GIL,但开销大
    # 异步 (asyncio) :单线程协作式调度,适合高并发 I/O
    # 本章聚焦 asyncio:现代 Python 网络/任务编排首选

    # ———————————————————————-
    # 14.2 asyncio 基础:事件循环 + 协程
    # ———————————————————————-
    import asyncio
    import random

    async def fetch_telemetry(sat_id: str, delay: float) > dict:
    """模拟从一颗卫星读取遥测。async def 定义的就是协程函数。"""
    await asyncio.sleep(delay) # 异步等待,不阻塞事件循环
    return {"sat_id": sat_id, "voltage": 28.0 + random.uniform(0.5, 0.5)}

    # ———————————————————————-
    # 14.3 asyncio.run 启动事件循环
    # ———————————————————————-
    async def main_simple() > None:
    result = await fetch_telemetry("SAT-001", 0.1)
    print("simple:", result)

    asyncio.run(main_simple())

    # ———————————————————————-
    # 14.4 gather:并发等待多个协程
    # ———————————————————————-
    async def gather_demo() > None:
    tasks = [fetch_telemetry(f"SAT-{i:03d}", random.uniform(0.05, 0.2))
    for i in range(1, 6)]
    results = await asyncio.gather(*tasks) # 并发执行,全部完成后返回列表
    for r in results:
    print("gather:", r)

    asyncio.run(gather_demo())

    # ———————————————————————-
    # 14.5 create_task:手动调度
    # ———————————————————————-
    async def task_demo() > None:
    task1 = asyncio.create_task(fetch_telemetry("SAT-A", 0.1))
    task2 = asyncio.create_task(fetch_telemetry("SAT-B", 0.05))
    print("waiting A…")
    a = await task1
    print("waiting B…")
    b = await task2
    print(a, b)

    asyncio.run(task_demo())

    # ———————————————————————-
    # 14.6 TaskGroup:Python 3.11+ 推荐的并发写法
    # 任何一个子任务抛异常,其余会自动取消,结构更清晰。
    # ———————————————————————-
    async def tg_demo() > None:
    async with asyncio.TaskGroup() as tg:
    results = [tg.create_task(fetch_telemetry(f"SAT-{i}", 0.05))
    for i in range(3)]
    # 退出 with 块说明全部成功
    for r in results:
    print("tg:", r.result())

    asyncio.run(tg_demo())

    # ———————————————————————-
    # 14.7 带超时的并发
    # ———————————————————————-
    async def slow_sensor() > dict:
    await asyncio.sleep(1.0)
    return {"ok": True}

    async def with_timeout() > None:
    try:
    result = await asyncio.wait_for(slow_sensor(), timeout=0.2)
    print("got:", result)
    except TimeoutError:
    print("Sensor timed out.")

    asyncio.run(with_timeout())

    # ———————————————————————-
    # 14.8 实战:并发监控 5 颗卫星
    # ———————————————————————-
    async def monitor_fleet() > None:
    sats = [f"SAT-{i:03d}" for i in range(1, 6)]

    async def poll(sat_id: str) > None:
    for tick in range(2):
    data = await fetch_telemetry(sat_id, 0.05)
    print(f" {sat_id} tick={tick} voltage={data['voltage']:.2f}V")

    async with asyncio.TaskGroup() as tg:
    for s in sats:
    tg.create_task(poll(s))
    print("Fleet poll complete.")

    asyncio.run(monitor_fleet())

    # ———————————————————————-
    # 14.9 线程小例子:阻塞 I/O 也可放线程里跑
    # ———————————————————————-
    import threading

    def blocking_io(name: str) > None:
    import time
    time.sleep(0.2)
    print(f"[thread {name}] done")

    threads = [threading.Thread(target=blocking_io, args=(f"T{i}",)) for i in range(3)]
    for t in threads: t.start()
    for t in threads: t.join()
    print("All threads finished.")

    课堂建议:把代码贴到 chapters/chapter14_concurrency.py,运行验证后再讲解每段作用。


    第 15 章–综合实战项目

    知识点:dataclass / asyncio / argparse / 综合 实战主题:综合实战项目

    """第 15 章:综合实战项目 —— Mission Control CLI。

    学习路径(零跳跃):
    包结构 → 模块拆分 → CLI 参数解析 argparse → 单元测试 → 打包

    本章将前面的所有知识组合成一个完整的命令行小工具。
    为了方便直接运行,本文件采用"单文件可执行"形式,但内部按模块风格组织。
    """

    import argparse
    import asyncio
    import json
    import math
    import random
    from dataclasses import dataclass, field, asdict
    from pathlib import Path
    from typing import Iterable

    # ———————————————————————-
    # 15.1 数据模型:dataclass 让实体定义简洁
    # ———————————————————————-
    @dataclass
    class Telemetry:
    sat_id: str
    alt_km: float
    voltage: float
    temp_c: float

    @dataclass
    class Fleet:
    crafts: list[Telemetry] = field(default_factory=list)

    def add(self, t: Telemetry) > None:
    self.crafts.append(t)

    def lowest_voltage(self) > Telemetry | None:
    return min(self.crafts, key=lambda t: t.voltage) if self.crafts else None

    def average_altitude(self) > float:
    return sum(t.alt_km for t in self.crafts) / len(self.crafts) if self.crafts else 0.0

    # ———————————————————————-
    # 15.2 业务函数:复用第 03/08 章的物理公式
    # ———————————————————————-
    GM_EARTH = 3.986e14
    EARTH_RADIUS_M = 6.371e6

    def orbital_speed(alt_km: float) > float:
    r = EARTH_RADIUS_M + alt_km * 1000
    return math.sqrt(GM_EARTH / r)

    async def fetch_telemetry(sat_id: str, alt_km: float) > Telemetry:
    await asyncio.sleep(random.uniform(0.01, 0.05))
    return Telemetry(
    sat_id=sat_id,
    alt_km=alt_km + random.uniform(1.0, 1.0),
    voltage=28.0 + random.uniform(0.4, 0.4),
    temp_c=15.0 + random.uniform(3.0, 3.0),
    )

    async def poll_fleet(altitudes: Iterable[float]) > Fleet:
    fleet = Fleet()
    sats = list(altitudes)
    async with asyncio.TaskGroup() as tg:
    tasks = [tg.create_task(fetch_telemetry(f"SAT-{i+1:03d}", alt))
    for i, alt in enumerate(sats)]
    for task in tasks:
    fleet.add(task.result())
    return fleet

    # ———————————————————————-
    # 15.3 报告输出:格式化表格 + JSON
    # ———————————————————————-
    def print_report(fleet: Fleet) > None:
    print(f"{'ID':<10} {'ALT (km)':>10} {'VOLT (V)':>10} {'TEMP (C)':>10} {'SPEED (m/s)':>14}")
    print("-" * 56)
    for t in fleet.crafts:
    speed = orbital_speed(t.alt_km)
    print(f"{t.sat_id:<10} {t.alt_km:>10.2f} {t.voltage:>10.2f} {t.temp_c:>10.2f} {speed:>14.1f}")
    print("-" * 56)
    print(f"Average altitude : {fleet.average_altitude():.2f} km")
    low = fleet.lowest_voltage()
    if low:
    print(f"Lowest voltage : {low.sat_id} ({low.voltage:.2f} V)")

    def save_report(fleet: Fleet, path: Path) > None:
    payload = {
    "crafts": [asdict(t) for t in fleet.crafts],
    "summary": {
    "average_altitude_km": fleet.average_altitude(),
    "lowest_voltage": asdict(fleet.lowest_voltage()) if fleet.lowest_voltage() else None,
    },
    }
    path.write_text(json.dumps(payload, indent=2), encoding="utf-8")

    # ———————————————————————-
    # 15.4 CLI 入口:argparse
    # ———————————————————————-
    def build_parser() > argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="missionctl", description="Mission Control CLI")
    parser.add_argument("–altitudes", nargs="+", type=float,
    default=[400, 540, 800, 1200],
    help="目标轨道高度列表(km)")
    parser.add_argument("–out", type=Path, help="可选:把报告写入 JSON 文件")
    return parser

    async def amain(args: argparse.Namespace) > int:
    fleet = await poll_fleet(args.altitudes)
    print_report(fleet)
    if args.out:
    save_report(fleet, args.out)
    print(f"Report saved to: {args.out}")
    return 0

    def main(argv: list[str] | None = None) > int:
    parser = build_parser()
    args = parser.parse_args(argv)
    return asyncio.run(amain(args))

    if __name__ == "__main__":
    raise SystemExit(main())

    # ———————————————————————-
    # 15.5 内置冒烟测试(取消注释运行)
    # ———————————————————————-
    """
    def _smoke_test() -> None:
    fleet = Fleet([Telemetry("SAT-001", 540, 28.0, -15.0),
    Telemetry("SAT-002", 800, 27.8, -22.0)])
    assert fleet.average_altitude() == 670.0
    assert fleet.lowest_voltage().sat_id == "SAT-002"
    print("smoke test passed")

    _smoke_test()
    """

    课堂建议:把代码贴到 chapters/chapter15_project.py,运行验证后再讲解每段作用。


    5. 章节串联的"叙事弧线"

    为了让学员学完后能记住整体结构,备课时可以用这条故事线:

  • 章 01–02:地面控制台欢迎新指挥官 → 登记任务代号
  • 章 03:根据火箭参数算出"圆形轨道速度"——给火箭选轨道
  • 章 04:根据天气/能见度判断"今天能不能发射"
  • 章 05:倒计时 10-9-8… → 发射!
  • 章 06:把多颗卫星的轨道装进一个列表
  • 章 07:给每颗卫星建一份遥测字典
  • 章 08:把第 3 章的公式封装成可重用函数
  • 章 09:处理传感器异常数据
  • 章 10:把任务事件写入本地日志文件
  • 章 11:造一组 Spacecraft / Satellite / Rocket / Probe 类
  • 章 12:用生成器模拟"实时遥测数据流"
  • 章 13:给代码加上类型契约,更安全
  • 章 14:并发监控多颗卫星
  • 章 15:整合成 missionctl 命令行工具

  • 6. 常见学生问题 & 应对话术

    问题推荐回答
    「为什么 Python 不需要声明类型?」 引到"鸭子类型 + 动态类型",并预告第 13 章会重新"加回类型提示"做平衡
    「f-string 嵌表达式会不会很难读?」 鼓励"短表达式可以嵌,长表达式应该先赋给变量"
    「列表和元组到底选谁?」 “要改用 list,不会改用 tuple;元组也常用于函数返回多个值”
    「生成器和迭代器看起来很玄?」 把 yield 比喻成"暂停键",调用方 next() 就像"再按一下播放"
    「asyncio 和线程到底用哪个?」 “高并发 I/O 用 asyncio;阻塞型 I/O 且数量少时用线程;CPU 密集才用进程”

    7. 进阶延伸(学员学有余力时布置)

    • 给第 10 章的日志加上"按日期轮转"(logging.handlers.TimedRotatingFileHandler)
    • 给第 15 章加 pytest 单元测试
    • 把第 15 章发布到 PyPI:uv build && uv publish
    • 在第 14 章基础上加上 aiohttp 真实抓取某公开遥测 API(推荐 https://api.wheretheiss.at/v1/satellites/25544)

    7.5 废弃 API 与最新语法自检(讲师必看)

    Python 3.14 仍保留许多"能用但已废弃"的旧写法。课程绝不能让学生学到这些 API。 本教程在交付前已通过以下三层检查:

    层次工具作用
    静态 AST 自研扫描脚本(/tmp/scan_deprecated.py) 扫 imp / optparse / distutils / asyncore / typing.Dict / Optional[X] / Union[X,Y] 等
    编译期 python -m py_compile chapters/chapter*.py 语法错误直接报错
    运行期 python -W error::DeprecationWarning -W error::SyntaxWarning -W error::FutureWarning 三类"未来必删"的警告升为错误,运行时拦截

    讲师自检命令(建议每次开课前跑一次):

    cd python_code
    # 1) 静态扫描:应输出 "✅ 未发现明显的废弃 API 使用"
    uv run python /tmp/scan_deprecated.py

    # 2) 全章语法编译
    uv run python -m py_compile chapters/chapter*.py

    # 3) 严格警告模式跑全部章节
    for n in 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15; do
    f=chapters/chapter${n}_*.py
    if [ "$n" = "15" ]; then
    timeout 15 uv run python -W error::DeprecationWarning -W error::SyntaxWarning -W error::FutureWarning $f –altitudes 400 540 800
    else
    timeout 15 uv run python -W error::DeprecationWarning -W error::SyntaxWarning -W error::FutureWarning $f < /dev/null
    fi
    done
    # 全部应输出 exit=0

    本项目使用的"现代首选"语法清单(避免学生学到旧写法)

    知识点旧写法(已废弃 / 不推荐)本项目采用
    容器类型提示 typing.List[int]、typing.Dict[str, int] list[int]、dict[str, int](PEP 585,Python 3.9+)
    联合类型 Optional[X]、Union[A, B] `X
    路径处理 os.path.join(…) pathlib.Path + / 拼接
    命令行参数 optparse(已废弃) argparse(标准库)
    并发模型 asyncore(3.12 已移除) asyncio + TaskGroup(Python 3.11+)
    异常告警 warnings.warn(…, DeprecationWarning) 当业务告警用 RuntimeWarning 才是业务告警首选
    字符串格式化 "%s = %d" % (name, value) f-string:f"{name} = {value}"
    协程并行 asyncio.gather(*coros) 单独使用 async with asyncio.TaskGroup() 上下文(Python 3.11+)
    JSON 输出 手写 json.dumps(…) 后手工美化 json.dump(obj, f, indent=2)
    数据类 手写 __init__ + __repr__ + __eq__ @dataclass(PEP 557,Python 3.7+)
    枚举 用类属性手写常量 enum.Enum + auto()
    文件关闭 f = open(…); …; f.close() with open(…) as f:
    赞(0)
    未经允许不得转载:171主机测评 » 《太空探索任务控制台》Python 教程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址