20 天 · 每天 2 小时 · 共 40 小时
页面里所有的 Python 代码都能直接改、直接跑,用的是浏览器里的真 CPython(Pyodide),不用装任何东西。第一次点「运行」会下载运行时,之后就有缓存了。
先把预期钉死
| 能达到 | 达不到 |
|---|---|
| 理解计算机执行代码的完整链路;能独立写出 50–100 行解决真实问题的程序;能读懂报错并自己排查;理解神经网络和大语言模型的工作原理;能向别人讲清楚「ChatGPT 为什么会说话」。 | 成为能接活的程序员;手写神经网络训练代码;掌握软件工程实践。这些需要几百小时,不是四十小时。 |
40 小时的正确目标不是「学会」,而是建立正确的心智模型,并装好一台能自己走下去的引擎。
| 时段 | 做什么 |
|---|---|
| 前 40 分钟 | 学 — 读材料或看视频,只读当天那一小块,不许超前 |
| 中间 65 分钟 | 做 — 动手写代码,必须亲手敲,不许复制粘贴 |
| 最后 15 分钟 | 记 — 写学习日志三行 |
最后那 15 分钟看起来最像浪费,实际是整个计划里回报最高的部分。
勾掉每一天。进度存在这台设备的浏览器里,关掉页面也还在。
能从零独立写出 30 行程序。写不出来说明前面某天虚了,回去补,不要往前赶。
能写一个「从网上取数据 → 处理 → 存到本地」的脚本。这是绝大多数软件的骨架。
能用 5 分钟向一个完全不懂技术的人讲清楚「ChatGPT 为什么会说话」,并且对方听懂了。讲不清,就等于你自己没懂。
阶段一 · 编程的本质 · 第 1 天,共 6 天
今天只有一个目标:搞清楚你敲下的那串字符,是怎么让机器动起来的。
很多人以为编程是「对电脑下命令」。不是。你做的事情朴素得多:用文本编辑器写一个纯文本文件,存成 .py 结尾。这个文件本身什么也不会发生,和一张购物清单没区别。
让它动起来的是另一个程序,叫 Python 解释器(你装的那个 python.exe)。整条链是:
你写的 .py 文件 → Python 解释器读它 → 一行行翻译成 CPU 能执行的操作 → 你看到结果
所以「装 Python」装的不是一门语言,是那个翻译官。语言只是一套约定好的写法。
有的语言(C、Go)是编译型:先整份翻译成机器码,生成 .exe,之后直接运行。快,但改一行就要重新翻译一遍。
Python 是解释型:运行时一行行现翻。慢一些,但改完立刻能跑。学习阶段,这个即时反馈比速度值钱得多——这也是为什么入门几乎都选 Python。
这是今天最该带走的一句:解释器不懂你在说什么,它只是在做严格的模式匹配。
你写 print,它查表发现这是个已知的名字,对应「把括号里的东西显示出来」这个动作。你写 prnt,它查不到,就报错——不是因为它看出你打错了,而是因为表里没有。
你后面遇到的所有报错,根子都在这里:你写的东西不在它的规则里。
下面这段直接点「运行」。然后把名字改成你自己的,再跑一次。
看清楚这四行的区别。一个引号,决定了它是「数字」还是「一串字」。
最后一行输出的是 1 + 1 这三个字符本身,不是 2。加了引号,它就不再是算式,而是一段文本。这个区别是明天的主题,今天先看见它。
初学者最大的心理障碍是怕报错。今天就主动撞一次,把它变成熟人。
看那行 NameError: name 'prnt' is not defined——「没见过 prnt 这个名字」。它没有责备你,它在告诉你查表失败了。报错是线索,不是判决。Day 7 会专门讲怎么读它们。
.py 文件和 Python 解释器,分别是什么、谁让谁动起来?print(1 + 1) 和 print("1 + 1") 为什么输出不同?prnt 改回 print,但把引号删掉一个,看会报什么错。抄下来。今天只记一句:计算机不理解你,它只是严格地按规则查表和执行。
明天预告:今天你让程序说了话,但它还没有「记性」。Day 2 给它记忆——变量。
阶段一 · 编程的本质 · 第 2 天,共 6 天
昨天的程序说完就忘。今天给它记性,顺便解决新手第一个真正的坑:为什么 "18" 和 18 不是一回事。
很多教材说「变量是一个盒子,把值装进去」。这个比喻在 Python 里会误导你。更准的说法是:
变量是贴在数据上的一张标签。
写 age = 18,不是把 18 塞进名叫 age 的盒子,而是内存里有个 18,你给它贴了张写着 age 的标签。再写 age = 19,标签被撕下来贴到 19 上,原来那个 18 没人要了,被回收掉。
为什么这个区别重要?因为一个数据可以贴多张标签,后面处理列表时,这一点会让你少踩一个大坑。
| 类型 | 写法 | 装什么 |
|---|---|---|
int | 18 | 整数 |
float | 1.75 | 小数 |
str | "你好" | 文本,必须有引号 |
bool | True / False | 真假,首字母大写 |
类型决定了这个数据能做什么。数字能相减,文本不能;文本能拼接,数字的「加」是求和不是拼接。
从键盘、从文件、从网络读进来的东西,默认全是字符串。
用户输入年龄「18」,你拿到的是 "18" 这两个字符,不是数字 18。直接拿去做算术就会炸。必须先用 int() 转换。
这个坑你未来会掉进去无数次。今天先认识它。
** 是乘方,height ** 2 就是身高的平方。
把 weight 和 height 改成你自己的数据跑一遍。注意 f"..." 这种写法(叫 f-string),花括号里的东西会被算出来填进去,:.1f 表示保留一位小数。这个写法你会用一辈子。
报错是 TypeError: can only concatenate str (not "int") to str——「字符串只能和字符串拼接」。Python 拒绝猜你想干嘛:是想拼成 "181",还是想算成 19?它不猜,它报错。
把 age = "18" 的引号去掉,再跑一次。然后把引号加回来,改成 int(age) + 1,再跑一次。两种修法都试。
int("18") 和 str(18) 分别做了什么?试着让 int("abc") 报错,抄下错误信息。今天只记一句:类型决定了一个数据能做什么;外面进来的东西默认都是字符串。
阶段一 · 编程的本质 · 第 3 天,共 6 天
前两天程序都是从上到下走一条直线。今天给它分岔口和回头路——有了这两样,它才算是在「思考」。
解释器读你的文件,从第一行到最后一行,一条条执行,不回头也不跳跃。控制流就是打破这条直线的两种手段:
if — 分岔。条件成立走这边,否则走那边for / while — 绕圈。同一段代码反复执行就这两样。所有软件——操作系统、游戏、浏览器——控制流都只由这两种东西组成。
大多数语言用花括号划定范围,Python 用缩进。这意味着:
缩进错了,程序的意思就变了。它不是「不好看」,是「不对」。
统一用 4 个空格,不要用 Tab 和空格混着来——这是新手最常见的隐形 bug,肉眼看不出区别,程序死活不对。
| 用在什么时候 | 例子 | |
|---|---|---|
for | 知道要转几圈,或者要把一堆东西挨个过一遍 | 把名单里每个人打印一次 |
while | 不知道要转几圈,只知道什么条件下该停 | 一直猜,直到猜中为止 |
while 有个天生的危险:条件永远成立,就是死循环。写 while 时永远先问自己一句:什么情况下它会停?
改 score 的值多跑几次,把每个分支都走一遍。注意 elif 是「否则如果」,而且一旦有一个分支成立,后面的就不再检查了——所以顺序很重要,把 >= 90 放在 >= 60 后面,90 分就会被判成及格。试试看。
经典的猜数字游戏。这里让程序来猜,而且用聪明的办法:每次猜区间正中间,一刀砍掉一半可能。
数一下用了几次。100 以内,最多 7 次必中。因为每猜一次范围减半:100 → 50 → 25 → 13 → 7 → 4 → 2 → 1。
把 secret 改成 1 或 100 这种极端值再试。然后把范围改成 1~1000,猜猜要几次?(答案:10 次。翻十倍,只多三次——这就是二分查找,你已经用上了一个真正的算法。)
for 和 while 分别在什么情况下用?>= 90 分支挪到 >= 60 后面,输入 95 分会怎样?为什么?low = guess + 1 的 + 1 删掉,会发生什么?动手试一次(别怕,有死循环保护)。今天只记一句:所有程序的流程,都只由「分岔」和「绕圈」两样东西组成。
阶段一 · 编程的本质 · 第 4 天,共 6 天
到今天为止你的代码都是一整坨。今天学会把它切开——这是从「能跑」到「能维护」的分水岭。
别把它想复杂。def 做的唯一一件事,是给几行代码贴个名字,以后喊名字就能用。
好处有三个,按重要性排:
calculate_bmi(70, 1.75) 你立刻懂了;看到那三行公式,还得读一遍参数是入口(给它什么),return 是出口(它还你什么)。
最常见的新手错误:把 print 当成 return。
print 是显示给人看,程序拿不到这个值return 是交还给程序,可以继续参与计算一个只 print 不 return 的函数,它的返回值是 None——「什么也没有」。拿去做算术就会炸。今天会亲眼看到。
A 调用 B,B 调用 C。C 干完了,怎么知道该回到 B 而不是回到 A?
因为解释器维护着一摞「待办便签」:每调用一个函数就往上压一张,记着「回来之后从这里继续」。函数结束就掀掉最上面一张,按上面写的地方回去。这摞便签叫调用栈。
这不是冷知识——你以后看到的每一个报错,那一长串 File ... line ... 就是这摞便签的快照,从下往上就是完整的呼叫路径。Day 7 会真正用到它。
两个函数看起来几乎一样,结果天差地别。
第二个报错了:unsupported operand type(s) for +: 'NoneType' and 'int'——「None 不能和数字相加」。因为那个函数只是把结果显示了出来,并没有把它交还给程序。
这个错误你以后会遇到很多次,每次都是同一个原因:忘了写 return。
同样的逻辑,切成三块。注意读起来是不是清楚多了。
这就是重构:行为一模一样,但结构变了。现在你想换一种猜法,只改 next_guess 一个函数;想改提示语,只碰 compare。
print 和 return 的根本区别是什么?return 的函数,调用它得到什么?next_guess 改成「每次都猜 low」(线性查找),猜 73 要多少次?和二分法比一下。今天只记一句:函数是给一段代码起名字;return 交还给程序,print 只给人看。
阶段一 · 编程的本质 · 第 5 天,共 6 天
到现在一个变量只装一个东西。要存一百个人的电话,总不能开一百个变量。今天学会成堆地装。
| 长什么样 | 怎么取东西 | |
|---|---|---|
list列表 | ["张三", "李四"] | 按位置:names[0] 是第一个 |
dict字典 | {"张三": "138...", "李四": "139..."} | 按名字:phones["张三"] |
选哪个,只看一个问题:你是靠「第几个」找它,还是靠「叫什么」找它?
names[0] 是第一个,names[1] 是第二个。别问为什么,记住就行——几乎所有编程语言都这样。
推论:5 个元素的列表,最后一个是 names[4]。写 names[5] 会报 IndexError。这个「差一」错误(off-by-one)是编程史上最经典的 bug 类型,你会犯很多次。
好在 Python 给了个捷径:names[-1] 直接取最后一个,不用数。
phones["王五"] 如果没这个人,直接 KeyError 崩掉。更安全的写法是 phones.get("王五", "查无此人")——查不到就返回你给的默认值,不崩。
处理外部数据时永远用 .get(),因为你没法保证对方给的东西里一定有那个键。
把今天学的和前四天的东西缝起来:字典存数据,函数封装操作,循环遍历,条件判断。
试着自己加一个功能:写一个 search_by_prefix(prefix),找出所有姓氏匹配的人。提示:字符串有个 .startswith() 方法。
names[5] 会怎样?names[-1] 又是什么?d["x"] 和 d.get("x") 在键不存在时表现有什么不同?哪个更适合处理外部数据?今天只记一句:按位置找用 list,按名字找用 dict。
阶段一 · 编程的本质 · 第 6 天,共 6 天 · 整合日
今天不学新东西。整合日的价值不在增加,在于把散的东西连成一条能走的路——这才是「会编程」和「学过编程」的区别。
| Day | 拿到了什么 | 一句话自检 |
|---|---|---|
| 1 | 执行链条 | .py 文件和解释器分别是什么? |
| 2 | 变量与类型 | 为什么 "18" + 1 会报错? |
| 3 | 分岔与绕圈 | for 和 while 怎么选? |
| 4 | 函数 | print 和 return 差在哪? |
| 5 | list 和 dict | 按位置找还是按名字找? |
有答不上来的,今天先回去补那一天,别急着往下走。这不是浪费时间——里程碑 1 过不去,后面十四天会越走越吃力。
写一个 30 行左右的小工具,解决你自己的一个真实小麻烦。
「你自己的」这四个字是重点。抄一个练习题的答案,和为自己解决一个问题,大脑投入程度完全不同。而且真实问题一定会撞上教程里没讲的细节,那才是学习真正发生的地方。
先跑这个看看一个「完整的小工具」长什么样。它用上了这五天的全部东西。
读一遍代码,确认每一行你都知道在干嘛。有不确定的,回去翻对应那天。
不知道写什么?这几个都是真实需求,任选一个,或者想你自己的:
用下面这个空白区写。写不出来就回去翻前五天,翻书不算作弊,查资料是这行的日常。
能从零独立写出 30 行程序。「独立」的标准不是不查资料,而是:你知道自己要干什么,也知道该去查什么。做不到就回去补,不要往前赶。
今天的日志比平时重要,多写几行:
print("你好,世界"),六天前的你和现在的你,差别在哪?明天预告:Day 7 是全程最重要的一天,也是最反直觉的一天——专门学怎么读报错。今天写工具时你八成撞了不少红字,明天让它们从敌人变成线索。
阶段二 · 软件如何构造 · 第 1 天,共 5 天
全程最重要的一天。不是因为内容最难,而是因为它决定了你能不能自己走下去。看懂报错的人能一直学;看不懂的人,卡住一次就放弃了。
新手看到红字的第一反应是慌:「我又错了。」
但报错其实是 Python 能给你的最友善的回应。它明确告诉你:哪一行、什么问题、什么类型。相比之下,真正可怕的是不报错但结果是错的——那种 bug 能藏几个月。
报错是线索,不是判决。一个会读报错的新手,战斗力超过一个只会写代码的新手。
随便一个报错,信息量都很密:
Traceback (most recent call last):
File "test.py", line 8, in <module>
result = level_one(data)
File "test.py", line 5, in level_one
return level_two(data)
KeyError: 'missing_key'
拆开看:
| 看哪里 | 告诉你什么 |
|---|---|
| 最后一行 | 什么错。KeyError = 字典里没这个键。这是最重要的一行 |
| 倒数第二、三行 | 哪里错。文件名、行号、出事的那行代码 |
| 中间那些 File | 怎么走到这的。就是 Day 4 说的调用栈 |
读报错的顺序是从下往上。先看最后一行知道「什么错」,再往上找第一个属于你自己代码的文件名——那通常就是元凶。
为什么从下往上?因为 Python 按调用顺序打印:最上面是最早的调用,最下面是真正炸掉的地方。而报错信息本身在最底下。
| 报错 | 意思 | 通常是因为 |
|---|---|---|
NameError | 没见过这个名字 | 拼错了,或者变量还没赋值就用 |
TypeError | 类型不对 | 字符串和数字混用;忘了写 return(None 参与运算) |
IndexError | 下标越界 | 差一错误:5 个元素却访问 [5] |
KeyError | 字典没这个键 | 拼错键名,或数据里本来就没有 → 用 .get() |
ZeroDivisionError | 除以零 | 除数是变量,恰好算出来是 0 |
IndentationError | 缩进不对 | 空格数不齐,或空格 Tab 混用 |
SyntaxError | 语法不通 | 少了冒号、括号没配对、引号没闭合 |
AttributeError | 这东西没这个方法 | 把类型搞混了,比如对数字用 .append() |
把这张表存进你的 errors.md。两周后你会发现,90% 的报错都在这八种里。
下面每组前面都有 #。一次去掉一组的注释,跑一次,读懂报错,再注释回去,换下一组。
每撞一个,就往你的 errors.md 里记一条:报错原文 + 一句话原因。
这段有三层函数调用,报错会显示完整的呼叫路径。
练习:先看最后一行知道是什么错,再从上往下数,说出「谁调用了谁」。最后指出真正出问题的是哪一个函数。
最危险的 bug 不报错。下面这段能正常跑完,但答案是错的。
别直接看代码找答案,用调试三板斧。在循环里加一行 print(s, total),跑一次,盯着 total 每一轮的变化——你会立刻看见它在干什么。
这个练习的目的不是修好这一行,是让你养成「不确定就打印出来看」的反射。
TypeError 最常见的两个原因是什么?errors.md 里记了几条?(少于 5 条说明动手一没认真做)今天只记一句:报错从下往上读——最后一行说什么错,往上找第一处自己的代码。
阶段二 · 软件如何构造 · 第 2 天,共 5 天
到今天为止,你的程序一关就失忆。Day 5 那个通讯录,跑完什么都不剩。今天给它长期记忆。
| 在哪 | 特点 | |
|---|---|---|
| 内存 | RAM | 快,但断电即失。你的变量全在这 |
| 硬盘 | SSD / HDD | 慢几个数量级,但关机也还在 |
「保存」这个动作,本质就是把内存里的东西写到硬盘上。所有软件的存档、配置、数据库,底下都是这一件事。
为什么不全用硬盘?因为慢得多——Day 10 会让你亲手测出这个差距。
| 模式 | 意思 | 注意 |
|---|---|---|
"r" | 读 | 文件不存在会报 FileNotFoundError |
"w" | 写 | 会清空原内容!文件不存在就新建 |
"a" | 追加 | 接在原内容后面写,不清空 |
"w" 是新手最容易犯的破坏性错误。想追加却写了 "w",原来的数据一瞬间全没,而且没有任何提示、没有回收站。
打开文件后必须关闭,否则内容可能还卡在缓冲区没真正落盘,或者文件被一直占用。手动 close() 很容易忘,而且中途报错就执行不到了。
with 保证无论如何都会关闭——正常跑完会关,中途炸了也会关。养成习惯:打开文件永远用 with,没有例外。
在 Windows 上读写文件,永远显式写 encoding="utf-8"。
不写的话,Windows 中文系统默认用 GBK 编码,而绝大多数文件是 UTF-8。结果:中文变成乱码,或者直接 UnicodeDecodeError。这是中文开发者遇到的第一个「玄学问题」,原因就这一个。
页面里的文件系统在浏览器内存里,刷新页面就清空——正好当沙盒,随便折腾。本机上跑的话,这些文件会真的出现在你的文件夹里。
看明白了吗?"w" 那次,之前写的三行凭空消失了。记住这个感觉。
加两个函数:存盘和读盘。这样程序重启后数据还在。
注意存盘用的格式:每行一条,用逗号分隔。这其实就是 CSV,Excel 能直接打开。真实项目里会用 JSON(明天讲)或数据库,但原理完全一样:把内存里的结构变成文本,再从文本还原回来。
"w" 和 "a" 的区别是什么?哪个更危险?with 而不是手动 close()?encoding="utf-8" 会发生什么?今天只记一句:保存 = 把内存里的东西写成文本存到硬盘;"w" 会清空原文件。
阶段二 · 软件如何构造 · 第 3 天,共 5 天
前八天你什么都自己写。今天学会一件让效率翻几十倍的事:用别人写好的代码。这是现代软件开发真正的样子。
import math 的意思是:去找一个叫 math 的文件(或文件夹),把里面定义的东西拿过来用。
没有魔法。math 就是别人写好的一个 .py,里面定义了 sqrt、pi 这些名字。你 import 之后,就能用 math.sqrt() 调用它们。
那个点号是命名空间:math.sqrt 意思是「math 里面那个 sqrt」。这样即使你自己也定义了一个 sqrt,两者不会打架。
| 来源 | 怎么拿到 | 例子 |
|---|---|---|
| 标准库 | 装 Python 时自带,直接 import | math random json datetime os |
| 第三方库 | pip install 下载 | requests pandas flask |
| 你自己的 | 同目录下的 .py 文件 | import mytools |
Python 的标准库大得出名,号称「自带电池」。动手写之前先查一下标准库有没有现成的,这个习惯能省下大量时间。
pip install requests 做的事:去 PyPI(一个几十万个包的公共仓库)下载 requests,连同它依赖的其他包一起,装到你的 Python 里。
它解决的核心问题是依赖管理:A 依赖 B,B 依赖 C 的 2.0 版本,而 D 又要 C 的 1.0——这种缠绕人工处理会疯掉。
这一段在你本机做,页面里的 Python 装不了第三方包。
pip install requests # 装
pip list # 看装了哪些
pip show requests # 看某个包的详情
pip uninstall requests # 卸
顺带一句虚拟环境:不同项目需要同一个包的不同版本,全装在一起会冲突。解决办法是给每个项目建一个独立的环境(python -m venv .venv)。今天知道有这回事就行,真正需要它是在你同时做两个项目的时候。
json 值得单独说。它是不同程序、不同语言之间交换数据的通用格式——明天调 API 拿回来的就是它。
注意 ensure_ascii=False。不写的话中文会变成 中文 这种转义,虽然不算错但没法看。中文用户记住这个参数。
再对比一下 Day 8 那个逗号分隔的存法:JSON 能存嵌套结构(字典里套列表,列表里套字典),而逗号分隔只能存平铺的表格。这就是为什么真实项目几乎都用 JSON。
比 Day 8 那版短了一大截,而且现在每个人可以有多个字段(电话、邮箱、备注),想加就加,不用改存盘逻辑。
pip install requestspip list,看看你的环境里已经有哪些包.py 文件 import requests 然后 print(requests.__version__),确认装成功了装好放着,明天要用它。
import math 的时候,Python 实际做了什么?今天只记一句:动手写之前,先查一下有没有现成的。
阶段二 · 软件如何构造 · 第 4 天,共 5 天
今天补一课硬件。不写新语法,但会解释你这九天遇到的所有「为什么」——为什么程序会卡、会崩、会吃内存。
| 部件 | 干什么 | 打个比方 |
|---|---|---|
| CPU | 执行运算,一次只做一件事(但快到每秒几十亿次) | 一个手速极快的工人 |
| 内存 | 放当前正在用的数据。快,断电即失 | 工人面前的工作台 |
| 硬盘 | 长期存储。慢,但持久 | 房间角落的仓库 |
| 操作系统 | 调度谁用 CPU、分配内存、管理文件 | 车间主任 |
你的程序运行时,叫一个进程。操作系统给每个进程分一块自己的内存,互相看不见——所以一个程序崩了不会带崩整台机器。
把 CPU 读一次内存的时间当作 1 秒来打比方:
| 操作 | 按这个比例换算 |
|---|---|
| CPU 内部运算 | 眨一下眼 |
| 读内存 | 1 秒 |
| 读 SSD | 几天 |
| 读机械硬盘 | 几个月 |
| 发一个网络请求 | 几年 |
这张表解释了性能优化的第一原则:能不碰硬盘就不碰,能不发网络请求就不发。
Day 8 那个「保存」为什么慢?因为它跨越了从工作台到仓库的距离。
read() 全读进来with)下面两段代码结果完全一样,速度差很多。先猜哪个快,再运行。
为什么差这么多?因为Python 的字符串不可变——s = s + "x" 不是在原来那串后面加一个字,而是新建一个更长的字符串,把原来的整个复制过去。循环两万次,就复制了两万次,总共搬运的字符数是 1+2+3+…+20000。
列表可以原地追加,最后 join 一次搞定,只搬一趟。
这是你第一次因为理解内存而写出更快的代码。
在十万个元素里找东西,list 和 dict 的差距会让你吃惊。
list 只能从头挨个比,最坏要比十万次。dict 用哈希直接算出位置,一步到位,和它有多少元素几乎无关。
回头看 Day 5 那句「按位置找用 list,按名字找用 dict」——那不只是写法习惯,是性能上的数量级差距。
合上电脑,拿纸笔画出:你双击一个程序图标之后,到它出现在屏幕上,中间发生了什么。
至少要出现这些角色:硬盘、内存、CPU、操作系统、进程。画完对着讲一遍。
讲不顺就回去重读前两节——这张图是你理解「计算机」这三个字的骨架。
s = s + "x" 循环两万次会那么慢?今天只记一句:CPU 快、内存中、硬盘慢、网络最慢——差的是数量级,不是一点点。
阶段二 · 软件如何构造 · 第 5 天,共 5 天
前十天你的程序活在自己的世界里。今天让它接上互联网——这是阶段二的收尾,也是 Day 17 调 AI 的前置课。
你想在程序里显示天气。自己造气象卫星?当然不。你去找一个气象机构提供的 API——一个专门给程序用的接口。
网页是给人看的,API 是给程序看的。同一份数据,两种包装。
网页返回花花绿绿的 HTML,API 返回干干净净的 JSON(昨天刚学)。因为程序不需要好看,只需要好解析。
状态码是响应里最先该看的东西:
| 码 | 意思 | 该怎么办 |
|---|---|---|
200 | 成功 | 正常解析 |
401 / 403 | 没权限 | 检查 API key |
404 | 地址不存在 | 检查网址拼写 |
429 | 请求太频繁 | 等一会再试,别硬冲 |
500+ | 对方服务器出问题 | 不是你的错,过会儿重试 |
永远先检查状态码再解析数据。直接把响应当成功来解析,是新手最常见的网络代码 bug。
requests.get(url, timeout=10).gitignore。代码传上 GitHub 之后再删就晚了——历史记录里还在,爬虫几分钟就能扫到页面里的 Python 发不了真实网络请求,但拿到数据之后的处理才是重点,那部分完全一样。这是一份真实格式的天气 API 返回:
真实 API 返回的数据往往嵌套好几层,而且你要的字段随时可能不存在。这是实战中最花时间的部分。
注意那一串 .get() 的写法。处理外部数据时永远假设它可能残缺——服务器改版、字段改名、网络截断,都会让你的 [] 直接崩掉。
这一段必须在你自己电脑上跑。新建 weather.py,把下面的代码抄进去(用昨天装的 requests):
import requests
import json
# 这是一个免费、不需要 key 的公开 API(北京的天气)
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": 39.9,
"longitude": 116.4,
"current": "temperature_2m,wind_speed_10m",
}
response = requests.get(url, params=params, timeout=10)
print("状态码:", response.status_code) # 先看这个!
if response.status_code == 200:
data = response.json() # 自动解析成字典
current = data.get("current", {})
print("温度:", current.get("temperature_2m"), "°C")
print("风速:", current.get("wind_speed_10m"), "km/h")
# 存下来 —— 这就是 Day 8 + Day 9 + 今天的合体
with open("weather.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("已保存到 weather.json")
else:
print("请求失败,检查网络或网址")
跑通之后,打开生成的 weather.json 看看。把经纬度改成你所在的城市再跑一次。
连不上的话,多半是代理问题。在代码最前面加两行:import os 然后 os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7078"。
能写一个「从网上取数据 → 处理 → 存到本地」的脚本。这是绝大多数软件的骨架——你刚写的那三十行,和一个真实的数据采集程序,结构上没有区别。
今天只记一句:先看状态码,再解析数据;外部数据永远假设它会残缺。
阶段二完成。明天进入 AI 原理——你会发现前十一天学的每一样东西,在那边都用得上。
阶段三 · AI 原理 · 第 1 天,共 6 天
今天搞懂「机器怎么会学习」。Day 13 拆开神经网络的零件,Day 14 看它怎么被训练出来,Day 15–16 一路走到 ChatGPT。今天是地基,别急着追大模型。
传统编程你已经做了十一天了,它长这样:
输入 + 规则 → 输出
你写 if 分数 >= 60——规则是你想出来的,计算机只负责执行。机器学习把这个式子转了个方向:
输入 + 输出 → 规则
你给它一堆「身高和对应的体重」,它自己算出那条公式。你给它十万张标好「猫/狗」的照片,它自己找出区分的办法。整个 AI 领域,起点就是这一次翻转。
因为有些规则,人根本写不出来。试着用 if 写出「什么是猫」:有毛?有胡须?四条腿?——狗也全中。耳朵是尖的?折耳猫不是。你会发现你认得猫,但你说不清你凭什么认得。
这类知识叫默会知识:你会,但你讲不出规则。人脸、语音、语义、审美,全属于这一类。于是换个思路:我说不清规则,但我能给你一万个例子,你自己去找。
重复几百万次,仅此而已。那个「错了多少」是一个具体的数字,今天你就会亲手算出它——它叫损失,是 Day 14 的主角。
| 词 | 是什么 |
|---|---|
| 训练 | 从数据里找规则的过程。慢、贵、只做一次 |
| 模型 | 找出来的那个规则,本质上就是一堆数字 |
| 推理 | 拿现成模型去用。快、便宜、每次调用都在做 |
先跑一遍,看看这条老公式偏得有多离谱。
假设关系是 体重 = a × 身高 + b,但 a 和 b 是多少谁也不知道。让程序把所有可能都试一遍,挑错得最少的那组。
程序没被告诉过任何公式,它只见过那五组数字。
a 和 b?total_error 里为什么要平方?把 ** 2 删掉再跑一次,看结果崩成什么样。data 里加一条 (200, 200) 这种明显不合理的数据,重跑。数据脏了,学出来的规则就是错的。加分题:把 range(0, 101) 改成 range(0, 1001) 并把 a_step / 100 改成 / 1000。误差变小了吗?变慢了吗?——你刚体会到 AI 领域最核心的那对矛盾:精度和算力的交换。
写一段一百字左右的「什么是机器学习」,不许出现「算法」「模型」「数据」「训练」「参数」。写给一个完全不懂电脑的人。
阶段三 · AI 原理 · 第 2 天,共 6 天
昨天你写的 a * height + b,把 a 改名叫权重、b 改名叫偏置,它就是一个神经元。你已经把神经网络最小的零件写出来了,只是没人告诉你它叫这个名字。
总和 = w₁·x₁ + w₂·x₂ + w₃·x₃ + … + b
假设不加激活,把一个神经元的输出直接喂给下一个:
第一层:h = w₁·x + b₁
第二层:y = w₂·h + b₂
代入化简:y = (w₂·w₁)·x + (w₂·b₁ + b₂)。括号里不过是两个新的常数。
两层不加激活,结果还是一条直线,和一层完全等价。叠一百层也一样。没有激活函数,深度学习的「深」就是白搭。
| 名字 | 做什么 | 特点 |
|---|---|---|
| ReLU | 负数变 0,正数不变 | 简单粗暴,现代主流 |
| Sigmoid | 把任何数压进 0 到 1 | 像「概率」,老牌选手 |
| 阶跃 | 大于 0 输出 1,否则 0 | 像开关,好手算,今天用它 |
输入 → 乘权重 → 全部加起来 → 加偏置 → 过激活函数 → 输出
这就是全部。 从今天到 GPT,零件没变过,变的只是数量。
输入 x₁=2, x₂=4,权重 w₁=0.5, w₂=-0.3,偏置 b=0.1,激活用 ReLU。在纸上算出输出,写下每一步。
算完了再跑下面这段对答案:
对不上就停下来查,别往下走。(答案是 0 ——注意 x₂ 的权重是负的,它在反对,而且 x₂ 比较大,一票把结果否掉了。)
同样的结构,只改了偏置,行为就完全不同。 这就是「学习」要调的东西。
现在做 XOR:两个不一样时输出 1,一样时输出 0。
花 10 分钟,试着给单个神经元找一组 w₁、w₂、b 满足这四行。 用下面这个改权重试:
十分钟后你会失败。这不是你的问题——它无解。1969 年 Minsky 和 Papert 在书里证明了这一点,直接掐灭了当时的神经网络研究,业界叫它第一次「AI 寒冬」,冷了将近二十年。
四行全对。你刚刚做了两件大事:
NAND 的偏置从 1.5 改成 0.5,哪几行错了?为什么?今天只记一句:没有激活函数,叠多少层都等于一层。
阶段三 · AI 原理 · 第 3 天,共 6 天
昨天每一组权重都是我直接给你的——凭什么是这几个数?我怎么知道的?今天补上这个洞。训练,就是自动找出这些数字的过程。
Day 12 那个双层循环就是一次完整的训练:一个衡量「错了多少」的损失函数,一堆待定的参数,一个搜索过程叫优化。今天要解决的只有一件事:那个搜索办法慢到不能用。
Day 12 有 2 个参数,每个试 100 种,一万次,眨眼就完。那 10 个参数呢?100¹⁰ = 10²⁰ 次,用最快的超算也要跑上千年。
Day 13 那个 XOR 网络有 9 个参数——已经算不动了。GPT 那种几千亿参数的,穷举次数比宇宙里的原子还多。每多一个参数,搜索空间就乘一次。这叫维度灾难。
浓雾里要走到谷底。穷举 = 把每一平方米都走一遍,荒谬。聪明的办法:用脚感觉哪边是下坡,朝那个方向迈一步,再重新感觉。
| 山 | 机器学习 |
|---|---|
| 你所在的位置 | 当前这组参数值 |
| 海拔高度 | 损失(错得多少) |
| 脚下的坡度 | 梯度 |
关键在于:算坡度只看你脚下这一点,不需要走遍整座山。 这就是它比穷举快出天际的原因。
网络有很多层,输出错了,到底是哪一层哪个权重的锅? 反向传播就是回答这个的:从输出端的误差出发,一层一层往回分摊责任。像追查一次事故——从结果倒推,每个环节按它实际的影响力承担一份;影响大的,调整也大。
训练 = 反向传播算坡度 + 梯度下降迈步子,循环几百万次。
先不碰神经网络。找 f(x) = (x-3)² 的最低点——你一眼知道答案是 3,正好用来验证。
盯着那两列数字往下看——x 一步步爬向 3。没人告诉过程序答案。
注意 x = x - lr * g 这一行。这就是整个深度学习的核心公式。 GPT 训练时跑的本质上也是它,只不过 x 换成了几千亿个数。
把上面那段的 lr 依次改成 0.01 / 0.1 / 1.0 / 1.1,每次重跑。
1.1 那次看清楚——数字会左右横跳,而且越跳越远。这就是「飞出去」。没有公式能算出最佳学习率,只能试。
先做一件真实工程里必做的事:把身高缩放到 -1 到 1 之间。原始数值 150~190 太大,梯度会跟着爆掉。
| Day 12 暴力穷举 | Day 14 梯度下降 | |
|---|---|---|
| 计算次数 | 12,221 | 500 |
| 精度 | 卡在 0.01 的格子上 | 想多准有多准 |
| 加到 10 个参数 | 宇宙热寂也算不完 | 一样 500 步 |
这就是为什么现代 AI 能存在。 不是硬件变快那么简单,是有了一个不随参数数量爆炸的搜索办法。
x = x - lr * g,为什么是减梯度不是加?(想想站在 3 左边时 g 是多少)阶段过半:你已经掌握了神经网络的全部零件。从这里到 ChatGPT,不再有新的基本原理,只有规模和结构上的变化。
阶段三 · AI 原理 · 第 4 天,共 6 天
前三天处理的输入全是数字。可语言不是数字——「小猫」这两个字,要怎么喂进一个只会做乘法和加法的神经元?答案分三层:先切开,再编号,最后让它们互相看见。
第三种叫 BPE,规则蠢得出奇:统计哪两个相邻片段一起出现得最多,就把它俩合并。重复几万次。
顺带解释一个你早该好奇的现象:模型数不清「strawberry 里有几个 r」——因为它看到的根本不是字母,而是被切好的两三个块。字母级别的信息,在切开那一刻就丢了。
每个 token 对应一串数字(几百到几千个)。关键在于它怎么来的:不是人写的,是训练出来的——就是 Day 14 那套梯度下降。调完之后会自动出现一个惊人的性质:
意思相近的词,数字也相近。国王 − 男人 + 女人 ≈ 女王
意思变成了空间里的位置,语义变成了几何。 注意:那几千个数字每一维代表什么,没有人知道。不是保密,是真的没人知道。
还差最后一块。两个问题:「我吃苹果」和「苹果发布会」是同一个 token,意思却完全不同;「小猫追鱼,因为它饿了」——「它」是谁?
让每个 token 去打量句子里所有其他 token,算出「我该重点看谁」,然后按这个权重,把别人的信息揉进自己的表示里。
「它」打量一圈,发现和「小猫」最像,于是给「小猫」很高的权重、给「追」几乎为零。词义随上下文而变,自动实现。
在它之前,主流是从左到右一个词一个词地读(RNN),两个致命伤:距离一远就忘;没法并行——必须读完第 3 个才能读第 4 个,GPU 有一万个核也只能干等。
注意力两刀砍掉:任意两个位置一步直连;所有位置可以同时算。第二点才是引爆点——能并行,才能上万张 GPU 一起训;能一起训,才可能有几千亿参数的模型。
注意力内部那些 Q、K、V 本质仍是权重矩阵,仍然靠梯度下降训练出来。Day 13 和 14 之后,你没有再遇到过新的基本原理。
程序自己发现了「情商」和「血压」是一个整体——没人告诉过它中文有词语这回事,它只是数了数谁和谁总一起出现。真实模型对着几万亿字跑同样的过程几万轮,就得到了词表。
猫和狗最近,和苹果最远。「意思」被换算成了距离。
句子:小猫 追 鱼,因为它饿了。「它」要去打量前面三个词:
看那三根柱子:「它」把七成注意力给了「小猫」,给「追」的几乎为零。模型没有语法规则,没有指代消解算法——它只是算了几个点积,做了一次加权平均,代词就找到了主语。
corpus 换成八个完全不重复的词,BPE 会合并出什么?为什么?* 5 改成 * 1 再改成 * 20,三根柱子怎么变?太大或太小分别有什么坏处?阶段三 · AI 原理 · 第 5 天,共 6 天
昨天结束时你手上有一台能理解上下文的机器,但它离「会聊天」还差得远。
大模型只会做一件事——猜下一个 token 是什么。对话、写代码、翻译、解数学题,全都是这一件事的副产品。
训练目标简单到近乎荒谬:遮住一段文字的后半截,让模型猜下一个 token,猜错了就用 Day 14 那套梯度下降调参数。
为什么这个笨办法能成?因为它不需要人工标注——文本本身就自带答案。互联网上有多少字,就有多少道免费的练习题。而想把这道题做准,模型被逼着学会记事实、学会指代、学会算术、学会写代码。
没有人专门教过它算术。 只是为了把下一个字猜得更准,它不得不顺带学会。这种现象叫涌现——能力不是设计出来的,是压出来的。
「知识截止日期」就是从这来的。 训练数据是某个时刻的世界快照,那之后发生的事,模型的权重里根本没有。
因为它只会续写。你问「中国的首都是哪里?」,它可能回你「中国的最大城市是哪里?中国的人口有多少?」——在互联网上,一个问题后面确实经常跟着更多问题。它猜得非常准,只是猜的不是你要的东西。
人工写一批「问题 + 好答案」的范例,拿这些继续训练。量不大(几万到几十万条)但质量高。练完之后模型学会了:看到问题,该给答案,而不是续写更多问题。
「什么是好答案」这件事写规则写不出来,但人一看就知道——又是默会知识。于是:生成好几个答案 → 请人排序 → 拿排序训练一个打分模型 → 让主模型去追求高分。这就是强化学习。
| 工序 | 干什么 | 成本 | 产出 |
|---|---|---|---|
| 预训练 | 猜下一个词 | 天价 | 博学但不会聊天的续写机 |
| 微调 | 学会答题格式 | 中等 | 会回答问题的助手 |
| 人类反馈 | 学会什么算好 | 中等 | 有分寸的助手 |
训练目标从头到尾都是「像真的」,从来不是「是真的」。
翻遍三道工序,没有任何一步的损失函数里写着「事实正确」。对一台猜下一个词的机器来说,「爱因斯坦生于 1879 年」和「生于 1872 年」流畅度完全一样。三个原因叠加:
结果就是那个最危险的特性:它编造的时候,和它正确的时候,语气一模一样。 现代模型校准好了很多,但这个根子拔不掉。这不是 bug,是这个训练范式的必然产物。
用最原始的办法实现「猜下一个字」:统计每个字后面都跟过什么字,然后按频率随机抽。
多跑几次,盯着那些句子看一会儿。 这就是幻觉最纯粹的形态:每一步的下一个字都合法、都符合统计规律,连起来却毫无事实可言。这台机器根本没有「真假」这个概念——而 GPT 和它是同一类东西,只是把「看前 1 个字」换成了「看前几十万个 token」,把统计表换成了几千亿个权重。
明显通顺了。记住这个对比——从「看 1 个字」到「看 2 个字」,通顺度就跳一大截。这正是过去几十年语言模型一路往上爬的方向:让模型看得更远、看得更准。昨天那个注意力机制,就是这条路上的终极答案。
不要靠瞎试,用今天学的原理推导出来,再去验证(打开任何一个聊天 AI 即可)。
| 类型 | 原理 | 怎么问 |
|---|---|---|
| 知识截止之后 | 训练快照之后的世界不在权重里 | 问一件你确知发生在最近几个月的事 |
| 字符级操作 | token 化那一刻字符信息就没了 | 数某个长词里某个字母出现几次,或把词反过来拼 |
| 极冷门的具体事实 | 训练数据里出现太少,没编码进权重,但它照样流畅生成 | 问某本冷门书第几页写了什么。重点看它编造时的语气有多笃定 |
这 20 分钟的真正目的不是抓 AI 的错,而是让你亲眼确认:它错的时候,你从语气上看不出来。 这个体感,比今天所有理论都重要。
你现在应该能完成里程碑 3 了——用 5 分钟给一个完全不懂技术的人讲清楚 ChatGPT 为什么会说话。今天找个人试一遍,讲不顺的地方就是你的漏洞。
阶段三 · AI 原理 · 第 6 天,共 6 天
前五天全是原理。今天把 Day 15、16 的每个概念对应到真实参数上——调完这一次,大模型对你就不再是黑箱。
API 是无状态的。它不记得你上一句说了什么。
你在聊天软件里感受到的「它记得我们聊过什么」,完全是客户端的功劳:每次发消息,程序都把之前的全部对话重新打包发过去一遍。模型每次都是第一次见你,只不过这次收到的材料里包含了历史。
三个直接后果:
请求的核心是一个列表,每项有 role 和 content:
| role | 是谁说的 |
|---|---|
system | 给模型的设定:身份、语气、规则。放在 messages 的第一条 |
user | 你说的 |
assistant | 模型之前说的——这就是「记忆」的载体 |
把模型的回复用 assistant 角色塞回历史,下一轮它就「记得」了。整个多轮对话的秘密,就这一句话。
| 参数 | 作用 |
|---|---|
model | 用哪个模型。能力和价格差别很大 |
messages | 完整对话历史,第一条通常是 system |
max_tokens | 输出的上限。给小了会被截断在半句话中间 |
temperature | 随机程度。0 更稳定保守,1 更发散。写代码用低的,写文案用高的 |
token 不是字。 Day 15 讲过,中文大致一个字一到两个 token,英文一个词往往就是一个。计费按 token 走,输入和输出分别计价,输出通常贵好几倍。
API key 永远不写进代码。放环境变量,并确认 .env 在 .gitignore 里。
泄露的后果很实在:别人拿你的 key 花你的钱。GitHub 上有专门扫 key 的爬虫,提交后几分钟就能被捞走——而且删掉那次提交也没用,历史记录里还在。
下面用一个假模型演示。它只能看到你传给它的 messages,没有任何别的记忆——真实 API 就是这样。
看清楚了吗?同一个模型,同一个问题,区别只在于你有没有把历史一起发过去。
把维护历史这件事封装起来。这段逻辑,你在任何一个 AI 应用里都会重写一遍。
注意最后那个统计:第五轮发出去的内容,是第一轮的好几倍。这就是长对话变贵变慢的原因,也是为什么真实产品需要「压缩历史」。
这一段必须在你自己电脑上做——页面里跑不了,也不该把 key 放进网页。
用国内能直连的模型,不用代理、支持支付宝/微信充值。关键是:下面这四家全都兼容同一套接口格式(业界叫「OpenAI 兼容」),所以代码只有两行不同:
| 厂商 | base_url | model 填什么 | 特点 |
|---|---|---|---|
| DeepSeek | https://api.deepseek.com | deepseek-chat | 便宜,文档清楚。推荐从这家开始 |
| 智谱 GLM | https://open.bigmodel.cn/api/paas/v4/ | glm-4-flash | 有免费额度,想零成本先试就用它 |
| Kimi | https://api.moonshot.cn/v1 | moonshot-v1-8k | 长文本处理是强项 |
| 腾讯混元 | https://api.hunyuan.cloud.tencent.com/v1 | hunyuan-turbo | 接在腾讯云体系里 |
模型名和价格会变,以各家官方文档为准。注册一般是手机号 + 实名,几分钟。
「兼容 OpenAI 格式」是个重要概念:接口成了事实标准之后,换模型的成本从「重写代码」降到了「改两行配置」。这也是为什么下面这段代码你学一次就能用在所有家上。
注意:装的是 openai 这个包,但连的是国产模型——因为它们说的是同一套「话」。
pip install openai
# PowerShell,只在当前窗口有效
$env:LLM_API_KEY = "你申请到的key"
chat.pyimport os
from openai import OpenAI
# ↓↓↓ 换厂商只需要改这两行 ↓↓↓
BASE_URL = "https://api.deepseek.com"
MODEL = "deepseek-chat"
client = OpenAI(
api_key=os.environ["LLM_API_KEY"], # 从环境变量读,绝不写在代码里
base_url=BASE_URL,
)
# 历史由你维护。第一条是 system,给模型定人设
messages = [
{"role": "system", "content": "你是一个耐心的编程老师,回答简短,多用比喻。"}
]
print("开始聊天,输入 quit 退出\n")
while True:
text = input("你:")
if text.strip() in ("quit", "exit", ""):
break
messages.append({"role": "user", "content": text})
response = client.chat.completions.create(
model=MODEL,
max_tokens=1024,
messages=messages, # 每次都发完整历史
)
reply = response.choices[0].message.content
print("AI:", reply, "\n")
# 关键的一步:把回复塞回历史,下一轮它才「记得」
messages.append({"role": "assistant", "content": reply})
usage = response.usage
print(f" [本轮 输入 {usage.prompt_tokens} tokens,"
f"输出 {usage.completion_tokens} tokens]\n")
连不上的话,先确认没开代理——国内的 API 走代理反而可能失败。如果你按 Day 11 设过 HTTPS_PROXY,这里要先取消:$env:HTTPS_PROXY = ""。
messages.append({"role": "assistant"...}) 那行注释掉,重来一次——它会失忆messages 里第一条 system 的内容,比如改成「你只能用五个字回答」,感受系统提示词的威力BASE_URL 和 MODEL 两行,别的一个字不动。跑通之后你会真正理解「接口标准」的价值从 Day 12 的「什么是机器学习」到今天亲手调通 API,你已经走完了从原理到实践的整条链。现在你看到的每一个 AI 产品,底下都是你今天写的这三十行。
role 应该填什么?不塞会怎样?今天只记一句:模型没有记忆,「对话」是你每次把完整历史重新发过去。
阶段四 · 合龙 · 第 1 天,共 3 天
接下来两天做一个真东西:一个读你这 20 天学习日志、然后出题考你的助手。今天先把「读」这一半做完。
它不是随便选的。它同时用到:
| 来自 | 用在哪 |
|---|---|
| Day 5 数据结构 | 把日志组织成列表套字典 |
| Day 7 调试 | 解析真实文本一定会出意外 |
| Day 8 文件 | 读 log.md |
| Day 9 模块 | json 存中间结果 |
| Day 17 API | 明天让 AI 出题 |
更重要的是:它的产出直接服务于你自己的持续学习。这就是那个正反馈闭环本身。
这是今天最该练的习惯——写代码之前,先在纸上把三件事写下来:
新手最容易犯的错是打开编辑器就开始敲。想清楚这三件事,能省掉一大半返工。
解析的前提是格式固定。用这个(你前面几天的日志如果不长这样,现在整理一下):
## Day 1
懂了:.py 是文本文件,解释器才是让它动起来的东西
卡在:分不清 print 和引号的关系
明天:变量
## Day 2
懂了:变量是标签不是盒子
卡在:TypeError 看不懂
明天:控制流
## Day N 开头,下面三行分别以「懂了:」「卡在:」「明天:」开头。简单、好写、好解析——设计数据格式时,这三条永远比「功能强大」重要。
这里用一段内置的示例日志。跑通之后,把 log_text 换成你自己的。
解析完只是拿到了结构化数据,真正有用的是从中发现规律。
看那个「卡住的地方」汇总——那就是你的知识地图上的坑。如果某个词反复出现,说明那不是一次偶然的卡壳,是真的没学透。
新建 study_helper.py,把动手一二的代码合起来,改成从真实文件读:
with open("log.md", "r", encoding="utf-8") as f:
log_text = f.read()
entries = parse_log(log_text)
# ... 后面接动手二的统计
报 FileNotFoundError 就说明你的 log.md 不在同一个文件夹——这是 Day 7 的练习,自己排查。
能把你自己真实的日志读进来并正确拆分,今天就算过。拆不对多半是格式不统一——这也是真实数据处理的日常:大部分时间花在清洗上,不是算法上。
阶段四 · 合龙 · 第 2 天,共 3 天
昨天读懂了日志,今天接上大脑。做完这一天,你就有了一个真正属于自己的工具。
代码只有几十行,效果好坏几乎全取决于提示词。这是 AI 应用和传统软件最大的区别:逻辑的一部分不在代码里,在你写给模型的那段话里。
三条经验,今天会一一验证:
动手写代码前,先把要发给模型的那段话想好:
你是一个严格但友善的助教。下面是学生某一天的学习笔记。
请出 3 道复习题,要求:
- 针对他「卡住」的地方重点提问
- 不要直接给答案
- 每题一行,用「1. 2. 3.」编号
- 只输出题目,不要多余的话
笔记:
{这里填当天的日志}
注意最后两条——「每题一行,编号」和「只输出题目」是给程序看的。没有这两条,模型可能返回一大段散文,你的代码就没法解析。
真调 API 之前,先用假的把整个流程走通。这样调试不花钱、不受网络影响——这是专业做法。
流程跑通了,把 fake_ai 换成真的。整个程序只有这一个函数要改,其余一个字不动——这就是把代码切成函数的回报。
用 Day 17 选好的那家(下面以 DeepSeek 为例,换厂商就改 BASE_URL 和 MODEL)。
import os
from openai import OpenAI
BASE_URL = "https://api.deepseek.com"
MODEL = "deepseek-chat"
client = OpenAI(api_key=os.environ["LLM_API_KEY"], base_url=BASE_URL)
def ask_ai(prompt):
"""把 fake_ai 换成它,函数签名完全一样"""
response = client.chat.completions.create(
model=MODEL,
max_tokens=1024,
messages=[
{"role": "system", "content": "你是一个严格但友善的助教,只输出题目,不要寒暄。"},
{"role": "user", "content": prompt},
],
)
return response.choices[0].message.content
def make_questions(entry):
return ask_ai(build_prompt(entry)) # build_prompt 沿用动手一的
# 用 Day 18 解析出来的 entries
for e in entries:
print(f"\n=== {e['day']} ===")
print(make_questions(e))
注意 ask_ai 的入参和返回值,和假的那个一模一样:给一段文字,还一段文字。所以调用它的代码完全不用改。
这个「先用假的跑通流程,再换成真的」的做法,在真实开发里叫 mock。它让你调试提示词逻辑时不花钱、不等网络、结果可复现。做 AI 应用的人天天在用。
第一版出来的题多半平庸。接下来的时间全花在改提示词上,每改一次跑一次,对比效果:
system这个「改一点 → 跑一次 → 看效果」的循环,就是 AI 应用开发的日常。它和 Day 14 的梯度下降是同一个形状:小步调整,朝效果更好的方向走。
用你自己真实的日志跑一次,拿到的题目确实问到了你心虚的地方。真的坐下来答一遍——这才是这个工具的意义。
阶段四 · 合龙 · 第 3 天,共 3 天 · 最后一天
最后一天不学新东西。今天做两件事:看清自己走了多远,以及确保这台机器在明天之后还能自己转下去。
打开你的 log.md,读第一天那三行。
二十天前,你不知道 .py 文件和解释器的区别。现在你能解释注意力机制为什么是那次突破。这个跨度值得你停下来认真感受一下——不是自我表扬,是因为下一次卡住的时候,这个记忆是你继续下去的燃料。
用 Day 18 的解析器,统计这 20 天。
把 log_text 换成你真实的日志跑一次。「卡住关键词」那一栏尤其值得看——反复出现的,就是你要在接下来一个月重点补的。
这 20 天真正的产出不是知识,是这四样东西。它们在明天之后还继续工作。
| 零件 | 为什么它能持续 |
|---|---|
| 代码是客观裁判 | 跑通了就是跑通了。不需要老师,不存在「我以为我懂了」 |
| 报错是线索 | Day 7 之后,卡住不再等于放弃,而是等于「该查什么」 |
| 日志有落差 | 每天三行,二十天后回看的落差就是燃料 |
| 难度可校准 | 太难就拆小,太简单就加码。永远待在「有点费劲但能做出来」的区间 |
最重要的一条:不要再找下一个「20 天课程」。
入门阶段需要有人给你排路线,因为你不知道该学什么。这个阶段已经过去了。再找教程,是在回避「自己决定学什么」这件事——而那才是真正的门槛。
正确的下一步只有一个形状:选一个你真的想要的东西,然后把它做出来。
| 如果你想… | 就去做 |
|---|---|
| 把编程学扎实 | 挑一个你每天在手动做的事,写程序自动化掉。读《流畅的 Python》 |
| 往 AI 应用走 | 把 Day 19 那个助手做成能用的产品。读《这就是 ChatGPT》,跟李沐的《动手学深度学习》 |
| 往底层走 | 《深入理解计算机系统》(CSAPP)。硬,但是分水岭 |
| 还不确定 | 把 Day 6 那个小工具扩展成有界面的。方向会在做的过程中浮现 |
今天的最后一件事,也是唯一的作业。新建 roadmap.md,写四段:
写完存好。三个月后打开它。
你现在能读懂代码、能自己排查报错、理解计算机怎么运行、理解大模型为什么会说话,并且有一套能自己转下去的学习机制。剩下的路没有捷径,但你已经知道怎么走了。
自由练习
空白的 Python 环境,随便写。和上面所有代码块一样,跑的是浏览器里的真 CPython——标准库都在,math、random、json、datetime 都能用。