Appearance
第2章 · 面向大模型的现代 Python 与张量工程底座
前置要求:熟悉常规电脑操作,具备基础 JavaScript/TypeScript 编程经验(ES6 语法:变量声明、箭头函数、数组方法与基础类)。无需任何 Python 或微积分基础,数学直觉仅需高中水平。
课程全景:五大知识板块
在正式开始前,请看整门课程的五大知识板块架构。每个板块学习负荷均衡,循序渐进:
本章核心定位与即用即学(Just-In-Time)声明
TIP
初级前端工程师减负声明(单一职责原则)
很多教材在入门阶段试图一次性灌输“编程语言所有高级特性”加“高等代数、概率论与微积分”,导致学习者产生严重的认知过载。
本章彻底解耦“语言工程”与“抽象数学”,严格专注于现代 Python 与张量工程底座:
- 本章唯一核心任务:作为前端工程师进入大模型手搓实战的平滑轻量工程坡道。你的目标学习时长为 25–30 分钟,只需搞懂 4 件事:
- Python 核心语法心智对照:掌握 f-string、列表推导式、字典与默认参数陷阱;
- 类的运算符重载(Dunder 方法):掌握
__init__、__add__、__mul__、__call__,为第 4 章手写Value算子打好语法基石; - 闭包机制与作用域:理解
_backward反向传播链条的物理载体与nonlocal; - NumPy 数组与张量维度:掌握张量世界的 Shape 类型契约、Broadcasting 广播规则与
keepdims黄金纪律。
所有数学符号、微积分导数推导、线性代数矩阵乘法与概率信息论均已独立分流至 第3章 · 大模型核心数学桥接与符号解码速查,在本章无需背负任何数学焦虑!
本章目标
学完本章核心内容后你能做到:
- 熟练编写后续大模型实战所需的 Python 核心语法:f-string、列表推导式、
def默认参数防坑、类的__init__/__add__/__mul__/__call__、闭包与with上下文。 - 独立创建与激活 Python 虚拟环境、使用
pip管理依赖、运行pytest并精准定位测试断言失败原因。 - 建立张量 Shape 强类型心智,熟练推导多维数组切片、正确应用 Broadcasting 广播规则,并在数据归一化中养成加
keepdims=True的防 Bug 习惯。 - 口头与代码解释为什么 Dunder 运算符方法必须返回新对象、为什么循环闭包必须固定当前值。
本章不教什么
以下主题后续章节用不到,本章一律不碰;遇到时记下名字、跳过、继续走主线:
- Python:复杂元类编程、协程与异步(
async/await)、复杂多重继承、包打包发布。 - 数学:微积分、矩阵求导、概率论(全部归口至第 3 章)。
阶段一:环境与工程工具
你在 JS 里已经精通“项目级依赖隔离(package.json + node_modules)+ 运行测试(npm test / vitest)”;Python 的对应物是 venv + pip + pytest:
bash
# 1. 创建虚拟环境(≈ 解释器级别的专用隔离沙箱)
python3 -m venv .venv
source .venv/bin/activate # Windows PowerShell 用 .venv\Scripts\Activate.ps1
# 2. 安装依赖(≈ pnpm install)
python -m pip install numpy pytest
# 3. 运行测试(≈ pnpm test;本仓库所有章节共用此规范)
PYTHONPATH=python python -m pytest python/tests/test_w0p_python_math.py -q若你已经按 第1章 用 python3.11 -m venv .venv 建过环境并 pip install -r requirements.txt,直接激活该 .venv 并执行测试即可。
pytest 的断言就是纯粹的 Python 关键字 assert:断言失败时 pytest 会自动展开左右两边的值与差异,无需额外记忆复杂的测试断言库。
避坑指南:
- 装到系统全局 Python 而非虚拟环境:
pip install之前务必确认终端提示符前有(.venv),或通过which python(Windows 用Get-Command python)确认路径指向当前项目的.venv。 - 模块找不到
ModuleNotFoundError:在项目根目录运行命令时,务必在前面加上PYTHONPATH=python,告诉解释器搜索根目录下的python/模块。
阶段二:针对前端开发者的 Python 核心语法
配套练习位于 python/labs/w0p_exercises.py,写完一节即可运行对应测试验证。
1. 变量、数值与 f-string 模板
js
// JavaScript
const name = "Ada";
const lr = 0.01;
console.log(`Hello, ${name}! lr=${lr}`);python
# Python
name = "Ada" # 无需 const/let,直接赋值(变量即对象指针标签)
lr = 0.01
print(f"Hello, {name}! lr={lr}") # f-string ≈ JS 模板字符串 `${...}`差异对比:
- Python 不使用分号结尾;
- 空值使用
None(对应 JS 的null与undefined); - 整数除法用
//(向下取整),单斜杠/永远是浮点除法(JS 只有浮点除法)。 - 高频陷阱:字符串前如果忘记写
f前缀(写成"{name}"),大括号会被当作普通文本原样输出且完全不报错!
2. 列表、字典与推导式
js
// JavaScript
const evens = [1, 2, 3, 4].filter(n => n % 2 === 0).map(n => n * n);
const counts = { a: 2, b: 1 };
const val = counts["c"] ?? 0;python
# Python
# 列表推导式 = [映射表达式 for 元素 in 迭代对象 if 过滤条件]
evens = [n * n for n in [1, 2, 3, 4] if n % 2 == 0]
counts = {"a": 2, "b": 1} # dict ≈ JS 对象字面量 / Map
val = counts.get("c", 0) # ≈ counts["c"] ?? 0,安全读取防 KeyError差异对比:
- 列表推导式将映射与过滤优雅合并,第 4 章的拓扑排序、参数遍历与注意力分数收集会高频使用;
- 引用赋值陷阱:在 Python 中执行
b = a,b和a指向同一个底层列表对象。修改b.append(99)会连带改写a。需要独立拷贝时务必显式写b = list(a)或b = a[:]。 - 严格缩进:Python 抛弃花括号
{},完全采用 4 个空格的缩进表示代码块,混用 Tab 与空格会直接引发IndentationError。
3. 函数定义与可变默认参数陷阱
python
def safe_divide(a, b, default=0.0):
"""带默认参数的除法"""
if b == 0:
return default
return a / bCAUTION
Python 最著名的大坑:可变默认参数
在 JS 里,function f(seen = []) 每次调用如果不传参,都会新建一个数组; 但在 Python 里,def f(seen=[]) 的默认列表 [] 是在模块加载定义时仅创建一次的单例!后续所有未传参的调用都会共享同一个列表!
铁律准则:可变对象(列表、字典)永远不要作为默认参数。标准写法为:
python
def f(seen=None):
if seen is None:
seen = [] # 每次调用独立初始化4. 类与 Dunder 魔术方法(第 4 章 Value 的直接前石)
在 JavaScript 中,语言不支持运算符重载:
js
// JavaScript:无法直接重载运算符,只能调用方法
class Scalar {
constructor(data) { this.data = data; }
add(other) { return new Scalar(this.data + other.data); }
}
const c = a.add(b); // 不能写 a + bPython 提供了以双下划线开头的 Dunder(Double Underscore)方法,让自定义类能够像原生数字一样使用 +、*、- 等运算符:
python
class Scalar:
def __init__(self, data):
# 对应 JS 的 constructor;self 对应 this,且必须显式声明为第一参数
self.data = float(data)
def __add__(self, other):
# 拦截 a + b 表达式
other_data = other.data if isinstance(other, Scalar) else other
return Scalar(self.data + other_data) # 严格返回全新对象!
def __mul__(self, other):
# 拦截 a * b 表达式
other_data = other.data if isinstance(other, Scalar) else other
return Scalar(self.data * other_data)
def __repr__(self):
# 类似 JS 的 toString(),控制控制台与 print 时的友好打印
return f"Scalar(data={self.data})"
def __call__(self, x):
# 让类的实例能够像函数一样直接调用:obj(x)
return self.data * x__call__方法:这是深度学习模型模块化构建的核心模式。在后续构建网络层与手搓 Transformer 时,所有的网络层(Linear、LayerNorm、Attention)均通过__call__触发前向传播forward计算。- 容器协议
__getitem__与__len__:让自定义类支持方括号索引obj[i]和求长度len(obj),这是第 6、9 章编写 Dataset 和 Token 批次切片的基础。
IMPORTANT
函数式不可变性纪律
在实现 __add__ 或 __mul__ 时,绝对不能写 self.data += other.data!修改原对象会导致复合计算图中的变量发生隐蔽漂移。这与 React/Vue 中“绝不直接修改 props/state”是完全相同的工程纪律。
5. 闭包与 nonlocal 机制(反向传播核心载体)
前端开发者在日常事件监听与防抖节流中天天写闭包;Python 的闭包机制完全一致:内层函数会捕获并记住定义时的外层作用域变量。
python
def make_multiplier(factor):
def multiply(x):
# multiply 闭包牢牢记住了定义时的 factor 参数
return factor * x
return multiply
triple = make_multiplier(3)
print(triple(5)) # 15(make_multiplier 已执行完毕,但 factor 依然存活)在第 4 章手写标量自动微分引擎时,每个算子(加法、乘法)在计算完成时,都会动态生成一个 _backward 闭包函数,将当前节点的输入与输出精准锁死在闭包上下文中;在后续执行反向传播时调用它完成局部梯度回传。
若闭包内层函数需要修改外层变量的值(而非只读),必须显式声明 nonlocal:
python
def make_counter():
count = 0
def increment():
nonlocal count # 没有这行,count += 1 会报 UnboundLocalError
count += 1
return count
return increment6. 异常防御与 with 上下文管理器
python
try:
result = a / b
except ZeroDivisionError:
result = 0.0 # 精确捕获特定异常,严禁裸写 except:
# with 语法糖 ≈ try/finally,无论中间是否抛出异常,退出作用域时自动安全释放句柄
with open("dataset.txt", "r", encoding="utf-8") as f:
text = f.read()
# 缩进结束,文件句柄已自动关闭阶段三:NumPy 数组与张量维度(Shape Contract)
标量只装载单个数字;而现代大模型内部的所有数据都是以高维矩阵与张量(Tensor)的形式批量流动的。
python
import numpy as np
# 1. 创建二维数组
x = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(x.shape) # (2, 3) -> 2 行 3 列。「先读 shape 再动手」是全书第一反射!
print(x[:, 0]) # 获取第 0 列数据 -> array([1., 4.])1. Shape Contract 就是张量世界的 TypeScript
在大模型代码中,绝大多数崩溃与致命错误不是语法问题,而是张量维度对不上。我们推崇将 Shape 视作不可动摇的类型契约:
| 核心张量 | 典型 Shape 表达 | 物理含义 |
|---|---|---|
| Token IDs | (Batch_Size, Seq_Len) | 批次中每句话对应的词表索引整数 |
| 嵌入向量 (Embedding) | (Batch_Size, Seq_Len, Hidden_Dim) | 每个词被映射成高维连续特征向量 |
| 线性层权重 (Weight) | (Hidden_Dim_In, Hidden_Dim_Out) | 空间特征投影转换矩阵 |
批量矩阵乘法 @ | (B, T, D_in) @ (D_in, D_out) -> (B, T, D_out) | 批处理所有序列的特征变换 |
2. 广播机制(Broadcasting)与大模型第一隐蔽 Bug
NumPy 的广播机制允许不同形状的张量在特定规则下进行算术运算。 广播的核心铁律:从张量形状的最后一维向前对齐比较;若对应维度相等,或者其中一方的维度为 1,则允许广播并自动沿该轴复制扩充,否则抛出错误。
text
张量 A 形状: (2, 3, 4)
张量 B 形状: (1, 4) <-- 从后往前:4与4匹配,1自动沿第1轴复制扩展为3,前面缺少维补齐为2
运算结果形状: (2, 3, 4)CAUTION
大模型代码第一高频隐蔽 Bug:一维数组 (N,) 与二维列向量 (N, 1)
在 JavaScript 中,[1, 2, 3] 只是一个无方向的普通一维数组; 但在张量计算中:
x.shape == (3,)是无方向的一维向量;x.shape == (3, 1)是三行一列的二维矩阵;x.shape == (1, 3)是一行三列的二维矩阵。
如果用一个 (3, 3) 的矩阵减去 (3,),广播机制会默认按行减去;但如果你原本的意图是按列减去均值,或者在中间步骤错误地将 (3, 1) 展平成了 (3,),代码完全不会报错,但会静默计算出全错的数值!
黄金避坑铁律:凡是对特定轴进行 mean() 或 sum() 归一化时,永远显式加上 keepdims=True,强制保持原维度形状不坍缩!
python
# 正确示范:保持列形状为 (2, 1),安全进行每行去均值广播
row_mean = x.mean(axis=1, keepdims=True) # shape 为 (2, 1)
normalized = x - row_mean # (2, 3) 减 (2, 1) 安全触发广播阶段四:故障注入与预期信号
在大模型工程中,学会观察特定报错信号是排查 Bug 的核心能力:
| 故障注入场景 | 预期报错或异常信号 | 正确修复与防护方案 |
|---|---|---|
可变默认参数 def f(cache=[]) | 重复调用函数时,上一次调用的数据被诡异残留 | 改为 def f(cache=None),并在函数体内动态判断初始化 |
列表传参直接赋值 b = a 后改 b | 原列表 a 在不知情的情况下被连带篡改 | 使用 list(a) 或切片 a[:] 产生独立深拷贝 |
| 循环内动态定义闭包(晚绑定问题) | 所有闭包调用的都是循环变量的最终退出值 | 使用默认参数 def func(val=current_val) 在声明时捕获瞬时值 |
Dunder 方法直接修改 self.data | 原对象被污染,复合运算链 (a+b)*a 结果漂移 | 严格实例化并返回全新的对象,杜绝就地变异 |
张量归一化漏写 keepdims=True | operands could not be broadcast together 或静默广播方向错误 | 针对多维轴求均值/求和时,永远强制声明 keepdims=True |
| 矩阵乘法中间维不相等 | ValueError: matmul dimension mismatch | 矩阵乘法前核对 Shape 契约:(M, K) @ (K, N) -> (M, N) |
阶段五:动手实验与自查验收
实验准备与判分测试
推荐规格:Google Pro A100 / L4 GPU (或 CPU)
零算力门槛,本地 CPU 或 Google Colab 免费运行时秒级跑通
💡 运行提示:若本仓库为 GitHub 私有仓库,首次在 Colab 打开时请在弹出的对话框中点击「Authorize with GitHub」授权读取;或直接点击「⬇️ 下载 .ipynb」并在 Colab 中选择「上传笔记本」运行。
bash
cd <仓库根目录>
source .venv/bin/activate
export PYTHONPATH="$PWD/python"
# 运行本章 Python 核心工程测试套件
python -m pytest python/tests/test_w0p_python_math.py -k "greeting or evens or words or divide or scalar or multiplier or counter or read or matmul or broadcasting" -qtext
# 完成前(练习全是 NotImplementedError;本仓库当前实测):
36 passed, 36 skipped in 0.06s
# 完成后(你的 exercises 全实现):
72 passed
# 判定条件:
# - 36 个测试函数 × {solutions, exercises} = 72 collected
# - solutions 参数组始终 36 passed(参考解回归)
# - exercises 参数组从 36 skipped 降到 0 skipped、36 passed核心能力自查清单
完成本章后,请对照下表确认心智模型是否建立完毕:
论文与延伸
本章无论文。延伸只看两个,都不必现在读完:
- Python 官方教程:本章最小集之外的语言特性以它为准查,不需从头通读。
- Karpathy 的 micrograd:第4章自动微分的参照物,本章只看
Value类的__add__/__mul__结构,确认自己能完全读懂运算符重载即可。
资源 / 成本 / 隐私
本地 Python/NumPy、CPU 即可秒级运行;gross cost 为 0。练习只用合成数值,不涉及外部数据、账号或隐私信息。
Evidence
仓库当前机器证据(只读快照)
本章机器证据:evidence/02-runtime-v1.json。证明 Python 基础与工程断言全绿。本文件已登记进 evidence/module-manifest-v1.json。
学习者提交模板(待填写,不是当前机器证据)
复制下面模板并填写自己的真实运行结果。所有 <...> 都是未填写状态;actual 和 artifacts 尤其不能被当作已运行或已通过。
yaml
schema: learn-llm.evidence.v1
module: 02-python-foundation
commit: <learner-commit-sha>
verified_at: <iso-date>
environment: <sanitized-python-device>
seed: 0
commands:
- PYTHONPATH=python python -m pytest python/tests/test_w0p_python_math.py -q
metrics:
- name: exercises_tests_passed
expected: 36
actual: <recorded-value>
- name: exercises_tests_skipped
expected: 0
actual: <recorded-value>
artifacts:
- <learner-repo-relative-artifact-path>
cost:
gross_usd: 0
credit_usd: 0
licenses:
- source: <source>
version: <version>
license: <license>
attribution: <attribution>
redistribution: <redistribution>
known_failures:
- <sanitized-failure-or-none>下一步
进入 第3章 · 大模型核心数学桥接与符号解码速查: 你将获得全书的【15 大数学符号解码字典】,并通过图形化与代码化的方式,直观掌握导数差商、偏导梯度、点积余弦、矩阵乘法与 Softmax 数值稳定证明,为亲手手搓大模型扫清一切数学恐惧!