← 代码课堂

第 01 课:怎么读代码 —— 模块地图

难度:★☆☆(先具备基础语法与运行能力,可先学《编程起步》)
教材:查资料/(1456 行的 server.py + 一个前端文件夹)
预计时间:第一遍 40 分钟,练习 20 分钟
学完你能:围绕一次具体操作,画出入口、规则、数据与显示的调用链,并用运行结果核对


主线导学:先追一张卡片,再画整个项目的地图

本课只要求交付一条有证据的调用链:输入标题 → 接口 → 数据库 → 列表显示。先能运行 Python 文件、理解函数和字典;不会时先完成 入门课程。

下载课程 ZIP,解压后在 assets/主线实验/ 打开终端,运行 py -3 server.py,再打开 http://127.0.0.1:8030。macOS/Linux 把解释器命令换成 python3。它只用标准库,不依赖 AI Key 或历史业务项目的完整环境。

输入“读懂 HTTP”并保存,刷新页面应仍有这张卡片。第一次运行空列表也是正常结果,不要求有预置数据。终端此时一直等待请求,Ctrl+C 才结束。

跟着同一个值读四处代码

位置要找的证据读完能回答
index.html 的 submit 监听JSON.stringify 中的 title输入框的文本怎样进入请求体
server.py 的 do_POST路径检查与 json.loads哪个函数接到了请求,如何读正文
card_store.py 的 create_cardvalidate_title、INSERT、with conn规则在哪里,哪一步提交数据库
index.html 的 refreshGET、创建 li、textContent为什么服务端成功后页面能显示新数据

先不要逐行读 CSS、请求头、数据库连接细节。每次只回答“这份 title 现在是什么类型,下一步交给谁”。例如请求体是 UTF-8 字节,解析后是字典,body.get("title") 才取得字符串。

从调用链提炼模块地图

入口是 server.py 末尾创建服务的代码;网络层是 Handler;存储层是 card_store;展示层是 index.html。入口在启动时运行,do_POST 在收到请求时由服务器调用。函数定义的位置先后,不代表每次点击都会从文件第一行重新执行。

回到下面的「查资料」地图时,按相同职责寻找对应函数。先按名字搜索,再核对历史教材版本;不要把固定行号当作跨版本保证。

练习、预期与排错

把保存标题换成“阅读计划”,只观察 Network 和列表,不改后端。预期 POST 请求体 title 和响应 title 一致;刷新 GET 后仍能看见它。将四个位置连成箭头,并为每条箭头写上数据形状。

服务起不来先看终端错误:端口占用可加 --port 8032;打不开网页先确认地址和端口;列表为空先查 POST 是否成功;POST 成功却刷新丢失,检查是否换了数据库路径或工作目录。

验收:能指出实际请求、对应函数、参数化 INSERT、提交点、渲染点,不只交一张目录树。这个证据链比“十分钟读完任何项目”更可靠。

下面进入历史业务项目对照;其分发清单决定哪些文件是阅读摘录,完整运行环境以对应 README 为准。


课前须知:为什么要先学"读"

很多初学者的顺序是错的:拼命写代码,却从不读代码。
但现实是:

读代码不是天赋,是有步骤的手艺。 这一课就把步骤教给你。


第一步:先把它跑起来

打开 查资料/,双击 启动服务.bat(或者命令行 py server.py),
然后浏览器打开 http://127.0.0.1:8000/。

先别管代码,点一点、用一用:

为什么第一步是跑? 因为你要先知道"这段代码活着的时候是什么样"。
读代码时,每一行都可以对应到"哦,这就是刚才那个按钮干的事"。


第二步:找到入口

程序总有一个"从哪开始执行"的地方。常见入口:

项目类型入口怎么找
Python 脚本/服务找 if __name__ == "__main__":
Node/前端package.json 里的 scripts
纯网页index.html

「查资料」的入口是 server.py 最后两行(第 1455-1456 行):

if __name__ == "__main__":
    main()

意思是:直接运行这个文件时,执行 main() 函数。
顺着 main()(第 1416 行)往下看,你会发现它做了四件事:

  1. 解析命令行参数(--host、--port)
  2. init_db() —— 建数据库表
  3. seed_demo_data() —— 没有数据时塞几条演示数据
  4. ThreadingHTTPServer(...).serve_forever() —— 启动服务器,一直等着别人访问

到这里你就明白:这是一个"先准备数据,然后一直对外提供服务"的程序。


第三步:画模块地图(本课核心技能)

别从头读到尾。 1456 行的文件,人眼从头读到尾只会睡着,还会迷路。

正确姿势:先列出所有的函数和类,然后按"职责"分组。方法:
在 VS Code / 记事本里搜索 def 和 class (Python),或者看编辑器的"大纲"面板。

这是我替你整理好的「查资料」真实地图(行号为起始处):

