{
  "schema_version": 1,
  "courses": [
    {
      "id": "python",
      "title": "Python 3.12 学习路线",
      "phase": "python",
      "description": "从环境、数据与控制流开始，逐步完成文件处理、面向对象和综合项目。",
      "chapters": [
        {
          "id": "py1", "title": "环境、输出与输入", "order_index": 1, "status": "complete", "difficulty": "beginner", "estimated_minutes": 90,
          "summary": "掌握 Python 运行方式、print 输出和 input 输入。", "common_mistakes": ["混淆字符串与数字", "忘记转换 input 返回值"],
          "project": {"title": "交互式自我介绍", "brief": "读取姓名和年龄并输出一句完整介绍。", "starter_code": "name = input()\nage = int(input())\n", "expected_output": "你好，我是{name}，今年{age}岁。", "acceptance": ["正确读取两行输入", "输出包含姓名和年龄"]},
          "lessons": [
            {"id":"py1l1","title":"运行第一个 Python 程序","order_index":1,"goals":["理解解释器和脚本","运行 print 示例"],"knowledge_points":[{"title":"解释执行","content":"Python 源文件由解释器逐句执行，保存为 .py 后即可运行。","analogy":"像翻译员边读边翻译。","common_mistake":"把 Python 当成需要先链接的 C 程序。"}],"example_code":"print('Hello, Python!')\n","summary":"脚本是 Python 学习的最小单元。","estimated_minutes":25},
            {"id":"py1l2","title":"输出格式与输入","order_index":2,"goals":["使用 print 的多个参数","读取用户输入"],"knowledge_points":[{"title":"input 返回字符串","content":"input 会等待一行文本；需要计算时使用 int 或 float 转换。","analogy":"输入框先把所有内容装进文字信封。"}],"example_code":"name = input('姓名: ')\nprint('你好，', name)\n","summary":"输入、转换、输出组成最基本的交互。","estimated_minutes":25},
            {"id":"py1l3","title":"练习与小项目","order_index":3,"goals":["完成输出与输入练习","独立完成自我介绍"],"knowledge_points":[{"title":"格式化字符串","content":"f-string 可以把变量清晰地嵌入文本。","analogy":"给句子留下带名字的插槽。"}],"example_code":"name='小明'\nage=18\nprint(f'你好，我是{name}，今年{age}岁。')\n","summary":"用小项目串起输入、转换和格式化输出。","estimated_minutes":40,
              "exercises":[
                {"id":"py1e1","type":"program","language":"python","runtime":"python312","question":"输出 Hello, Python!","answer":"judge:Hello, Python!","difficulty":"easy","knowledge_point":"print","required":true,"test_cases":[{"stdin":"","expected_stdout":"Hello, Python!\n"}]},
                {"id":"py1e2","type":"program","language":"python","runtime":"python312","question":"读取一个名字并输出 Hello, 名字!","answer":"judge:Hello","difficulty":"easy","knowledge_point":"input","required":true,"test_cases":[{"stdin":"Ada\n","expected_stdout":"Hello, Ada!\n"}]},
                {"id":"py1e3","type":"choice","language":"python","question":"input() 返回什么类型？","options":["str","int","float","bool"],"answer":"0","explanation":"input 默认返回字符串。","difficulty":"easy","knowledge_point":"input","required":true},
                {"id":"py1e4","type":"true_false","language":"python","question":"print 可以一次输出多个参数。","answer":"1","explanation":"多个参数默认以空格分隔。","difficulty":"easy","knowledge_point":"print","required":true},
                {"id":"py1e5","type":"fill","language":"python","question":"把字符串 12 转为整数应使用 ____。","answer":"int","explanation":"int('12') 返回整数 12。","difficulty":"easy","knowledge_point":"类型转换","required":true},
                {"id":"py1e6","type":"output_predict","language":"python","question":"预测输出","code":"print('A', 'B')","answer":"A B","explanation":"参数之间默认加入空格。","difficulty":"medium","knowledge_point":"print","required":true},
                {"id":"py1e7","type":"choice","language":"python","question":"Python 脚本文件通常使用哪个扩展名？","options":[".py",".c",".class",".html"],"answer":"0","explanation":"Python 源文件扩展名为 .py。","difficulty":"easy","knowledge_point":"环境","required":true},
                {"id":"py1e8","type":"program","language":"python","runtime":"python312","question":"读取两个整数并输出它们的和。","answer":"judge:sum","difficulty":"medium","knowledge_point":"输入转换","required":true,"test_cases":[{"stdin":"2 3\n","expected_stdout":"5\n"}]}
              ]}
          ]
        },
        {
          "id": "py2", "title": "变量和基本数据类型", "order_index": 2, "status": "complete", "difficulty": "beginner", "estimated_minutes": 100,
          "summary": "理解变量绑定、整数、浮点数、布尔值和类型检查。", "project": {"title":"温度转换器","brief":"把摄氏温度转换为华氏温度。","starter_code":"c = float(input())\n","expected_output":"华氏温度","acceptance":["使用浮点计算","输出转换结果"]},
          "lessons": [
            {"id":"py2l1","title":"变量是名字绑定","order_index":1,"goals":["创建和更新变量","理解动态类型"],"knowledge_points":[{"title":"变量绑定","content":"变量名绑定到对象，重新赋值可以绑定另一种类型。","analogy":"标签可以贴到不同盒子上。"}],"example_code":"score = 95\nscore = score + 2\nprint(score)\n","summary":"变量保存的是对象引用关系。","estimated_minutes":30},
            {"id":"py2l2","title":"数字、布尔与类型","order_index":2,"goals":["区分 int、float、bool","使用 type 检查类型"],"knowledge_points":[{"title":"基本类型","content":"整数适合计数，浮点数适合近似小数，bool 表示真或假。","analogy":"不同尺寸的量具对应不同类型。"}],"example_code":"count = 3\nratio = 0.5\nready = True\nprint(type(count).__name__)\n","summary":"选择合适类型能让表达式更清晰。","estimated_minutes":30},
            {"id":"py2l3","title":"练习与小项目","order_index":3,"goals":["完成变量练习","实现温度转换"],"knowledge_points":[{"title":"运算与转换","content":"算术运算会根据操作数产生结果，必要时显式转换类型。","analogy":"把刻度统一后再进行测量。"}],"example_code":"c = 20\nf = c * 9 / 5 + 32\nprint(f)\n","summary":"用类型与运算构成小型计算程序。","estimated_minutes":40,
              "exercises":[
                {"id":"py2e1","type":"program","language":"python","question":"输出变量 total 的值 42。","answer":"judge:42","difficulty":"easy","knowledge_point":"变量","required":true,"test_cases":[{"stdin":"","expected_stdout":"42\n"}]},
                {"id":"py2e2","type":"program","language":"python","question":"读取摄氏温度，输出华氏温度。","answer":"judge:68","difficulty":"medium","knowledge_point":"运算","required":true,"test_cases":[{"stdin":"20\n","expected_stdout":"68\n"}]},
                {"id":"py2e3","type":"choice","language":"python","question":"布尔值的字面量是？","options":["True","TRUE","true","yes"],"answer":"0","explanation":"Python 使用 True 和 False。","difficulty":"easy","knowledge_point":"bool","required":true},
                {"id":"py2e4","type":"true_false","language":"python","question":"变量在 Python 中必须先声明类型。","answer":"0","explanation":"Python 是动态类型语言。","difficulty":"easy","knowledge_point":"动态类型","required":true},
                {"id":"py2e5","type":"fill","language":"python","question":"查看对象类型的内置函数是 ____。","answer":"type","explanation":"type(value) 返回类型对象。","difficulty":"easy","knowledge_point":"类型","required":true},
                {"id":"py2e6","type":"output_predict","language":"python","question":"预测输出","code":"print(3 // 2)","answer":"1","explanation":"// 是整除。","difficulty":"medium","knowledge_point":"运算","required":true},
                {"id":"py2e7","type":"choice","language":"python","question":"float(2) 的结果是？","options":["2.0","'2'","2","错误"],"answer":"0","explanation":"float 将数字转为浮点数。","difficulty":"easy","knowledge_point":"类型转换","required":true},
                {"id":"py2e8","type":"program","language":"python","question":"读取两个数并输出平均值。","answer":"judge:3.0","difficulty":"medium","knowledge_point":"浮点运算","required":true,"test_cases":[{"stdin":"2 4\n","expected_stdout":"3.0\n"}]}
              ]}
          ]
        },
        {
          "id": "py3", "title": "条件语句与循环", "order_index": 3, "status": "complete", "difficulty": "beginner", "estimated_minutes": 110,
          "summary": "使用 if、for、while 和循环控制语句表达流程。", "project": {"title":"猜数字","brief":"循环读取猜测并给出大小提示。","starter_code":"answer = 7\n","expected_output":"猜中提示","acceptance":["使用循环","处理猜对和猜错"]},
          "lessons": [
            {"id":"py3l1","title":"条件分支","order_index":1,"goals":["编写 if/elif/else","使用比较运算"],"knowledge_points":[{"title":"条件表达式","content":"if 根据布尔表达式选择要执行的代码块。","analogy":"路口的信号灯决定下一条路。"}],"example_code":"score = 88\nif score >= 60:\n    print('通过')\nelse:\n    print('继续练习')\n","summary":"缩进定义分支代码块。","estimated_minutes":30},
            {"id":"py3l2","title":"for 与 while","order_index":2,"goals":["遍历 range","设计终止条件"],"knowledge_points":[{"title":"循环","content":"for 适合遍历序列，while 适合条件驱动的重复。","analogy":"for 像逐个点名，while 像一直等到条件满足。"}],"example_code":"for i in range(3):\n    print(i)\n","summary":"循环让重复操作变得简洁。","estimated_minutes":30},
            {"id":"py3l3","title":"练习与小项目","order_index":3,"goals":["组合分支和循环","完成猜数字逻辑"],"knowledge_points":[{"title":"break 与 continue","content":"break 结束循环，continue 跳过本轮。","analogy":"break 是离开队伍，continue 是跳过一个人。"}],"example_code":"for i in range(1, 6):\n    if i == 3:\n        continue\n    print(i)\n","summary":"把流程控制组合成可交互程序。","estimated_minutes":50,
              "exercises":[
                {"id":"py3e1","type":"program","language":"python","question":"读取整数，偶数输出 even，奇数输出 odd。","answer":"judge:even","difficulty":"easy","knowledge_point":"if","required":true,"test_cases":[{"stdin":"8\n","expected_stdout":"even\n"}]},
                {"id":"py3e2","type":"program","language":"python","question":"输出 1 到 5 的累加和。","answer":"judge:15","difficulty":"medium","knowledge_point":"循环","required":true,"test_cases":[{"stdin":"","expected_stdout":"15\n"}]},
                {"id":"py3e3","type":"choice","language":"python","question":"range(3) 产生的最后一个值是？","options":["2","3","1","0"],"answer":"0","explanation":"终点不包含在 range 中。","difficulty":"easy","knowledge_point":"range","required":true},
                {"id":"py3e4","type":"true_false","language":"python","question":"Python 代码块使用花括号包围。","answer":"0","explanation":"Python 使用缩进表示代码块。","difficulty":"easy","knowledge_point":"缩进","required":true},
                {"id":"py3e5","type":"fill","language":"python","question":"提前结束循环使用 ____。","answer":"break","explanation":"break 立即跳出当前循环。","difficulty":"easy","knowledge_point":"break","required":true},
                {"id":"py3e6","type":"output_predict","language":"python","question":"预测输出","code":"for i in range(2): print(i)","answer":"0\\n1","explanation":"range(2) 遍历 0、1。","difficulty":"medium","knowledge_point":"for","required":true},
                {"id":"py3e7","type":"choice","language":"python","question":"while 循环必须具备什么？","options":["最终可达的终止条件","列表","函数","类"],"answer":"0","explanation":"没有终止条件可能无限循环。","difficulty":"easy","knowledge_point":"while","required":true},
                {"id":"py3e8","type":"program","language":"python","question":"读取 n，输出 n 的阶乘。","answer":"judge:120","difficulty":"hard","knowledge_point":"循环","required":true,"test_cases":[{"stdin":"5\n","expected_stdout":"120\n"}]}
              ]}
          ]
        },
        {
          "id": "py4", "title": "字符串、列表和元组", "order_index": 4, "status": "complete", "difficulty": "beginner", "estimated_minutes": 120,
          "summary": "掌握序列索引、切片、常用方法以及可变性。", "project": {"title":"成绩分析器","brief":"读取成绩列表，输出总分和最高分。","starter_code":"scores = [72, 88, 91]\n","expected_output":"总分和最高分","acceptance":["使用列表遍历","输出统计结果"]},
          "lessons": [
            {"id":"py4l1","title":"字符串与切片","order_index":1,"goals":["使用索引和切片","调用字符串方法"],"knowledge_points":[{"title":"序列索引","content":"索引从 0 开始，切片右端点不包含。","analogy":"书架编号从零号格开始。"}],"example_code":"word = 'Python'\nprint(word[0], word[1:4])\n","summary":"字符串是不可变字符序列。","estimated_minutes":35},
            {"id":"py4l2","title":"列表与元组","order_index":2,"goals":["创建列表和元组","理解可变与不可变"],"knowledge_points":[{"title":"列表","content":"列表可以增删改元素；元组适合表达固定结构。","analogy":"列表像可改的清单，元组像封存的档案。"}],"example_code":"items = [1, 2]\nitems.append(3)\nprint(items)\n","summary":"序列容器是组织数据的基础。","estimated_minutes":35},
            {"id":"py4l3","title":"练习与小项目","order_index":3,"goals":["处理序列数据","完成成绩分析器"],"knowledge_points":[{"title":"常用函数","content":"len、sum、min、max 可以快速统计数值序列。","analogy":"工具箱里现成的测量工具。"}],"example_code":"scores = [72, 88, 91]\nprint(sum(scores), max(scores))\n","summary":"用列表承载输入数据并计算统计量。","estimated_minutes":50,
              "exercises":[
                {"id":"py4e1","type":"program","language":"python","question":"输出列表 [1,2,3] 的总和。","answer":"judge:6","difficulty":"easy","knowledge_point":"列表","required":true,"test_cases":[{"stdin":"","expected_stdout":"6\n"}]},
                {"id":"py4e2","type":"program","language":"python","question":"读取一行单词，输出其长度。","answer":"judge:5","difficulty":"easy","knowledge_point":"字符串","required":true,"test_cases":[{"stdin":"hello\n","expected_stdout":"5\n"}]},
                {"id":"py4e3","type":"choice","language":"python","question":"列表索引从几开始？","options":["0","1","-1","任意"],"answer":"0","explanation":"第一个元素索引为 0。","difficulty":"easy","knowledge_point":"索引","required":true},
                {"id":"py4e4","type":"true_false","language":"python","question":"字符串可以直接修改其中一个字符。","answer":"0","explanation":"字符串不可变。","difficulty":"easy","knowledge_point":"字符串","required":true},
                {"id":"py4e5","type":"fill","language":"python","question":"向列表末尾添加元素使用 ____。","answer":"append","explanation":"list.append(value) 添加一个元素。","difficulty":"easy","knowledge_point":"列表方法","required":true},
                {"id":"py4e6","type":"output_predict","language":"python","question":"预测输出","code":"print([1,2,3][1:])","answer":"[2, 3]","explanation":"切片从索引 1 到末尾。","difficulty":"medium","knowledge_point":"切片","required":true},
                {"id":"py4e7","type":"choice","language":"python","question":"哪个容器通常不可变？","options":["tuple","list","dict","set"],"answer":"0","explanation":"tuple 是不可变序列。","difficulty":"easy","knowledge_point":"元组","required":true},
                {"id":"py4e8","type":"program","language":"python","question":"读取空格分隔的三个数并输出最大值。","answer":"judge:9","difficulty":"medium","knowledge_point":"列表统计","required":true,"test_cases":[{"stdin":"3 9 4\n","expected_stdout":"9\n"}]}
              ]}
          ]
        },
        {
          "id": "py5", "title": "字典与集合", "order_index": 5, "status": "complete", "difficulty": "beginner", "estimated_minutes": 110,
          "summary": "使用键值映射和去重集合解决数据查找问题。", "project": {"title":"词频统计","brief":"统计一句话中每个单词出现的次数。","starter_code":"text = input()\n","expected_output":"词频","acceptance":["使用字典计数","忽略大小写"]},
          "lessons": [
            {"id":"py5l1","title":"字典键值映射","order_index":1,"goals":["创建和访问字典","安全读取键"],"knowledge_points":[{"title":"映射","content":"字典通过键快速找到值，键必须可哈希。","analogy":"通讯录用姓名找到电话号码。"}],"example_code":"user = {'name': 'Ada', 'score': 95}\nprint(user['name'])\n","summary":"字典适合表达带名称的数据。","estimated_minutes":30},
            {"id":"py5l2","title":"集合与去重","order_index":2,"goals":["使用 set","理解集合运算"],"knowledge_points":[{"title":"集合","content":"集合无序且不重复，适合去重与成员判断。","analogy":"把重复的票据合并成一摞。"}],"example_code":"tags = set(['c', 'python', 'c'])\nprint(len(tags))\n","summary":"集合快速解决去重与关系判断。","estimated_minutes":30},
            {"id":"py5l3","title":"练习与小项目","order_index":3,"goals":["遍历键值对","完成词频统计"],"knowledge_points":[{"title":"get 与计数","content":"d.get(key, 0) 可以优雅地处理第一次出现。","analogy":"没有记录时先从零开始。"}],"example_code":"counts = {}\nfor word in 'a b a'.split():\n    counts[word] = counts.get(word, 0) + 1\nprint(counts)\n","summary":"映射和循环组合成实用统计工具。","estimated_minutes":50,
              "exercises":[
                {"id":"py5e1","type":"program","language":"python","question":"输出字典 {'a':1} 中 a 的值。","answer":"judge:1","difficulty":"easy","knowledge_point":"字典","required":true,"test_cases":[{"stdin":"","expected_stdout":"1\n"}]},
                {"id":"py5e2","type":"program","language":"python","question":"读取一行单词，输出不同单词数量。","answer":"judge:2","difficulty":"medium","knowledge_point":"集合","required":true,"test_cases":[{"stdin":"a b a\n","expected_stdout":"2\n"}]},
                {"id":"py5e3","type":"choice","language":"python","question":"字典通过什么访问值？","options":["键","下标位置","随机数","文件名"],"answer":"0","explanation":"字典使用键访问值。","difficulty":"easy","knowledge_point":"字典","required":true},
                {"id":"py5e4","type":"true_false","language":"python","question":"集合中的元素允许重复。","answer":"0","explanation":"集合会自动去重。","difficulty":"easy","knowledge_point":"集合","required":true},
                {"id":"py5e5","type":"fill","language":"python","question":"安全读取字典键可以调用 ____ 方法。","answer":"get","explanation":"get 可以提供默认值。","difficulty":"easy","knowledge_point":"字典方法","required":true},
                {"id":"py5e6","type":"output_predict","language":"python","question":"预测输出","code":"print(len(set([1,1,2])))","answer":"2","explanation":"集合去掉重复的 1。","difficulty":"medium","knowledge_point":"set","required":true},
                {"id":"py5e7","type":"choice","language":"python","question":"哪个对象可以作为字典键？","options":["字符串","列表","字典","集合"],"answer":"0","explanation":"字符串可哈希，列表不可哈希。","difficulty":"medium","knowledge_point":"哈希","required":true},
                {"id":"py5e8","type":"program","language":"python","question":"读取单词并输出出现次数。","answer":"judge:2","difficulty":"hard","knowledge_point":"词频","required":true,"test_cases":[{"stdin":"a b a\n","expected_stdout":"a 2\nb 1\n"}]}
              ]}
          ]
        },
        {
          "id": "py6", "title": "函数、作用域和参数", "order_index": 6, "status": "complete", "difficulty": "intermediate", "estimated_minutes": 120,
          "summary": "把重复逻辑封装为函数，理解参数、返回值和作用域。", "project": {"title":"命令行计算器","brief":"定义加减乘除函数并处理用户输入。","starter_code":"def add(a, b):\n    pass\n","expected_output":"计算结果","acceptance":["使用函数封装运算","处理返回值"]},
          "lessons": [
            {"id":"py6l1","title":"定义和调用函数","order_index":1,"goals":["编写 def 函数","使用 return"],"knowledge_points":[{"title":"函数","content":"函数把输入转换为输出，return 会结束函数并返回对象。","analogy":"自动售货机接收选择并吐出商品。"}],"example_code":"def square(x):\n    return x * x\nprint(square(4))\n","summary":"函数让程序结构清晰且易复用。","estimated_minutes":35},
            {"id":"py6l2","title":"参数与作用域","order_index":2,"goals":["使用默认参数","区分局部和全局名字"],"knowledge_points":[{"title":"作用域","content":"函数内创建的局部变量通常只在函数中可见。","analogy":"办公室内部工牌不能直接带到楼外。"}],"example_code":"def greet(name='同学'):\n    return f'你好，{name}'\nprint(greet())\n","summary":"参数设计决定函数的可用性。","estimated_minutes":35},
            {"id":"py6l3","title":"练习与小项目","order_index":3,"goals":["拆分计算逻辑","完成计算器"],"knowledge_points":[{"title":"可测试函数","content":"小函数更容易独立验证和复用。","analogy":"把机器拆成可替换的模块。"}],"example_code":"def add(a, b):\n    return a + b\n","summary":"用函数组织多个操作步骤。","estimated_minutes":50,
              "exercises":[
                {"id":"py6e1","type":"program","language":"python","question":"定义函数 twice(x) 并输出 twice(6)。","answer":"judge:12","difficulty":"easy","knowledge_point":"函数","required":true,"test_cases":[{"stdin":"","expected_stdout":"12\n"}]},
                {"id":"py6e2","type":"program","language":"python","question":"定义函数 max2(a,b)，读取两个数输出较大值。","answer":"judge:9","difficulty":"medium","knowledge_point":"参数","required":true,"test_cases":[{"stdin":"4 9\n","expected_stdout":"9\n"}]},
                {"id":"py6e3","type":"choice","language":"python","question":"定义函数使用哪个关键字？","options":["def","func","function","lambda"],"answer":"0","explanation":"def 定义普通函数。","difficulty":"easy","knowledge_point":"def","required":true},
                {"id":"py6e4","type":"true_false","language":"python","question":"没有 return 的函数也可以被调用。","answer":"1","explanation":"没有 return 时返回 None。","difficulty":"easy","knowledge_point":"return","required":true},
                {"id":"py6e5","type":"fill","language":"python","question":"函数返回结果使用 ____。","answer":"return","explanation":"return 将值交给调用方。","difficulty":"easy","knowledge_point":"return","required":true},
                {"id":"py6e6","type":"output_predict","language":"python","question":"预测输出","code":"def f(): return 3\nprint(f())","answer":"3","explanation":"调用返回 3。","difficulty":"easy","knowledge_point":"调用","required":true},
                {"id":"py6e7","type":"choice","language":"python","question":"函数内部创建的变量默认属于？","options":["局部作用域","全局作用域","系统作用域","模块外"],"answer":"0","explanation":"函数体内的名字通常是局部的。","difficulty":"medium","knowledge_point":"作用域","required":true},
                {"id":"py6e8","type":"program","language":"python","question":"定义递归阶乘函数并计算 5!。","answer":"judge:120","difficulty":"hard","knowledge_point":"递归","required":true,"test_cases":[{"stdin":"","expected_stdout":"120\n"}]}
              ]}
          ]
        },
        {
          "id": "py7", "title": "文件操作与异常处理", "order_index": 7, "status": "complete", "difficulty": "intermediate", "estimated_minutes": 120,
          "summary": "安全读写文本文件，使用异常处理构建可靠程序。", "project": {"title":"日志分析器","brief":"读取日志文件并统计错误行。","starter_code":"from pathlib import Path\n","expected_output":"错误数量","acceptance":["使用 with 管理文件","捕获文件异常"]},
          "lessons": [
            {"id":"py7l1","title":"打开和读取文件","order_index":1,"goals":["使用 open","区分文本模式"],"knowledge_points":[{"title":"with open","content":"with 会在代码块结束时自动关闭文件。","analogy":"借书时自动归还。"}],"example_code":"with open('note.txt', encoding='utf-8') as f:\n    text = f.read()\nprint(text)\n","summary":"文件句柄应及时关闭。","estimated_minutes":35},
            {"id":"py7l2","title":"异常与恢复","order_index":2,"goals":["捕获常见异常","设计友好提示"],"knowledge_points":[{"title":"try/except","content":"try 放可能失败的代码，except 处理特定异常。","analogy":"给电梯准备故障应急方案。"}],"example_code":"try:\n    value = int(input())\nexcept ValueError:\n    print('请输入数字')\n","summary":"异常是运行时可处理的分支。","estimated_minutes":35},
            {"id":"py7l3","title":"练习与小项目","order_index":3,"goals":["读写文本","完成日志分析器"],"knowledge_points":[{"title":"路径与编码","content":"显式指定 UTF-8 编码能避免跨平台乱码。","analogy":"不同国家用共同语言传递文件。"}],"example_code":"from pathlib import Path\nPath('out.txt').write_text('done', encoding='utf-8')\n","summary":"文件和异常让程序接近真实任务。","estimated_minutes":50,
              "exercises":[
                {"id":"py7e1","type":"program","language":"python","question":"使用 try/except 将输入整数加一并输出。","answer":"judge:4","difficulty":"easy","knowledge_point":"异常","required":true,"test_cases":[{"stdin":"3\n","expected_stdout":"4\n"}]},
                {"id":"py7e2","type":"program","language":"python","question":"使用 with 创建文件并写入 done，然后输出 done。","answer":"judge:done","difficulty":"medium","knowledge_point":"文件","required":true,"test_cases":[{"stdin":"","expected_stdout":"done\n"}]},
                {"id":"py7e3","type":"choice","language":"python","question":"自动关闭文件的推荐结构是？","options":["with open(...) as f","open(...) 永不关闭","file()","close only"],"answer":"0","explanation":"with 能保证关闭。","difficulty":"easy","knowledge_point":"with","required":true},
                {"id":"py7e4","type":"true_false","language":"python","question":"except 可以捕获指定异常类型。","answer":"1","explanation":"指定类型能避免误捕获。","difficulty":"easy","knowledge_point":"except","required":true},
                {"id":"py7e5","type":"fill","language":"python","question":"读取全部文本常用文件对象的 ____ 方法。","answer":"read","explanation":"read() 返回全部内容。","difficulty":"easy","knowledge_point":"文件读取","required":true},
                {"id":"py7e6","type":"output_predict","language":"python","question":"预测输出","code":"try: 1/0\nexcept ZeroDivisionError: print('safe')","answer":"safe","explanation":"捕获除零异常。","difficulty":"medium","knowledge_point":"异常","required":true},
                {"id":"py7e7","type":"choice","language":"python","question":"文本文件推荐使用什么编码？","options":["utf-8","binary-only","ascii-only","gbk-only"],"answer":"0","explanation":"UTF-8 兼容性好。","difficulty":"easy","knowledge_point":"编码","required":true},
                {"id":"py7e8","type":"program","language":"python","question":"读取一行文本，统计其中字母 a 的数量。","answer":"judge:2","difficulty":"hard","knowledge_point":"文件分析","required":true,"test_cases":[{"stdin":"banana\n","expected_stdout":"3\n"}]}
              ]}
          ]
        },
        {
          "id": "py8", "title": "模块和包", "order_index": 8, "status": "complete", "difficulty": "intermediate", "estimated_minutes": 100,
          "summary": "使用标准库模块拆分程序，理解导入和命名空间。", "project": {"title":"随机练习题生成器","brief":"调用 random 生成一道随机题。","starter_code":"import random\n","expected_output":"随机题目","acceptance":["正确导入模块","调用标准库函数"]},
          "lessons": [
            {"id":"py8l1","title":"导入模块","order_index":1,"goals":["使用 import","调用模块成员"],"knowledge_points":[{"title":"模块","content":"模块是可复用的 Python 文件，import 建立命名空间。","analogy":"从工具仓库取出一个工具箱。"}],"example_code":"import math\nprint(math.sqrt(16))\n","summary":"标准库提供大量可复用能力。","estimated_minutes":30},
            {"id":"py8l2","title":"自定义模块与包","order_index":2,"goals":["拆分文件","理解 __name__"],"knowledge_points":[{"title":"入口保护","content":"if __name__ == '__main__' 只在直接运行时执行。","analogy":"只有站在舞台中央时才开始表演。"}],"example_code":"def add(a, b):\n    return a + b\n\nif __name__ == '__main__':\n    print(add(1, 2))\n","summary":"模块化可以降低耦合。","estimated_minutes":30},
            {"id":"py8l3","title":"练习与小项目","order_index":3,"goals":["使用标准库","完成随机题目生成器"],"knowledge_points":[{"title":"模块边界","content":"通过清晰的函数和导出名称保持模块职责单一。","analogy":"每个工具箱只放一类工具。"}],"example_code":"import random\nprint(random.randint(1, 10))\n","summary":"模块让程序结构可以持续扩展。","estimated_minutes":40,
              "exercises":[
                {"id":"py8e1","type":"program","language":"python","question":"导入 math 并输出 pi 保留两位小数。","answer":"judge:3.14","difficulty":"easy","knowledge_point":"import","required":true,"test_cases":[{"stdin":"","expected_stdout":"3.14\n"}]},
                {"id":"py8e2","type":"program","language":"python","question":"导入 random 生成 1 到 1 的随机数并输出。","answer":"judge:1","difficulty":"easy","knowledge_point":"标准库","required":true,"test_cases":[{"stdin":"","expected_stdout":"1\n"}]},
                {"id":"py8e3","type":"choice","language":"python","question":"导入模块的关键字是？","options":["import","include","use","require"],"answer":"0","explanation":"Python 使用 import。","difficulty":"easy","knowledge_point":"import","required":true},
                {"id":"py8e4","type":"true_false","language":"python","question":"模块可以被多个文件复用。","answer":"1","explanation":"模块的核心价值是复用。","difficulty":"easy","knowledge_point":"模块","required":true},
                {"id":"py8e5","type":"fill","language":"python","question":"直接运行模块时，__name__ 等于 ____。","answer":"__main__","explanation":"入口保护依赖该值。","difficulty":"medium","knowledge_point":"__name__","required":true},
                {"id":"py8e6","type":"output_predict","language":"python","question":"预测输出","code":"import math\nprint(math.ceil(2.1))","answer":"3","explanation":"ceil 向上取整。","difficulty":"medium","knowledge_point":"math","required":true},
                {"id":"py8e7","type":"choice","language":"python","question":"包通常由什么组织？","options":["目录和模块","单个数字","数据库表","编译器参数"],"answer":"0","explanation":"包是组织多个模块的目录。","difficulty":"easy","knowledge_point":"包","required":true},
                {"id":"py8e8","type":"program","language":"python","question":"使用 math.gcd 输出 12 和 8 的最大公约数。","answer":"judge:4","difficulty":"medium","knowledge_point":"标准库","required":true,"test_cases":[{"stdin":"","expected_stdout":"4\n"}]}
              ]}
          ]
        },
        {
          "id": "py9", "title": "面向对象编程", "order_index": 9, "status": "complete", "difficulty": "intermediate", "estimated_minutes": 130,
          "summary": "定义类、实例、属性和方法，理解封装与继承。", "project": {"title":"图书管理模型","brief":"用 Book 类表示图书并打印借阅状态。","starter_code":"class Book:\n    pass\n","expected_output":"图书状态","acceptance":["定义类和方法","更新对象状态"]},
          "lessons": [
            {"id":"py9l1","title":"类和实例","order_index":1,"goals":["定义 class","创建实例属性"],"knowledge_points":[{"title":"对象","content":"类描述共同结构，实例保存具体状态。","analogy":"类是建筑图纸，实例是盖好的房子。"}],"example_code":"class Dog:\n    def __init__(self, name):\n        self.name = name\n\nd = Dog('旺财')\nprint(d.name)\n","summary":"对象把数据和行为放在一起。","estimated_minutes":40},
            {"id":"py9l2","title":"方法与继承","order_index":2,"goals":["编写实例方法","理解继承关系"],"knowledge_points":[{"title":"self","content":"实例方法的 self 指向当前对象。","analogy":"每个人都能用自己的工牌访问自己的柜子。"}],"example_code":"class Animal:\n    def speak(self):\n        return 'sound'\nclass Cat(Animal):\n    def speak(self):\n        return 'meow'\n","summary":"继承复用通用行为并允许重写。","estimated_minutes":40},
            {"id":"py9l3","title":"练习与小项目","order_index":3,"goals":["完成图书管理模型","练习封装状态"],"knowledge_points":[{"title":"职责设计","content":"让对象管理自己的状态，外部通过方法交互。","analogy":"前台提供服务，不让访客直接进入机房。"}],"example_code":"class Book:\n    def __init__(self, title):\n        self.title = title\n        self.borrowed = False\n","summary":"用对象模型表达现实实体。","estimated_minutes":50,
              "exercises":[
                {"id":"py9e1","type":"program","language":"python","question":"定义 Person 类，创建对象并输出 name。","answer":"judge:Ada","difficulty":"easy","knowledge_point":"class","required":true,"test_cases":[{"stdin":"","expected_stdout":"Ada\n"}]},
                {"id":"py9e2","type":"program","language":"python","question":"定义 Rectangle.area 返回 12。","answer":"judge:12","difficulty":"medium","knowledge_point":"方法","required":true,"test_cases":[{"stdin":"","expected_stdout":"12\n"}]},
                {"id":"py9e3","type":"choice","language":"python","question":"定义类使用哪个关键字？","options":["class","struct","object","type"],"answer":"0","explanation":"class 定义类。","difficulty":"easy","knowledge_point":"class","required":true},
                {"id":"py9e4","type":"true_false","language":"python","question":"self 通常代表当前实例。","answer":"1","explanation":"实例方法通过 self 访问实例状态。","difficulty":"easy","knowledge_point":"self","required":true},
                {"id":"py9e5","type":"fill","language":"python","question":"初始化实例通常定义 ____ 方法。","answer":"__init__","explanation":"__init__ 在创建对象后初始化属性。","difficulty":"easy","knowledge_point":"初始化","required":true},
                {"id":"py9e6","type":"output_predict","language":"python","question":"预测输出","code":"class A:\n  x=2\na=A()\nprint(a.x)","answer":"2","explanation":"实例可访问类属性。","difficulty":"medium","knowledge_point":"属性","required":true},
                {"id":"py9e7","type":"choice","language":"python","question":"子类重写父类方法体现了什么？","options":["多态","输入","序列化","编译"],"answer":"0","explanation":"不同对象对同一方法可有不同实现。","difficulty":"medium","knowledge_point":"多态","required":true},
                {"id":"py9e8","type":"program","language":"python","question":"实现 Counter 类，每次 increment 后输出 2。","answer":"judge:2","difficulty":"hard","knowledge_point":"封装","required":true,"test_cases":[{"stdin":"","expected_stdout":"2\n"}]}
              ]}
          ]
        },
        {
          "id": "py10", "title": "综合项目：学习任务板", "order_index": 10, "status": "complete", "difficulty": "advanced", "estimated_minutes": 180,
          "summary": "综合使用函数、容器、文件、模块和类完成一个可持久化任务板。", "project": {"title":"学习任务板","brief":"支持添加、完成、保存和加载学习任务。","starter_code":"class TaskBoard:\n    def __init__(self):\n        self.tasks = []\n","expected_output":"任务列表和完成状态","acceptance":["支持增删改查","使用文件保存数据","处理异常输入"]},
          "lessons": [
            {"id":"py10l1","title":"需求拆解与数据模型","order_index":1,"goals":["设计任务对象","拆分功能模块"],"knowledge_points":[{"title":"分层设计","content":"把数据模型、存储和界面逻辑拆开，便于测试和修改。","analogy":"工厂把设计、生产和质检分成不同工位。"}],"example_code":"tasks = [{'title': '复习列表', 'done': False}]\n","summary":"先设计数据，再实现功能。","estimated_minutes":45},
            {"id":"py10l2","title":"持久化与测试","order_index":2,"goals":["保存 JSON 数据","编写边界测试"],"knowledge_points":[{"title":"可靠性","content":"保存失败、空列表和重复任务都应有明确处理。","analogy":"出门前检查钥匙、钱包和门锁。"}],"example_code":"import json\ntext = json.dumps({'tasks': []}, ensure_ascii=False)\nprint(text)\n","summary":"让程序在重启后继续工作。","estimated_minutes":55},
            {"id":"py10l3","title":"综合验收与展示","order_index":3,"goals":["完成任务板","解释设计取舍"],"knowledge_points":[{"title":"项目复盘","content":"通过测试用例验证核心流程，记录下一步改进方向。","analogy":"项目发布前进行飞行检查。"}],"example_code":"def remaining(tasks):\n    return [t for t in tasks if not t['done']]\n","summary":"把十章知识组合成完整作品。","estimated_minutes":80,
              "exercises":[
                {"id":"py10e1","type":"program","language":"python","question":"从任务列表中输出未完成任务数量 2。","answer":"judge:2","difficulty":"medium","knowledge_point":"综合容器","required":true,"test_cases":[{"stdin":"","expected_stdout":"2\n"}]},
                {"id":"py10e2","type":"program","language":"python","question":"将任务标题写入 JSON 字符串并输出标题。","answer":"judge:学习","difficulty":"medium","knowledge_point":"持久化","required":true,"test_cases":[{"stdin":"","expected_stdout":"学习\n"}]},
                {"id":"py10e3","type":"choice","language":"python","question":"JSON 文本通常用于什么？","options":["结构化数据交换","编译机器码","绘制像素","音频压缩"],"answer":"0","explanation":"JSON 适合保存和交换结构化数据。","difficulty":"easy","knowledge_point":"JSON","required":true},
                {"id":"py10e4","type":"true_false","language":"python","question":"项目应为异常输入准备处理分支。","answer":"1","explanation":"边界处理是可靠性的基础。","difficulty":"easy","knowledge_point":"异常处理","required":true},
                {"id":"py10e5","type":"fill","language":"python","question":"把对象编码为 JSON 文本常用 json.____。","answer":"dumps","explanation":"dumps 将对象序列化为字符串。","difficulty":"medium","knowledge_point":"json","required":true},
                {"id":"py10e6","type":"output_predict","language":"python","question":"预测输出","code":"print(len([x for x in [1,2,3] if x>1]))","answer":"2","explanation":"列表推导筛出 2、3。","difficulty":"medium","knowledge_point":"综合语法","required":true},
                {"id":"py10e7","type":"choice","language":"python","question":"自动化验证功能的样例称为什么？","options":["测试用例","变量","模块","注释"],"answer":"0","explanation":"测试用例描述输入和预期结果。","difficulty":"easy","knowledge_point":"测试","required":true},
                {"id":"py10e8","type":"program","language":"python","question":"实现 remaining(tasks)，输出未完成任务标题。","answer":"judge:学习","difficulty":"hard","knowledge_point":"综合项目","required":true,"test_cases":[{"stdin":"","expected_stdout":"学习\n"}]}
              ]}
          ]
        }
      ]
    }
  ]
}
