Skip to content

第2章 · 面向大模型的现代 Python 与张量工程底座 ​

前置要求:熟悉常规电脑操作,具备基础 JavaScript/TypeScript 编程经验(ES6 语法:变量声明、箭头函数、数组方法与基础类)。无需任何 Python 或微积分基础,数学直觉仅需高中水平。


课程全景:五大知识板块 ​

在正式开始前,请看整门课程的五大知识板块架构。每个板块学习负荷均衡,循序渐进:


本章核心定位与即用即学(Just-In-Time)声明 ​

TIP

初级前端工程师减负声明(单一职责原则)

很多教材在入门阶段试图一次性灌输“编程语言所有高级特性”加“高等代数、概率论与微积分”,导致学习者产生严重的认知过载。

本章彻底解耦“语言工程”与“抽象数学”,严格专注于现代 Python 与张量工程底座:

  • 本章唯一核心任务:作为前端工程师进入大模型手搓实战的平滑轻量工程坡道。你的目标学习时长为 25–30 分钟,只需搞懂 4 件事:
    1. Python 核心语法心智对照:掌握 f-string、列表推导式、字典与默认参数陷阱;
    2. 类的运算符重载(Dunder 方法):掌握 __init__、__add__、__mul__、__call__,为第 4 章手写 Value 算子打好语法基石;
    3. 闭包机制与作用域:理解 _backward 反向传播链条的物理载体与 nonlocal;
    4. NumPy 数组与张量维度:掌握张量世界的 Shape 类型契约、Broadcasting 广播规则与 keepdims 黄金纪律。

所有数学符号、微积分导数推导、线性代数矩阵乘法与概率信息论均已独立分流至 第3章 · 大模型核心数学桥接与符号解码速查,在本章无需背负任何数学焦虑!

本章目标 ​

学完本章核心内容后你能做到:

  1. 熟练编写后续大模型实战所需的 Python 核心语法:f-string、列表推导式、def 默认参数防坑、类的 __init__/__add__/__mul__/__call__、闭包与 with 上下文。
  2. 独立创建与激活 Python 虚拟环境、使用 pip 管理依赖、运行 pytest 并精准定位测试断言失败原因。
  3. 建立张量 Shape 强类型心智,熟练推导多维数组切片、正确应用 Broadcasting 广播规则,并在数据归一化中养成加 keepdims=True 的防 Bug 习惯。
  4. 口头与代码解释为什么 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 / b

CAUTION

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 + b

Python 提供了以双下划线开头的 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 increment

6. 异常防御与 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=Trueoperands could not be broadcast together 或静默广播方向错误针对多维轴求均值/求和时,永远强制声明 keepdims=True
矩阵乘法中间维不相等ValueError: matmul dimension mismatch矩阵乘法前核对 Shape 契约:(M, K) @ (K, N) -> (M, N)

阶段五:动手实验与自查验收 ​

实验准备与判分测试 ​

Google Pro 云端实验

第 2 章 · 面向大模型的 Python 工程底座实验场

⚡推荐规格: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" -q
text
# 完成前(练习全是 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 数值稳定证明,为亲手手搓大模型扫清一切数学恐惧!

私有学习站 · 原理从零构建 · 勿提交个人隐私或密钥