server.py
│
├── ① 配置区(第 1-60 行)
│     作用:读 config.json / 环境变量,决定用真实 AI 还是离线演示
│     关键:_load_config_file()、AUTHORITY_SOURCES(权威信源白名单)
│
├── ② 数据层(第 475-610 行)
│     作用:只管数据库,不管网页
│     关键:db() 连接、init_db() 建表、card_by_id() 查、validate_sources() 校验
│
├── ③ 智能与搜索层(第 611-760 行)
│     作用:AI 回答、全文搜索、语义搜索
│     关键:_ai_stream_real()、fulltext_search()、_tfidf_vectors()、_cosine()
│
├── ④ 导出与渲染层(第 755-905 行)
│     作用:把卡片导出成 Markdown/HTML、把 Markdown 渲染成网页
│     关键:export_md()、export_html()、md_to_html()
│
├── ⑤ 网络层(第 908-1350 行)
│     作用:接收浏览器请求,分发到上面各层,把结果发回去
│     关键:Handler 类(do_GET / do_POST / do_DELETE 是路由入口)
│
└── ⑥ 启动层(第 1354-1456 行)
      作用:初始化 + 启动服务
      关键:seed_demo_data()、lan_ips()、main()

看到没有?一个 1456 行的文件,其实是 6 个模块拼起来的。
每个模块只干一类事情,这就是"模块化"最朴素的样子。

看名字猜职责的小技巧

不打算给外面用(Python 的约定,不是强制)

只有叫这个名字,服务器收到请求才会调用它(第 02 课细讲)


第四步:追一条数据流(把地图串起来)

光有地图还不够,要会"顺藤摸瓜"。选一个功能,从用户点击一路追到数据落地。

案例:在网页上新建一张卡片时,代码里发生了什么?

用户点击"保存"
   ↓
前端 js 发请求:POST /api/cards(web/js/ 里)
   ↓
server.py 的 do_POST() 第 1023 行:path == "/api/cards"
   ↓
调用 _card_create() 第 1092 行
   ↓
   ├─ 校验:标题、来源合法性(validate_sources)
   ├─ 组装数据:生成 id(uuid)、时间(now_str)
   └─ 写数据库:INSERT INTO cards ...
   ↓
_json() 把结果打包成 JSON 发回浏览器
   ↓
前端收到后刷新列表

追完这一条线,你就同时看懂了:**请求怎么进来(网络层)、数据怎么存(数据层)、
结果怎么回去(网络层)、界面怎么更新(前端)**。

技巧:追数据流优先追"写入"和"查询",因为它们最能体现数据结构。


第五步:带着问题读(最重要)

不要为了"读完"而读。 项目是工具书,不是小说。


对照阅读:把刚才的方法迁移到其他项目

你去 GitHub 上随便打开一个认真维护的 Python 项目,会发现也是这个套路:

教材项目常见开源项目作用
server.py 顶部注释 + 说明README.md告诉别人这是什么、怎么跑
web/static/ 或 public/前端资源
config.example.json.env.example配置模板(不含密钥)
knowledge.db数据库文件或迁移脚本数据
if __name__ == "__main__"同左(或 entry_points)统一入口

你会发现「查资料」已经是一个"小型开源项目"的样子了。
差别在于:大项目会把 server.py 按上面的 6 个模块拆成 6 个文件(甚至 6 个文件夹)。
拆不拆只是规模问题,模块思想是一样的——这正好是第 06 课的内容。


动手练习(做完本课才算过关)

  1. 照着地图数一遍:打开 server.py,用搜索功能找到上面 6 个模块各自的起始行,

亲手确认一遍(5 分钟)

  1. 找入口:打开 worry_debate_game/run.py,找出它的入口和「查资料」有什么不同
看答案

一个用 uvicorn.run,一个用 ThreadingHTTPServer

  1. 追一条流:在「查资料」里,从 do_GET 开始,追出"获取单张卡片"的完整链路

(提示:/api/cards/ 开头 → _card_get → card_full),把链路写在纸上

练习做没做只有你自己知道 —— 但做与不做的差别,一动手就现形。


本课小结

术语表

术语人话解释
入口(entry point)程序开始执行的地方
模块一块职责单一的功能代码,可以是一个文件、一个文件夹或一段区域
数据流一次操作中数据经过的路径:从哪来 → 怎么变 → 到哪去
标准库Python 自带的工具箱(如 sqlite3、http.server),不用安装就能用
第三方库需要 pip install 才能用的库(如 FastAPI、PySide6)

下节预告

第 02 课:手写 HTTP 服务器。「查资料」没用 Flask/FastAPI,
而是用 Python 标准库自己撸了一个服务器——我们把 Handler 类拆开看,
并对照 Flask 和 FastAPI 的写法,看看"框架到底帮你干了什么"。