《程序员数学》Python修正版:线性代数与微积分代码实战

发布时间:2026/10/20 5:22:27
《程序员数学》Python修正版:线性代数与微积分代码实战
简介配套《程序员数学用Python学透线性代数和微积分》一书的源码程序包面向希望借助Python夯实数学基础的程序员、数据从业者与自学者。资源共104个文件以74个.py源码和17个.ipynb交互式笔记为主体按章节覆盖线性代数的向量运算、矩阵乘法、行列式、特征值分解以及微积分的极限、导数、积分等核心主题同时包含7张可视化图片、3个off三维模型文件及Markdown说明文档。与一般源码包不同作者在整理时修正了原书代码中的多处错误bug修复确保脚本可直接运行避免初学者踩坑。整套包仅13.55MB7z压缩目录结构与书本章节一一对应便于边读边练、逐章对照调试。已有1652人学习下载无论作为入门辅助还是复习参考都能帮助读者更直观地理解抽象数学概念并提升用代码解决实际问题的能力。1. 从书本到能跑的代码一本数学书和它的Python修正版买过《程序员数学》这类书的人大概都有同一种经历书上的数学推导看得懂一打开编辑器照着敲代码就原形毕露。不是书上代码缺了导入就是API早就过时更别说有时候打印出来的结果和公式都对不上。这份资源把书里线性代数和微积分两大部分的全部示例代码整理成了可运行的Python源码并且针对书中已知错误做了修正——从向量、矩阵运算到特征值分解从极限、导数到定积分和梯度下降每一段代码都能直接跑出和数学定义一致的结果。适合两类人一类是想用Python验证数学公式的自学者另一类是手边需要现成数值计算范例、想快速二次开发的程序员。别把它当什么插件它是一套和书本章节一一对应的源码工程。2. 技术选型与项目骨架三个库怎么分工拿到源码先看依赖这是所有Python项目的通用动作。这套代码的技术选型非常收敛只围绕三个库展开NumPy负责数值计算SymPy负责符号运算Matplotlib负责可视化。三者几乎没有重叠分工干净学起来也不累。2.1 数值、符号与可视化NumPy、SymPy、Matplotlib各管一段数学代码最怕“数值和符号混着算”。比如你想验证导数公式用NumPy只能算出某个点上的近似斜率而SymPy能直接给你3*x**2这个表达式两者用途完全不同。项目里常见的设计是凡是“算出一个数”的地方用NumPy凡是“推导出一个公式”的地方用SymPy凡是“把结果画出来”的地方用Matplotlib。# 项目里每个脚本的公共头部三个库的分工一眼能看明白 import numpy as np import sympy as sp import matplotlib.pyplot as plt # SymPy 打印结果时用接近数学书的排版方式 sp.init_printing() # Matplotlib 全局设置中文避免图上出现方块字 plt.rcParams[font.sans-serif] [SimHei] # 换成系统里的中文字体 plt.rcParams[axes.unicode_minus] False # 否则负号显示成一条横线这三行配置是整个项目的地基。sp.init_printing()让SymPy在控制台里输出x**3而不是难看的一维字符串plt.rcParams里如果不设置中文字体后面所有带中文标题的图都会变成一个个空心方块调试起来非常劝退。项目把这段公共配置抽成了一个common/utils.py其他脚本统一导入避免每份文件里重复写。2.2 目录结构按章节映射的组织方式这套源码的目录结构是“按书本章节映射”的思路不是按算法类型分。线性代数的内容在一个文件夹微积分的内容在另一个文件夹每个脚本的文件名直接对应书上的知识点。这种方式对读者最友好——你翻到书上哪一节就去哪个文件夹找同名脚本定位成本几乎为零。math_with_python/ ├── chapter02_vector/ │ ├── vector_basics.py # 向量的定义、加减法、数乘 │ ├── dot_product.py # 点积、夹角、模长 │ └── projection.py # 向量投影已修正书上公式错误 ├── chapter03_matrix/ │ ├── matrix_ops.py # 矩阵创建、转置、逆、行列式 │ ├── solve_linear_eq.py # 线性方程组求解 │ └── eig_demo.py # 特征值、特征向量与几何意义 ├── chapter04_calculus/ │ ├── limit_demo.py # 用 SymPy 求极限 │ ├── derivative_demo.py # 符号求导和数值差分 │ ├── integral_demo.py # 定积分、不定积分、数值积分 │ └── taylor_series.py # 泰勒展开与逼近误差 ├── common/ │ └── utils.py # 公共字体、打印配置 └── README.md # 依赖清单和每个脚本的说明这种目录设计好在两个地方第一你按书上进度走就行不需要猜测“这个知识点脚本放哪”第二二次开发时想扩展某个知识点直接在同目录加同名_v2脚本不会污染原工程。common/utils.py是唯一跨章节共享的模块这符合“多读少改”的原则——公共配置收敛在一处以后换字体或加库只动一个文件。这本书主要是概念讲解代码量不算大文件列表如下章节对应脚本核心内容向量vector_basics.py / dot_product.py / projection.py向量运算、点积、投影矩阵matrix_ops.py / solve_linear_eq.py / eig_demo.py矩阵运算、方程组、特征分解微积分limit_demo.py / derivative_demo.py / integral_demo.py / taylor_series.py极限、导数、积分、展开2.3 环境搭建跑通第一个示例环境搭建不复杂Python 3.8以上都能跑核心依赖就三个。我一般会让学习者先建独立虚拟环境不要直接往全局装避免和系统自带的包冲突。# 创建独立虚拟环境 python -m venv .venv # 激活环境Windows 下用 .venv\Scripts\activate source .venv/bin/activate # 安装三个核心依赖 pip install numpy sympy matplotlib安装完先跑一个验证脚本确认环境没问题。这里拿dot_product.py当冒烟测试最合适因为它只用NumPy不涉及SymPy和绘图跑挂了可以立刻定位到依赖层。python chapter02_vector/dot_product.py这份源码里脚本大多带if __name__ __main__保护所以既能直接python xxx.py执行也能被其他模块导入复用。运行结果通常是控制台打印关键数值比如点积结果、夹角弧度矩阵和微积分相关脚本还会弹出Matplotlib窗口。如果弹出程序画图后挂起关掉图形窗口程序就会继续执行这不是卡死而是plt.show()的阻塞行为。提示如果遇到ModuleNotFoundError: No module named numpy说明你激活的不是装好依赖的那个虚拟环境检查一下终端前面有没有(.venv)前缀。3. 线性代数模块拆解从向量运算到特征值分解线性代数部分占了这本书前三分之一也是后续微积分几何意义的基础。这套源码在向量、矩阵、方程组、特征值四个点上写得最扎实下面挑三个关键脚本拆开讲。3.1 向量与矩阵基础把数学定义翻译成ndarray向量在数学里是“既有方向又有大小的量”在NumPy里就是一个一维数组。这里有一个新手很容易绕进去的点np.array和np.matrix都能表示矩阵但前者才是现在的官方推荐后者在新版NumPy里已经边缘化。这套源码里统一用np.array就是为了避免旧书上np.matrix那套.I逆矩阵写法在新环境里翻车。import numpy as np # 向量一维 ndarray数学里的 u (1, 2, 3) u np.array([1.0, 2.0, 3.0]) v np.array([4.0, 5.0, 6.0]) # 点积两种写法等价np.dot(u, v) 和 u v dot_uv np.dot(u, v) # 模长默认算 2 范数ord 参数可以改成 1 范数等 norm_u np.linalg.norm(u) # 夹角公式cos θ (u·v) / (|u|·|v|) cos_theta dot_uv / (norm_u * np.linalg.norm(v)) theta np.arccos(cos_theta) print(u 和 v 的夹角弧度:, theta)这段代码逻辑很直白但有两个细节值得说。第一数组里的数字写成1.0而不是1是为了让后续除法默认用浮点数运算避免整数除法在极端场景下出现截断。第二np.arccos之前最好把cos_theta用np.clip(cos_theta, -1, 1)夹一下因为浮点误差可能导致这个值变成 1.0000000002arccos直接返回nan这是数值计算里很常见的隐蔽问题。矩阵部分代码套路类似但多了一个高频易错点矩阵乘法必须用A B或np.matmul(A, B)而A * B是对应位置相乘不是数学上的矩阵乘法。# 2×2 矩阵创建 A np.array([[1, 2], [3, 4]]) # 转置、逆、行列式一把梭 A_T A.T A_inv np.linalg.inv(A) det_A np.linalg.det(A) # 矩阵乘法行乘列不是逐元素相乘 B np.array([[5, 6], [7, 8]]) C A Bnp.linalg.inv内部用的是LAPACK的DGETRF例程实际就是LU分解加回代。需要注意的是逆矩阵不总是存在行列式为0时inv会抛LinAlgError。这本《程序员数学》里特意强调了“行列式为0意味着矩阵不可逆”这个知识点代码里对应就要求先算det再决定要不要调inv这是个好习惯。3.2 线性方程组求解从LU分解到numpy.linalg.solve线性方程组求解是线性代数里应用最广的部分也是代码最简洁的部分。有人问为什么不手写高斯消元这套项目的答案是有np.linalg.solve直接封装了LAPACK里经过优化和稳定性处理的求解例程比自己造的轮子可靠得多。import numpy as np # 3 元一次方程组 # 2x y - z 8 # -4x 6y 2z -2 # x - 3y z 1 A np.array([[2, 1, -1], [-4, 6, 2], [1, -3, 1]]) b np.array([8, -2, 1]) # solve 内部就是 LU 分解 前后代 x np.linalg.solve(A, b) print(解:, x) # 后验检查A x 理论上等于 b residual A x - b print(残差:, residual)np.linalg.solve要求A是方阵且可逆如果矩阵奇异会抛出LinAlgError: Singular matrix。这里“残差”是判断数值解靠不靠谱的关键手段——用浮点数算出来的解本来就带误差残差数量级在1e-14说明精度正常如果残差很大说明矩阵接近奇异或者你b的形状传错了。有一点值得提醒解这类方程之前可以先检查A的条件数np.linalg.cond(A)。条件数越大解对输入的扰动越敏感这是书里讲“数值稳定性”时没展开的部分项目里这段脚本的注释里补了一行print(np.linalg.cond(A))算是给学有余力者留的钩子。3.3 特征值与特征向量几层意义和一次可视化特征值和特征向量是线性代数里最抽象的部分光看公式容易晕。项目里的eig_demo.py做了一个非常直观的可视化把一个单位圆上的所有点用矩阵作用一遍观察它如何变成一个椭圆同时把特征向量的方向画出来。抽象概念一旦画出来记一辈子。import numpy as np import matplotlib.pyplot as plt # 实对称矩阵特征值是实数特征向量正交 A np.array([[2, 1], [1, 2]]) # eig 返回特征值数组和特征向量矩阵列向量 eigvals, eigvecs np.linalg.eig(A) print(特征值:, eigvals) # 用特征值最大的方向验证 A·v λ·v v eigvecs[:, 0] lhs A v rhs eigvals[0] * v print(验证等式成立:, np.allclose(lhs, rhs)) # 可视化单位圆在 A 作用下变成椭圆 theta np.linspace(0, 2 * np.pi, 100) circle np.vstack([np.cos(theta), np.sin(theta)]) mapped A circle fig, ax plt.subplots(1, 2, figsize(8, 3.5)) ax[0].plot(circle[0], circle[1]) ax[0].set_title(原始单位圆) ax[1].plot(mapped[0], mapped[1]) ax[1].set_title(A 作用后的椭圆) for i in range(2): vec eigvecs[:, i] * eigvals[i] ax[1].arrow(0, 0, vec[0], vec[1], colorred, width0.05) plt.show()这段代码里有三个高频函数需要理解。np.linalg.eig对非对称矩阵可能返回复数特征值这是数学上成立的不是程序出错eigvecs[:, 0]取第一列也就是对应eigvals[0]的特征向量注意是列方向np.allclose自带误差容限用来比较浮点数组是否“近似相等”千万不要用。为什么用A [[2, 1], [1, 2]]这个矩阵因为它是实对称矩阵特征值是常数而且特征向量一定正交。这样一来单位圆被拉伸成椭圆后椭圆的长短轴方向正好就是两个特征向量方向特征值就是各自轴的拉伸倍数。代码里的红色箭头乘以特征值就是矩阵实际“拉”出去的方向和幅度。跑一遍这个脚本特征值那套推论基本就刻在脑子里了。4. 微积分模块拆解符号求导、数值积分与梯度下降微积分部分的代码比线性代数更贴近“数学味”因为SymPy能做符号运算。看书上公式推导时觉得理所当然但用代码验证一遍常常能发现直觉上的偏差。4.1 极限与导数数值差分和SymPy符号求导两种姿势极限是微积分的起点SymPy里求极限非常简洁但这有个前提所有参与运算的符号变量必须先声明否则解释器直接报TypeError或NameError这个坑后面避坑章节还会细说。import sympy as sp # 先声明符号变量这一步不能省 x sp.symbols(x) # 经典的 sin(x)/x 在 0 处极限结果是 1 limit sp.limit(sp.sin(x) / x, x, 0) print(极限:, limit) # 多项式求导f(x) x³ → f(x) 3x² fx x**3 dfdx sp.diff(fx, x) print(导数:, dfdx)sp.limit的第一个参数是被求极限的表达式第二个是变量第三个是趋近值。sp.diff还可以传第三个参数表示求几阶导比如sp.diff(fx, x, 2)是二阶导。项目里用的是“先符号求导、再数值代值”的验证方式——先用sp.diff拿到导数表达式再用fx.subs(x, 1.5)代入具体数值验证。除了符号求导项目里还有一段数值差分的代码。符号求导给出精确公式但在工程里很多函数的解析式根本不存在只有一组采样点这时候只能用差分近似。import numpy as np def derivative_center(f, x0, h1e-6): 中心差分公式 f(x) ≈ [f(xh) - f(x-h)] / 2h 相比前向差分误差从 O(h) 降到 O(h²) return (f(x0 h) - f(x0 - h)) / (2 * h) # 验证 sin 在 0 处的导数应该是 1 print(derivative_center(np.sin, 0.0))中心差分的参数里h步长的选择是个双向权衡。h太大截断误差膨胀h太小浮点舍入误差反过来影响结果一般取1e-6到1e-8是经验区间。这个脚本的注释里特别提醒导数是“函数在某一点的变化率”而差分是用“附近两点连线的斜率”去近似切线斜率理解这一点比记住公式重要。4.2 积分定积分、不定积分与数值积分积分部分的代码分两层。第一层用SymPy求解析解适合检验书上那些积分公式有没有抄错第二层用数值积分处理“没有初等原函数”的积分两者是互补关系不是替代关系。import sympy as sp x sp.symbols(x) # 不定积分∫ x² dx x³/3 indef sp.integrate(x**2, x) # 定积分∫₀¹ x² dx 1/3 defi sp.integrate(x**2, (x, 0, 1)) print(不定积分:, indef) print(定积分:, defi) # 数值积分当被积函数没有初等原函数时用 import scipy.integrate as spi val, err spi.quad(lambda t: np.exp(-t**2), 0, 1) print(数值积分结果:, val, 误差估计:, err)sp.integrate有两种参数写法传(x, 0, 1)表示定积分只传x表示不定积分。第二种数值积分scipy.quad是自适应高斯求积返回值第一个是积分近似值第二个是误差估计。这个脚本被设计成“scipy装不装都能跑”——装了就演示数值积分没装则跳过去直接用SymPy的解析结果。这种兼容设计在源码工程里很实用依赖不是硬性的读者可以根据手边环境灵活处理。4.3 泰勒展开与梯度下降从公式到算法微积分模块最后落到两个高价值内容泰勒展开和梯度下降。前者是“用多项式逼近函数”后者是“用导数找极小值”它们放在一起的目的是让读者看到微积分到底怎么变成算法。泰勒展开用SymPy一行就能完成import sympy as sp x sp.symbols(x) # e^x 在 x0 处展开到 5 阶O(x⁵) 是余项 series sp.series(sp.exp(x), x, 0, 5) print(series)sp.series四个参数分别是表达式、展开变量、展开中心和阶数。输出里会带一个O(x**5)余项表示截断误差。想拿到多项式本体用.removeO()去掉余项。项目里脚本后续会画出原始函数和不同阶展开的对比曲线直观展示“展开阶数越高逼近范围越宽”这件事。梯度下降是微积分到机器学习的桥。项目里给了一个特别精简的通用模板代码本身不超过15行但把梯度、步长、收敛容差这几个核心概念全部落到了参数上。import numpy as np def gradient_descent(grad, start, lr0.1, max_iter100, tol1e-6): 通用梯度下降模板。 grad: 传入一个点返回该点的梯度数组 start: 初始点坐标一维或高维数组 lr: 学习率太大震荡发散太小收敛慢 tol: 两次迭代的移动距离小于它视为收敛 x np.array(start, dtypefloat) for i in range(max_iter): g np.array(grad(x)) # 当前点的梯度 x_new x - lr * g # 沿负梯度方向迈一步 if np.linalg.norm(x_new - x) tol: print(f迭代 {i1} 次后收敛) break x x_new return x # 目标函数 f(x, y) x² 2y²梯度为 [2x, 4y] grad lambda p: np.array([2 * p[0], 4 * p[1]]) minimum gradient_descent(grad, [2.0, 1.0], lr0.1) print(极小值点:, minimum)这段代码逻辑就是不断重复“算梯度、往负方向挪一步、检查是否挪得够小”。lr的取值是关键中的关键设成0.1对这个问题合适设成1.0就可能来回震荡不收敛这就是书里“学习率是玄学”这句话背后的代码证据。tol控制提前退出的条件实际工程里远远不止这个简单实现但用来理解“微积分定理如何变成优化算法”这段代码是最短的路径。5. 避坑记录跑这本书的代码我踩过的五个坑这部分全是实战里真会遇到的报错和诡异现象。作者在整理这套源码时修复了不少“书上代码的错误”所以我把经典的坑按“现象→原因→解决”记在这里你照着跑大概率会撞上其中一两个。5.1 np.matrix 的 .I 逆矩阵写法翻车现象照着老代码敲A np.matrix([[1, 2], [3, 4]])然后用A.I求逆新版NumPy直接抛AttributeError控制台里还会带一行FutureWarning或者干脆找不到属性。原因书中写这套代码时np.matrix还是主流官方后来强推np.ndarraymatrix类的专属方法.I被逐步移除边缘化。解决项目里统一把所有np.matrix替换成np.array逆矩阵一律用np.linalg.inv(A)矩阵乘法用。如果你手里还有老代码全局搜索.I和.getA()改成np.linalg.inv和np.asarray就完事。5.2 SymPy 符号变量未声明的报错现象运行sp.diff(x**2, x)时报NameError: name x is not defined或者报TypeError: x object is not callable这种奇怪错误。原因SymPy 里所有独立变量必须先用sp.symbols(x)声明。Python 解释器在遇到x时会先到命名空间去找这个变量找不到就当场报错跟数学里“假设x是实数”的习惯完全不同。解决每个脚本顶部强制写一行x, y, z sp.symbols(x y z)。我在整理这套源码时发现书上不少代码片段省略了符号声明这一步以为读者会补结果成了跑不起来的第一大原因。5.3 Matplotlib 中文方块和负号乱码现象图表的标题、坐标轴标签里凡是中文全部显示成空心方块坐标轴上的负号变成一条短横线甚至消失。原因Matplotlib 默认字体是 DejaVu Sans不含中文字形负号用的是 Unicode 码位渲染部分后端不支持。解决在脚本开头统一设置两行plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False这个项目把这两行抽到了common/utils.py里每个绘图脚本导入一下就行。macOS 或 Linux 上如果SimHei不存在需要改成PingFang SC或WenQuanYi Zen Hei看自己系统里装了哪个。5.4 向量投影公式方向写反现象按照书上代码把向量a投影到b上算出来的结果方向和b相反或者和手算结果对不上。更隐蔽的是当a的长度大于b时投影长度甚至大于被投影向量。原因书上投影公式分母用错了模长。正确公式是proj_b(a) (a·b / |b|²) · b方向是b的方向如果分母错写成|a|²方向和尺度全乱了。解决项目里的projection.py已按正确公式修正并且加了注释说明原始错误位置。二次开发时如果自己写投影代码先做两个呼啦圈测试投影结果和b方向必须一致当a垂直于b时结果必须为零向量。两个测试过了公式基本没问题。5.5 特征值分解返回复数验证一直失败现象用np.linalg.eig求某个矩阵的特征值结果里有0.51.0j这样的虚部拿np.allclose(A v, λ * v)验证时怎么都对不上。原因矩阵不是实对称矩阵。实数非对称矩阵的特征值完全可能是复数这是数学事实而非程序错误程序返回复数不是bug是你输入矩阵的“属性”和期望不符。解决先判断np.allclose(A, A.T)如果矩阵对称改用更稳定更快的np.linalg.eigh/eigvalsh它们保证返回实数特征值。非对称矩阵就接受复数结果验证时注意np.allclose能处理复数不行。6. 二次开发把书上的练习改成自己的数学实验台源码拿到手跑通只是第一步真正有价值的是这套结构怎么改成你自己的工具。这里给出三个我最常用的扩展思路都是基于现有文件小改就能完成的方向。第一个思路是把散落的脚本函数化。项目里的脚本大多直接打印结果但如果你想在自己的程序里调用投影公式就得把核心逻辑抽成函数。比如把projection.py改成这样import numpy as np def project(u, v): 把向量 u 投影到 v 的方向上。 参数u, v 都是 ndarray形状一致。 返回投影向量。 return (np.dot(u, v) / np.dot(v, v)) * v # 一个快速验证投影结果必须和 v 同向 u np.array([1, 2]) v np.array([3, 0]) assert np.dot(project(u, v), v) 0 print(project(u, v))这样改造以后原来的脚本变成可导入的模块任何其他脚本都能from projection import project。参数说明浓缩在docstring里调用方不用翻书就能知道u和v都是ndarray。第二个思路是给绘图脚本加交互参数。原脚本的图和参数都是写死的比如特征值可视化里A [[2, 1], [1, 2]]你可以把A改成从命令行读入python eig_demo.py 3 1 1 2代码里用sys.argv解析这四个数字重新拼成矩阵就能在不改代码的情况下比较不同矩阵的拉伸效果。同一个脚本变成实验工具这是最省力的交互化改造也适合拿给身边的人按着玩。第三个思路是把微积分模块和线性代数模块串起来。比如用梯度下降模板去求“矩阵的特征值”——其实np.linalg.eig内部就在干这件事但你自己写一遍x_next x - lr * (A x - λ_min * x)这样的迭代才能体会到数值线性代数和优化算法的边界到底在哪。项目里的脚本当初分开是配合书本章节二次开发时就不该被目录结构限制住跨章节组合才是真正的收益。最后说一个我自己的习惯从那以后我每拿到一份书配代码第一件事永远不是看正文而是把所有脚本按章节过一次先跑通、再读数学、最后才动手改。这份项目让我最满意的地方恰恰是它在README里标注了每一处对原书的修正省掉了我逐个对比公式的时间。希望这些经验能帮到你哪怕只帮你少踩一个坑。本文还有配套的精品资源点击获取