简介:配套《程序员数学》一书的完整源码程序包,面向需要借助编程理解线性代数与微积分的学习者,尤其适合初中级程序员与数据科学入门人群,用可运行的代码化解抽象概念。压缩包共一百零四个文件,总体积约十三点五五兆字节,其中七十四份源代码脚本对应书内算法实现,十七份交互式笔记用于分章节走读与实验演示,另有七张示意图、三个三维模型文件与少量配置说明,方便按章节对照学习。作者在原书基础上修正多处错误并修复缺陷,确保代码在常用科学计算环境中顺畅执行;内容覆盖向量与矩阵运算、行列式与逆矩阵、特征值特征向量、线性空间与坐标变换,以及极限、导数、定积分、微分方程等核心主题。已有超过一千六百五十人学习下载,适合希望通过运行代码验证数学结论、打通理论与工程应用的读者。
1. 用 Python 学数学最先遇到的坑:一跑源码就报错,到底是环境问题还是代码问题
用 Python 学线性代数和微积分,很多人的第一个坎不是搞不懂矩阵乘法,而是把书里的配套源码弄到本地跑起来的那一刻。明明代码是完整的,一执行却弹 ModuleNotFoundError: No module named 'numpy',或者是 ValueError: shape mismatch,再或者一个 Singular matrix 把整个上午耗掉。这类数学源码项目特别吃环境,NumPy、SciPy、SymPy、Matplotlib 一个都不能少,而它们各自的版本、与你电脑上 Python 解释器的匹配程度、甚至源码文件本身存不存在暗坑,都会让同一份代码在不同机器上呈现出完全不同的结果。我见过太多人在这上面反复卸载重装、来回改代码,最后发现根因根本不是代码逻辑,而是环境或者一个被旧版写法坑到的接口。这篇文章就把我在跑这套用 Python 讲透线性代数和微积分的源码时,踩过的坑按环境、线性代数、微积分、避坑清单这四类拆开,顺便把这套资源修掉的 bug 一并讲清楚。新手可以照着命令一步步配好环境,熟手也能从我的排查顺序里找到一些可复用的思路。
2. ModuleNotFoundError 与依赖黑洞:装好 NumPy/SymPy/Matplotlib 只是第一步
2.1 为什么源码在别人机器上能跑、到你机器上必炸
这类数学源码最大的特点是依赖极其集中。理论上只需要几个库,但实际的坑恰恰出在“装没装对地方”上:很多开发者的电脑里同时存在系统自带的 Python、自己手动装的 Python、还有 IDE 内置的解释器,甚至还有 Anaconda。你在终端里敲 pip install numpy 时,pip 指向的是系统 Python;而你在 IDE 里创建一个项目再跑源码时,项目默认用的解释器很可能是 IDE 内置的那个。两边不在同一个环境,装得再多,代码照样 import 不到。
另一个很常见的情况是源码里 import 的库并不全在 requirements 里。比如书里某一章做数值积分会用到 scipy.integrate,结果作者只在说明里提了要装 NumPy 和 Matplotlib,新读者解压源码后一跑,报的是 ModuleNotFoundError: No module named 'scipy'。这种报错字面上是“缺包”,实际上是你没有拿到一份完整的依赖清单。遇到这种情况别急着去网上搜“某某库装不上”,先把报错信息里的模块名记下来,核对当前环境里到底装了哪些包。
数学书籍的配套源码还有一个年代问题。很多代码写作时用的是当时版本的 API,三四年前写的东西放到现在跑,一部分函数已经被废弃或者改了行为。比如老代码里出现 np.float、np.int、np.bool 这类写法,在较新的 NumPy 版本下会直接属性报错。所以这类源码项目一跑就炸,往往不是单一原因,而是“环境错位 + 依赖缺失 + API 变更”三者混在一起。第一步要做的是把环境因素彻底排查干净,而不是直接去改源码逻辑。
2.2 一套可复现的依赖安装顺序与验证命令
我拿到任何数学源码项目都会先做一件事:创建虚拟环境。虚拟环境能让你项目里的依赖和系统环境完全隔离,避免“在 A 环境装了包、在 B 环境跑代码”这种问题。下面这组命令是我处理这套源码时的标准操作,建议按顺序执行:
cd math-source python3 -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate python -m pip install --upgrade pip python -m pip install numpy sympy scipy matplotlib python -c "import numpy, sympy, scipy, matplotlib; print('numpy', numpy.__version__); print('sympy', sympy.__version__); print('scipy', scipy.__version__); print('matplotlib', matplotlib.__version__)"这段命令的逻辑是:先用 python3 -m venv 在当前目录下创建一个名为 .venv 的虚拟环境,然后通过 source 命令激活它。重点在于后面安装包时用 python -m pip 而不是直接写 pip,因为前者能保证安装操作绑定到当前虚拟环境的解释器上,后者有可能又装回系统全局环境。最后的 python -c 是一个冒烟测试,一次性验证四个核心库能否正常导入并打印版本号,这一步能确认环境本身没有问题。
如果你下载的源码目录里带了 requirements.txt,可以直接把中间那行 pip install 换成 python -m pip install -r requirements.txt,这样连版本号都由项目锁定,能少踩很多版本坑。这里提醒一句:看完版本输出后别急着关终端,先用同样的解释器跑一下源码里的主脚本,确认 import 阶段全部通过再继续排查其他报错。很多报错在 import 之后就消失了,后边的坑就纯粹属于代码逻辑层面的问题了。
2.3 版本不匹配:Python 3.12 下旧版 NumPy 的翻车现场
依赖装上之后,最常见的下一层报错是 API 变更。如果你拿到的源码是按几年前的习惯写的,里面很可能出现 numpy 老写法。我印象最深的一个报错是 AttributeError: module 'numpy' has no attribute 'float'。原因是 NumPy 在 1.24 版本移除了 np.float、np.int、np.bool 这一系列别名,官方要求直接用 Python 内置的 float、int、bool 替代。
这种报错有一个迷惑性:它不在 import 时发生,而是等你运行到某个具体计算时才炸。源码本身看起来“没毛病”,只有执行到那一行才报错,很容易让人误以为是自己的调用方式有问题。遇到这种情况,我一般会先全局搜一遍源码里的旧写法:
grep -rn "np\.float\|np\.int\|np\.bool\|np\.object" . --include="*.py"这条命令的逻辑是在当前目录下递归查找所有 .py 文件里出现过 np.float、np.int、np.bool、np.object 的位置。参数 -r 表示递归子目录,-n 表示显示行号,--include="*.py" 把搜索范围限定在 Python 脚本里。搜出来的每一处都是需要修改的点,把它们批量替换成 float、int、bool 这类内置类型即可。
另外,SciPy 和 SymPy 也有类似的版本迁移。比如 scipy.integrate.quad 在老代码里的返回值是一个元组,新版本也是,但中途若有对 res[0] 的强转,在数值为负时会报警告。SymPy 的 init_printing 在新版本里的展示效果也变了。如果你在跑源码时遇到“同样的报错网上搜不到几个结果”,优先级最高的怀疑对象就是版本差异。这类源码修复的价值正在于此:把原书中过时的写法改成当前主流版本能跑通的形式,并保证数学结果不变。拿到这类修复版源码后,最好不要用最新版环境硬跑,建议按修复时验证过的版本组合去装依赖。
3. 线性代数源码报错:矩阵维度和广播规则的三种典型翻车
3.1 shape mismatch:矩阵乘法不是你想乘就能乘
线性代数源码里最热闹的报错就是 ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0。这个报错说明你的两个矩阵在维度上对不上,前面那个矩阵的列数不等于后面那个矩阵的行数。很多人一看到“mismatch”就以为是数据错了,其实绝大部分原因出在定义矩阵时的形状跟书里的公式没对上。
我遇到过这样一个场景:书里写的是一个 2×2 矩阵乘以一个二维列向量,但源码里把向量写成了 Python 列表,比如 [1, 2]。这个列表在 NumPy 里是一维数组,没有“行”和“列”的语义,直接和 2×2 矩阵相乘时,NumPy 会把它当作列向量来算,结果输出的是一个一维数组。如果你的后续代码再对结果做转置或按矩阵操作,就会在更远的地方冒出一个看似不相关的 shape 错误。处理这个问题时,建议先打印所有参与运算的数组的 shape:
import numpy as np A = np.array([[1, 2], [3, 4]]) # 2x2 矩阵 x = np.array([1, 2]) # 一维向量 print("A.shape:", A.shape) # (2, 2) print("x.shape:", x.shape) # (2,) # 正确做法:直接用 @ 做矩阵乘法 y = A @ x print("y:", y, y.shape) # 结果是一维数组 (2,) # 如果你明确想要列向量,应该用 reshape 而不是 x.T x_col = x.reshape(-1, 1) # (2, 1) print("x_col.shape:", x_col.shape)这段代码的核心逻辑是先确认矩阵和向量的维度,再做矩阵乘法。x.reshape(-1, 1) 里的 -1 表示自动计算这个维度的大小,后面的 1 表示把它变成一列,它和 x.T 在差在一个关键细节上:一维数组的转置还是它自己,不会自动变成列向量,所以 x.T 这种做法在数学书里成立,在 NumPy 里不成立。你只要记住一个原则:碰到 shape mismatch,先打印参与运算的变量形状,再核对矩阵乘法要求的内维度一致,百分之八十的报错在这一步就能定位。
3.2 LinAlgError:逆矩阵不存在还强行用 inv
第二个高频报错是 numpy.linalg.LinAlgError: Singular matrix。这个报错的本意是矩阵不可逆,也就是行列式等于零。出现这种情况,很可能是书里的练习题正好给了一个奇异矩阵,比如某一行是另一行的整数倍,照抄进代码后直接求逆,就炸了。从数学上讲,这不是 bug,而是矩阵本身的性质;但从代码角度讲,你需要在调用 inv 之前判断矩阵是否可逆。
有些源码修复点就在这里:原书为了省事,在求逆前不做任何判断,一旦读者换一个矩阵就会翻车。可行的写法是提前检查矩阵的秩,而不是只用行列式判断:
import numpy as np M = np.array([[1.0, 2.0], [2.0, 4.0]]) # 第二行是第一行的两倍,不可逆 det = np.linalg.det(M) rank = np.linalg.matrix_rank(M) print("det =", det) # 约等于 0.0 print("rank =", rank) # 1,小于矩阵阶数 2 # 直接 inv 会抛 LinAlgError,所以先做判断 if rank == M.shape[0]: print("可逆,逆矩阵为:\n", np.linalg.inv(M)) else: print("不可逆,尝试伪逆或最小二乘解") print("伪逆:\n", np.linalg.pinv(M))这里的判断逻辑是:矩阵的秩等于它的行数时才是满秩方阵,才存在逆矩阵。matrix_rank 比 det 可靠,因为数值行列式在矩阵接近奇异时会因为浮点误差变得很难判断;而秩判断对浮点误差不敏感,更适合在代码里做守卫。如果矩阵不可逆但你需要一个“等效逆”来完成公式演示,可以用 np.linalg.pinv 求出伪逆,它在数学上不叫逆矩阵,但能维持很多公式的形式。源码如果还涉及解线性方程组,优先用 np.linalg.solve 而不是先求逆再乘,后者不仅慢,数值稳定性也更差。
3.3 广播陷阱:行向量与列向量相加的无声错误
比报错更折磨人的是那种“不报错但结果不对”的情况。线性代数源码里最常见的无声错误就是 NumPy 广播。比如你想实现一个矩阵运算:把向量 a 的每个元素和向量 b 的每个元素两两相加,形成一个 2×2 矩阵;如果直接写 a + b,得到的结果其实是一维数组的逐元素相加,而不是二维结构。
这个问题在源码里很难发现,因为代码执行后不抛任何异常,结果的数量也对,只是 shape 比你预期少了一个维度。后续所有基于这个结果的画图、求导、解方程都会带着这个错误一路往下走,等到最终结果不对时,你根本不知道源头在哪。所以要养成打印 shape 的习惯,尤其是从向量变矩阵的场合。
import numpy as np a = np.array([1, 2]) b = np.array([3, 4]) # 直接相加:一维数组按元素加 print(a + b) # [4 6] # 显式升维为列向量,再与 b 广播:得到 2x2 矩阵 outer_sum = a.reshape(2, 1) + b print(outer_sum) # [[4 5] # [5 6]]逻辑解释一下:a.reshape(2, 1) 的第二个维度是 1,和 b 这个形状为 (2,) 的数组相加时,NumPy 的广播规则会把它们扩展成 (2, 2),从而得到所有两两组合的和。这里最关键的参数是 (2, 1),它给了数组一个“列”的语义。如果你在原书代码里看到类似想生成外积、网格坐标矩阵的地方,都要检查是否有人漏写了 reshape。这类坑在 Matplotlib 画图时也会出现:x 和 y 的 shape 不一致时,plot 和 contour 会直接报错或者出现空白图。处理方式同样是打印两者的 shape,别急着改数据。
4. 微积分源码报错:SymPy 符号求导与数值积分的黑匣子
4.1 符号变量没声明:NameError 与 SympifyError
微积分部分的源码里,最常遇到的第一个坑是 NameError: name 'x' is not defined,或者封装后的 SympifyError: 'x'。原因是 SymPy 做符号运算时,所有变量必须先通过 symbols 声明成符号对象,代码才能对它们做求导、积分、展开等操作。很多人习惯数值计算里的写法,拿过代码就把 x 当变量用,结果 Python 解释器直接把它当成一个普通变量去找,找不到就抛错。
import sympy as sp x = sp.symbols('x') # 声明符号变量,漏掉这行必炸 f = x**2 + 2*x + 1 print(sp.diff(f, x)) # 输出 2*x + 2,一阶导 print(sp.diff(f, x, 2)) # 输出 2,二阶导这段代码的关键在于第一行 sp.symbols('x')。它的参数是字符串 x,返回一个符号对象,之后所有表达式里的 x 都会和这个符号绑定。sp.diff 的第三个参数是可选的整数,表示求导阶数;sp.diff(f, x, 2) 就是二阶导。这类源码修复中经常出现的情况是:原书代码在书页里把 symbols 声明单独放了一行,读者抄代码时容易忽略它;又或者代码中同时使用了两个符号变量,但只声明了一个,导致报错反复出现。如果你拿到源码后报的是这类错误,第一反应应该是检查文件顶部有没有完整的符号声明,而不是去改表达式本身。
另一个相关入口是 sp.sympify("x**2"),它可以把字符串形式的表达式转成符号表达式。如果传入的字符串里有未声明的符号名,同样会报 SympifyError。所以当你使用 sympify 时,习惯上也要确认所有符号都已经通过 symbols 声明,或者在字符串里直接定义符号。
4.2 lambdify 之后函数定义域出错,quad 直接积出 inf 或 nan
数值积分部分的报错相对更隐蔽。用 scipy.integrate.quad 对函数做数值积分时,如果被积函数在积分区间内存在奇点,比如分母为零,quad 会返回 inf 或者直接给 nan,有时候还会伴随 RuntimeWarning: divide by zero encountered。这个报错的根源往往是数学意义上的不收敛,而不是积分算法本身的问题。最常见的场景是积分 1/x 从 -1 到 1,原函数在 x=0 处无定义,区间跨过奇点,quad 自然算不出有限值。
import numpy as np from scipy.integrate import quad def bad_f(x): return 1.0 / x # 下面这行会触发除零警告,且返回 inf # val, err = quad(bad_f, -1, 1) # 检查定义域:在奇点附近打印函数值 xs = np.linspace(-1, 1, 2001) ys = np.where(np.abs(xs) < 1e-10, np.nan, 1.0 / xs) print(xs[:3], ys[:3]) # 观察奇点附近是否出现 nan这段代码里我用了 np.where 把非常接近零的点标记为 nan,避免直接计算时打印出一堆 inf 干扰判断。实际修复做法是:先求出分母等式的根,判断这些根是否落在积分区间内;如果落进去了,就对积分区间做分段,把奇点排除在外;再不行,就要考虑这个积分本来就是发散的,mathematically 无解。quad 本身提供了一个 points 参数,可以把已知的奇点坐标传进去,让积分器在奇点附近加密采样,但它改变不了不收敛的事实。如果源码里需要精确的符号结果,可以先用 sp.integrate(f, (x, a, b)) 判断是否存在有限解析解,再决定要不要切到数值积分。记住:quad 返回 inf 时,先检查数学定义域,再去怀疑代码参数。
4.3 原书代码的坑:导数可视化时函数对象和数组混用
用 SymPy 求导后画切线图是微积分章节的常见练习,但这里有一个非常容易翻车的地方:直接拿 SymPy 的符号表达式去喂给 NumPy 数组,或者直接调用 Matplotlib 画图。具体报错是 TypeError: only length-1 arrays can be converted to Python scalars。这个报错的意思是:你用了一个期望接收单个数、但接收了数组的函数。Symbolic 表达式里包含的 sin、exp 等操作在默认情况下是用 Python 标准库的 math 实现,math.sin 只接受一个标量,给它一个 500 个元素的数组当然不行。
解法是先通过 lambdify 把符号表达式转成能接受 NumPy 数组的数值函数:
import sympy as sp import numpy as np import matplotlib.pyplot as plt x = sp.symbols('x') expr = sp.sin(x) * sp.exp(-x) # 关键:把符号表达式转换成数值函数,第三个参数 'numpy' 让它用 numpy 的实现 f = sp.lambdify(x, expr, 'numpy') xs = np.linspace(0, 10, 500) ys = f(xs) plt.plot(xs, ys) plt.xlabel('x') plt.ylabel('f(x)') plt.show()lambdify 的调用逻辑是:第一个参数是符号变量,第二个参数是符号表达式,第三个参数指定用什么模块实现数学函数。写成 'numpy' 之后,内部的 sin、exp 都会调用 numpy 里的版本,这样 f 接收数组时就能逐元素运算并返回同形状的数组。如果不写第三个参数或者写 'math',f 就只接受标量。源码里如果还有求导后计算斜率、画切线的部分,同理也要把导数表达式先 lambdify 成数值函数再传入点的坐标。很多“照着书敲代码但图画不出来”的问题,根因都在这一处:不是 Matplotlib 没装好,而是表达式类型不对。这套源码修复版里,这类问题属于改动最频繁但最不起眼的部分,每次看到这个 TypeError 我就知道又是哪里少写了 'numpy' 参数。
5. 避坑与常见问题:数学源码运行中反复折腾的 5 个坑
5.1 坑一:明明 pip install 成功,import 还是报 ModuleNotFoundError
现象:终端执行 pip install numpy 显示 Successfully installed,但一跑源码,import numpy 直接 ModuleNotFoundError。
原因:你安装包的解释器和运行脚本的解释器不是同一个。最常见的是命令行里 pip 属于系统 Python,而 IDE 项目用的是虚拟环境,两边互相看不到对方。这不是包没装上,而是装错了位置。
解决:在项目目录激活对应虚拟环境后,统一用 python -m pip install 命令,不用 pip 裸命令。然后执行 python -c "import numpy; print(numpy.file)",观察 numpy 路径是否在 .venv 目录下。如果不在,就说明当前激活的环境不对。
5.2 坑二:Jupyter 里能跑,命令行一跑就炸
现象:在 Jupyter Notebook 里运行同一份源码一切正常,退出系统用命令行 python main.py 却报 ModuleNotFoundError。
原因:Jupyter 启动时使用的内核 Kernel 对应的解释器,和你在终端敲 python 时调用的解释器是两个不同环境。Notebook 环境里装了包,不代表命令行环境也有。
解决:先运行 jupyter kernelspec list,看当前 Notebook 使用哪个内核。如果你用虚拟环境跑源码,需要手动把虚拟环境注册为内核,命令是 python -m ipykernel install --user --name venv_name。之后在 Notebook 切换内核时选 venv_name,确保两边环境一致。数学源码里很多可视化代码适合 Notebook 边跑边看,所以这个坑尤其值得提前避免。
5.3 坑三:Windows 下源码脚本中文注释导致语法错误
现象:双击运行源码或命令行执行时报 SyntaxError: Non-UTF-8 code starting with '\x...',位置显示在第 1 行或文件头部附近。
原因:源码文件是用 GBK 编码保存的,里面写了中文注释,而 Python 3 默认按 UTF-8 读取源码文件,遇到中文字节序列解析失败。这个坑多出现在从旧电脑拷贝、或某编辑器默认用 GBK 保存文件的场景。
解决:如果只是少数文件,在源码第一行加# -*- coding: gbk -*-声明文件编码,让解释器按 GBK 解码。如果文件很多,建议还是用代码编辑器把所有文件统一转成 UTF-8 编码,避免后续其他工具链出问题。这里注意,加 UTF-8 声明并不能让一个 GBK 文件被正确读取,声明必须和实际编码一致,这是最容易搞混的一点。
5.4 坑四:断言失败,实际是 1e-15 级别的浮点误差
现象:源码里写了 assert A @ A_inv == np.eye(2),运行时断言失败,但不是每次都失败,换个矩阵又好了。
原因:浮点数二进制表示带来的误差。A 的逆矩阵数值解乘回 A,结果通常不是精确的 1.0,而是 1.0000000000000002 或 0.9999999999999999 这类数。直接拿 == 比较必然失败,但这不代表矩阵的逆求错了。
解决:把所有浮点数比较统一换成 np.allclose(A @ A_inv, np.eye(2), atol=1e-12)。atol 参数是绝对容差,表示允许的最大误差,数学演示设为 1e-12 足够。这个坑很反直觉,因为它不是“代码 bug”,而是数值计算的固有属性。很多人为了过断言去改动算法,实际上是改错了方向。
5.5 坑五:det 明明等于 0,矩阵却可逆
现象:np.linalg.det(M) 返回 0.0 或一个极小值,但线性代数课堂上老师手算后明确说这个矩阵可逆。
原因:矩阵元素量级差异大时,数值行列式会下溢或丢失精度。比如对角线上同时存在 1e200 和 1e-200 这种量级,行列式在双精度浮点下就被算成 0。det 是数值计算的结果,不等于数学意义上的行列式。
解决:判断可逆优先用 np.linalg.matrix_rank(M) == M.shape[0],或者直接写 try 块尝试 np.linalg.inv(M),能算出就不必纠结 det。如果想要一个绝对精确的结论,用 SymPy 的符号矩阵 sp.Matrix(M).det(),它会保留精确有理数运算。数学源码演示不要依赖 det 的数值结果做分支,这是血泪经验。
6. 不用猜:一套 10 分钟内跑通数学源码的快速定位法
6.1 五步定位:从报错信息还原到最小可复现代码
拿到这套源码后,我建议你按这个顺序排查,别上来就改代码:
第一步,验证解释器与依赖。终端执行 python --version,再执行 python -c "import numpy, sympy, scipy, matplotlib" 确认四件套都在。哪一步报错,就回到章节 2.2 修哪一步。
第二步,定位报错文件行号。报错 traceback 的最后几行会指向项目内某个 .py 文件的具体行号。别急着看全部内容,只看最后一行指向的源码那一句。
第三步,把报错行做成最小脚本。复制出报错涉及的那几行代码,单独存成一个 10 行以内的临时 .py 文件,用和项目一样的解释器运行。绝大多数问题在最小脚本里会轻松复现,而一旦复现,你就可以通过注释逐行缩小范围。
第四步,打印所有中间变量的 shape、dtype、前几个值。数学代码 80% 的报错来自 shape 不匹配,打印一眼就能看出来。打印结果用 # 注释写在源码对应行旁边,方便和原书对照。
第五步,精确搜索报错信息。把报错最后一行去掉文件路径、去掉具体行号,只留错误类型和提示关键词,去搜索。搜索出来的内容往往更精准,因为相同的字符串不会夹杂你项目里的噪音。
6.2 快速对照表:报错关键内容该往哪个方向查
| 报错关键内容 | 优先排查方向 | 对应章节 |
|---|---|---|
| ModuleNotFoundError / No module named | 依赖环境、解释器错位 | 2 |
| ValueError: matmul / shape mismatch | 矩阵维度、向量升维 | 3 |
| LinAlgError: Singular matrix | 矩阵是否可逆、用 pinv | 3 |
| NameError: x 或 SympifyError | 符号变量未声明 | 4 |
| only length-1 arrays can be converted | SymPy 表达式直接进 NumPy | 4 |
| Non-ASCII / Non-UTF-8 code | 文件编码 | 5 |
| assert 失败但结果数量一致 | 浮点误差、用 allclose | 5 |
这套方法之所以快,是因为它坚持“最小化”三个字:最小环境、最小脚本、最小搜索串。很多人卡在数学源码问题是把整个项目一起调试,错误被上下文裹住看不清楚。你把它拆到最小,根因立刻浮出水面。
从那以后,我每次拿到一份数学配套源码,不管它看起来多简单,都会先强制走一遍“环境验证 + 最小还原”。这个习惯帮我省下的时间远比多花的那几分钟多,也让我把报错从“玄学”变成了可以一条条列出的确定性问题。修复版源码里那些改过的 bug,本质上也都是用这套思路一条条揪出来的。希望你跑通之后,能更专注在书里的数学本身,而不是环境上折腾。希望帮到你。
本文还有配套的精品资源,点击获取