
简介这份资源是《程序员数学用Python学透线性代数和微积分》一书的配套源码程序面向IT从业者、算法工程师及正在补数学基础的开发者。资源以Python代码为主直击书中向量、矩阵、特征值、线性变换、极限、导数、积分等核心知识点并针对原书错误做了修正和bug修复能帮助读者避开常见坑点边写代码边理解数学原理。压缩包内含104个文件其中74个py脚本和17个ipynb交互式笔记本覆盖各章节示例另有可视化的jpg图片及少量辅助文件整体约13.55MB按章节文件名组织便于对照查阅。目前已有1652人学习下载。通过运行这些源码读者可以直观观察矩阵运算、线性空间映射以及导数积分图像借助sympy和numpy等库完成符号计算与数值实验从而将抽象的数学概念转化为可调试、可扩展的Python实践提升用数学解决编程问题的能力。1. 数学 Python 源码的报错怪圈import 就崩、运算结果对不上的折腾时刻很多想用 Python 验证数学公式的开发者拿到的第一份线性代数与微积分源码包常常在第一行就停住import numpy直接报 ModuleNotFoundError好不容易装上依赖运行矩阵求逆又报Singular matrix再往前推一步SymPy 的积分结果与书上的例题怎么都对不上。这类 bug 单独看像玄学放在一起就是一条被忽略的依赖与代码版本链。我在帮人排查这类源码时发现八成问题不在数学而在软件环境、API 新旧写法、还有源码里被人工改坏的小细节。这篇文章就按环境 → 线性代数 → 微积分 → 疑难排查 → 快速定位的顺序把报错根因和可复制的解决步骤讲透适合拿源码验证公式、却总被 Python 环境卡住的开发者。2. 环境地基先过关import 失败、pip 安装失败与版本不匹配2.1 先确认你跑的是哪个 Python 解释器报ModuleNotFoundError的第一反应不是急着装包而是先看清楚当前解释器是谁。我见过最典型的现场用户在一个终端里执行pip install numpy然后用 IDE 里另一个解释器跑代码结果自然是找不到模块。因为pip默认装到它对应的 Python 环境而 IDE 可能用的是 pyvenv 虚拟环境两者互相看不见。先跑下面三条命令确认解释器路径、版本和已安装的包which python python -V python -m pip show numpy sympy matplotlibwhich python告诉你当前命令行解析到哪个解释器python -V看版本号Python 3.8 以下跑现代 NumPy 会有问题python -m pip show能同时列出三个核心数学库的版本。如果pip show输出WARNING: Package(s) not installed说明对应解释器里确实没装。接下来建议直接建一个虚拟环境把源码包隔离在里面。这样不会污染系统全局 Python也不会被别的项目改版本。常见的做法是python -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip python -m pip install -r requirements.txt.venv是虚拟环境目录名source激活它之后你的python和pip都指向这个环境。之后所有依赖都装到这里IDE 里也把解释器切到.venv/bin/python。这一步能消掉一半以上的 import 报错。2.2 pip 安装失败权限、源与依赖名写错装依赖时最常见三类失败。第一类Permission denied或者Could not install packages due to an EnvironmentError这是因为你在系统级目录写文件没有权限加--user装到当前用户目录即可第二类No matching distribution found for numpy通常是源里没有这个包或者包名拼错第三类ERROR: Could not find a version that satisfies the requirement说明源里给定版本不存在需要换源。一个稳妥的安装命令是python -m pip install --user numpy sympy matplotlib--user参数让 pip 把包安装到用户级目录不需要 sudonumpy sympy matplotlib三个包依次列出是线性代数、符号运算、绘图的三个基础依赖。如果网络慢可以在命令后追加-i指定一个内部镜像源例如-i https://mirrors.xxx/simple把xxx换成你所在网络提供的 PyPI 镜像域名。还有一个容易忽略的坑源码里的requirements.txt是旧的里面写着一些已经不存在的包名比如把numpy写成numpi安装时直接报错。我一般会先打开requirements.txt看一眼再决定是整体安装还是逐包安装。2.3 老源码遇上新库np.matrix 和 numpy 版本下限源码能导入、能运行但满屏DeprecationWarning:the matrix subclass is not the recommended way...这是典型的老源码配了新库。早期很多数学书用np.matrix写矩阵这个类在 NumPy 里被长期保留但明确不推荐新版里行为也变过比如A * B 在 matrix 类里是矩阵乘法在普通 ndarray 里却是逐元素乘法同一个符号两套语义非常容易踩坑。最干净的做法是把.py文件里的np.matrix(...)全部替换成np.array(...)乘法用import numpy as np # 旧写法A np.matrix([[1, 2], [3, 4]]) # 新写法保持二维数组矩阵乘法用 A np.array([[1, 2], [3, 4]]) B np.array([[5, 6], [7, 8]]) print(A B)np.array返回的是普通 ndarray运算符明确表示矩阵乘法不受历史包袱影响。替换之后凡是依赖matrix特殊语义的代码比如.I求逆、.A1转一维数组也要逐一改掉.I改成np.linalg.inv(...).A1改成.flatten()。如果项目比较旧还可以在requirements.txt里锁一个兼容范围避免未来大版本把 API 再换一遍numpy1.21,2.3 sympy1.10 matplotlib3.52.3是上限防止哪天 NumPy 3.x 发布后语义变更matplotlib3.5保证中文字体配置的接口存在。锁定范围并不妨碍学习反而给了你一个稳定的复现环境。数学验证最怕的就是上午能跑下午装个新包就不能跑。3. 线性代数模块的连环坑shapes not aligned、奇异矩阵与星号乘法3.1 shapes 对不上报错信息已经把错误写在眼前线性代数源码最常见的报错长这样ValueError: shapes (2,3) and (4,) not aligned: 3 (dim 1) ! 4 (dim 0)它翻译成人话是左边矩阵的列数是 3右边向量的长度是 4列数和向量长度不相等矩阵乘法没法做。这种错误通常不是数学概念问题而是构造数据时多写或少写了一个元素。复现片段如下import numpy as np A np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3) c np.array([1, 0, 2, 1]) # 长度 4规则上无法与 A 相乘 print(np.dot(A, c))原因是np.dot要求第一个参数的第二个维度等于第二个参数的第一个维度。A形状是 (2, 3)c形状是 (4,)3 不等于 4于是直接抛出not aligned。解决办法是回到书本公式数清楚向量到底有几个分量。如果书上写的是三维向量那就把多余的那个元素删掉c np.array([1, 0, 2]) # 长度 3现在可以正常相乘 print(np.dot(A, c)) # 输出 [7, 16]定位这类问题我一般先看报错里两个括号里的数字(2,3)是前一个数组的形状(4,)是后一个数组的形状中间3 (dim 1) ! 4 (dim 0)明确指出哪个维度对不上。照着这个数字去查构造数据的代码比一行行读逻辑快得多。3.2 奇异矩阵求逆不是所有矩阵都有解另一个高频报错是LinAlgError: Singular matrix意思是矩阵不可逆通常是矩阵里有成比例的行或列行列式为零或者数据本身被错误地复制粘贴导致完全相同的两行。比如下面这个矩阵第二行是第一行的两倍行列式为 0np.linalg.inv直接拒绝执行import numpy as np A np.array([[1, 2], [2, 4]]) # 两行成比例奇异 try: print(np.linalg.inv(A)) except np.linalg.LinAlgError as e: print(奇异矩阵无法求逆:, e) print(伪逆结果:, np.linalg.pinv(A))这里有两个知识点。第一np.linalg.inv只对方阵且非奇异的矩阵有效第二即使矩阵奇异np.linalg.pinv还能给出 Moore–Penrose 伪逆在最小二乘和数据处理里非常有用。如果源码里这一步只是为了算回归系数直接用pinv往往比强行求逆更合理。排查时先算行列式det np.linalg.det(A) print(det)如果det的绝对值在1e-12以下基本可以断定矩阵数值上奇异。这时要回看数据来源是不是某一行被重复复制了是不是特征向量构造错了修正数据源而不是硬套求逆公式。3.3 星号是逐元素乘不是矩阵乘最隐蔽的 bug 是A * B不报错但结果和书上对不上。因为 ndarray 的*是逐元素乘法不是线性代数里定义的矩阵乘法。书上写ABPython 里必须写A B或np.matmul(A, B)。用同一个例子看差异import numpy as np A np.array([[1, 2], [3, 4]]) B np.array([[5, 6], [7, 8]]) print(A * B) # 逐元素乘法[[5, 12], [21, 32]] print(A B) # 矩阵乘法[[19, 22], [43, 50]]A * B将对应位置元素相乘而A B计算标准的行列内积。如果源码里混用了*数学推导会跟着错。搜索方法很简单在源码目录里执行grep -n \* *.py然后逐个看乘法两侧的变量是不是矩阵。凡是矩阵乘法统一改成这个符号从 NumPy 1.10 之后就成为推荐的矩阵乘法运算符语义清晰不会产生歧义。4. 微积分与符号运算的坑math 与 sympy 混用、积分不化简、中文乱码4.1 math.sin 遇上 sympy 符号变量类型错乱用 SymPy 做微积分时一个很典型的报错是TypeError: cant convert expression to float根因往往是源码里把import math和import sympy混着用了。math.sin、math.cos只接受浮点数不接受 SymPy 的符号变量而sympy.sin专门处理符号表达式。两者看起来同名底层完全不同。错误的写法大概长这样import math import sympy as sp x sp.symbols(x) f math.sin(x) # 这里就会炸math.sin 不能接收符号 x正确的做法是把所有针对符号变量的函数统一换成 SymPy 版本from sympy import symbols, sin, integrate x symbols(x) f sin(x) # 符号函数不是 math.sin print(integrate(f, x)) # 输出 -cos(x)symbols(x)定义符号变量xsin(x)构建符号表达式integrate(f, x)对变量x积分。注意symbols一次可以定义多个符号例如x, a, b symbols(x a b)但必须在等号左边数量与字符串里的符号数量一致否则直接报解包错误。判断源码是否混用的技巧打开文件头部看import段是否同时出现了math和sympy。如果出现再全局搜索math.凡是行里出现了math.xxx(某个 symbols 定义的符号)基本都要改掉。4.2 积分结果不化简带着 Integral 返回另一种现象是代码不报错但结果长这样Integral(exp(x)/(exp(2*x) 1), x)意思是 SymPy 没算出来把积分原样返回。常见原因有两个一是这个积分确实需要换元技巧SymPy 的启发式算法没触发二是符号变量没有声明正负性导致它不敢开根号或做绝对值化简。处理办法是先给变量加假设再用simplify收尾from sympy import symbols, exp, integrate, simplify x symbols(x, positiveTrue) # 声明 x 为正数 f exp(x) / (exp(2 * x) 1) result integrate(f, x) print(simplify(result))positiveTrue告诉 SymPyx 0它就可以放心做某些涉及对数、平方根的化简simplify会把atan(exp(x))之类的结果统一成最简形式。如果simplify之后还是那一串Integral说明闭式解可能不存在这时候可以考虑数值积分验证范围。我还会用一个小技巧验证符号结果是否正确对化简结果求导看能不能回到原函数。from sympy import diff check diff(result, x) print(simplify(check)) # 如果输出等于 f说明积分正确用导数验积分是数学源码排错里性价比最高的一招。很多 Symbolic 结果对不对求导回来一验就现原形。4.3 绘图空白与中文字体微积分源码里通常还带matplotlib画图。两个高发问题一是plt.show()后图形窗口是空白的二是标题或坐标轴中文全部变成方框乱码。空白图多数是因为直接用plt.plot(x, y)后没有调用plt.show()或者脚本跑完就退出图像窗口来不及刷新。养成固定写法import matplotlib.pyplot as plt import numpy as np fig, ax plt.subplots(figsize(6, 4)) x_vals np.linspace(0, 2 * np.pi, 200) y_vals np.sin(x_vals) ax.plot(x_vals, y_vals, labelsin(x)) ax.set_title(函数图像) # 注意这里的标题可能乱码 ax.legend() plt.show()fig, ax plt.subplots是现代 Matplotlib 推荐写法ax是坐标轴对象fig是画布对象np.linspace(0, 2*np.pi, 200)生成 200 个均匀分布的点ax.plot绘制折线图。如果输出文字乱码是因为 Matplotlib 默认字体不支持中文在绘图前加两行配置plt.rcParams[font.sans-serif] [SimHei, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False第一行指定备选字体SimHei 是黑体WenQuanYi 是 Linux 下的中文字体第二行关闭负号的 Unicode 渲染否则坐标轴上的负号会显示成方块。按这个配置标题和轴标签基本都能正常显示。5. 排查与避坑五条最像玄学的疑难记录5.1 循环边界多写一个数报错却出现在赋值那行现象程序运行到后段才抛IndexError: index out of bounds而且报错行根本不是循环那一行而是函数内部某句普通赋值。原因源码里遍历矩阵时把边界写错比如本应for i in range(n)被改成了for i in range(n 1)。前 n 次循环正常最后一次i超过最大下标在下一轮访问A[i]的地方才爆出来。报错位置远离真正出错的循环很迷惑人。解决先看堆栈最后一行拿到行号后往前找循环边界。统一写法import numpy as np A np.array([[1, 2, 3], [4, 5, 6]]) for i in range(A.shape[0]): # shape[0] 是行数绝不会多走 for j in range(A.shape[1]): # shape[1] 是列数 print(A[i, j])用A.shape[0]而不是写死的range(3)这样即使矩阵尺寸变了也不会越界。如果你从书上抄的伪代码用的是1起始下标移植到 Python 时必须整体减一。这是数学源码里最常见的改坏位置。5.2 全局打印选项被悄悄改掉数值看着像错的现象矩阵运算理论上没问题但打印出来的每个数都只有三位小数连求逆结果都变成一串0.000或inf。用户以为是算法错实际是显示精度被某个初始化脚本改了。原因源码里某处写了np.set_printoptions(precision3)它是个全局设置一旦执行整个进程后续所有数组打印都只显示三位小数。书上例题答案是1.0程序显示1.000还能接受但遇到1e-8这种小量直接显示成0.000就导致误判。解决不要用全局设置影响所有输出改用上下文管理器import numpy as np A np.linalg.inv(np.array([[1, 2], [3, 4]])) with np.printoptions(precision6, suppressTrue): print(A)np.printoptions只在这个with块内生效块结束就自动恢复默认打印设置。precision6表示保留六位小数suppressTrue表示不显示科学计数法。这样既能看到真实数值也不会污染后面代码的输出格式。排查这类问题先看源码里有没有set_printoptions这种全局调用有就改成局部写法。5.3 浮点误差让书上的精确值永远对不上现象调用np.linalg.det求一个二阶矩阵的行列式书上答案是0程序输出2.220446049250313e-16。第一反应是程序算错了实际上这是因为计算机用二进制近似表示浮点数微小误差在减法运算中会残留。原因浮点数本身不精确2 * 0.1不等于0.2差一个epsilon。只要涉及浮点运算直接比较相等就可能失败。解决用np.isclose和np.allclose做近似比较替代判断import numpy as np A np.array([[1, 2], [2, 4]]) print(np.isclose(np.linalg.det(A), 0)) # True print(np.allclose(A np.linalg.pinv(A), A)) # 验证伪逆的还原性np.isclose(a, b)默认在相对误差1e-5和绝对误差1e-8内视为相等np.allclose用于两个数组逐元素比较。写数学源码时凡是断言结果应该等于某值都建议用这两个函数避免在浮点精度上反复折腾。5.4 转置与共轭转置混用复数矩阵算到后面全错现象代码里全是.T运算不报错但复数矩阵的特征值、内积结果和书上不一致。原因线性代数书上涉及复数矩阵时需要的是共轭转置A^H也就是先取共轭再转置。NumPy 中的.T只转置不取共轭.conj().T才是共轭转置。源码里如果统一用.T遇到复数会静默出错。解决复数矩阵统一写import numpy as np A np.array([[1 2j, 0], [0, 3 - 1j]]) print(A.conj().T) # 共轭转置 print(A.T) # 只是转置结果不同A.conj()对每个元素取共轭.T做转置两步连用得到数学里的共轭转置。如果你确定矩阵是实数矩阵.T没问题一旦数据类型带了复数优先用.conj().T。排查时会看到复数矩阵的特征向量与书不一致先看这一行是.T还是.conj().T。5.5 solve 和 lstsq 混用过定方程组直接报 not square现象调用np.linalg.solve(A, b)报Last 2 dimensions of the array must be square意思是矩阵不是方阵没法用经典的求解公式。原因当方程组行数多于未知数过定方程组A是矩形矩阵solve要求A必须是方阵。数学源码里如果作者想求最小二乘解却错误地调用了solve就会卡在这里。解决判断方程组形态。如果shape(A)的行数大于列数改用np.linalg.lstsqimport numpy as np A np.array([[1, 1], [1, 2], [1, 3]]) # 3 行 2 列过定 b np.array([2, 4, 5]) x, residuals, rank, s np.linalg.lstsq(A, b, rcondNone) print(x) # 最小二乘解lstsq返回四个值解x、残差residuals、矩阵的秩rank、奇异值s。rcondNone让 NumPy 自动选择奇异值截断阈值避免处理病态矩阵时报错。如果你只需要解直接取第一个返回值x np.linalg.lstsq(A, b, rcondNone)[0]。这条规则记牢方阵用solve矩形矩阵用lstsq不是瞎换是数学问题类型不同。6. 快速定位一条最小检查链把对不上的结果锁死6.1 三个命令先跑一遍拿到任何一份数学源码包我先不急着打开 IDE而是在终端里按顺序执行三件事python -V python -m pip check python -m pytest -q tests/ --tbshortpython -V确认解释器版本pip check检查当前环境里依赖有没有互相冲突比如两个包各自要求不同版本的 NumPy它会直接打报告pytest如果有测试用例跑一遍能立刻知道哪些函数是坏的。没有 tests 目录的话跳过第三步直接看源码里的if __name__ __main__入口手动跑主文件。这三条命令的意义是先排除环境和依赖问题再谈数学问题。我见过太多人把依赖冲突误判成求逆公式写错在代码里来回改了好几个小时最后发现是两个包版本打架。6.2 数值探针与公式对照法如果没有测试用例我会在怀疑有 bug 的函数旁边临时插一段探针代码打印中间值和理论值import numpy as np # 理论值书上公式给了闭式解 expected np.array([1.0, -2.0]) A np.array([[3, 1], [1, 2]]) b np.array([1, 3]) actual np.linalg.solve(A, b) print(expected:, expected) print(actual: , actual) print(diff: , np.abs(actual - expected))一旦diff里的值不是零量级说明当前函数没按预设公式算。这时回到数学定义逐行对比矩阵构造顺序对不对向量拼接方向是行还是列乘法用的是还是*绝大多数问题出在这三处而不是算法本身。定位的优先级我建议按形状 → 类型 → 数值走先看数组 shape 是否符合矩阵乘法规则再看 dtype 是不是浮点或复数最后才看数值大小。形状错后面全白算类型错复数结果静默失真数值错才需要回到浮点误差和精度问题。从那以后我每次拿到这类用 Python 学数学的源码包都会强制先走一遍这条检查链建独立虚拟环境、锁依赖版本、跑最小用例、打印中间变量、再对着书上的公式核验数值。这个习惯帮我挡掉了大部分看起来像玄学的报错大多数问题在十分钟内就能定位到具体行。希望帮到你。本文还有配套的精品资源点击获取