2. 矩阵的创建与基本操作

在矩阵计算中,第一件事不是算,而是“把矩阵正确地造出来,并能准确地取到想要的那一块”。Python(以 NumPy 的 ndarray 和 numpy.matrix 为代表)与 MATLAB 在创建语法、索引基准、切片规则、拼接与变形接口上都有明显差异。把这些差异弄清楚,后续的运算、广播和线性代数调用才不会踩坑。本节围绕“创建—索引—切片—拼接—变形—属性”这条主线,用同一组数据在两种环境中对照操作。

一、创建:字面量语法与函数构造

MATLAB 的矩阵字面量非常直观:行内元素用空格或逗号分隔,行与行之间用分号分隔,例如 A = [1 2 3; 4 5 6] 得到一个 2×3 矩阵。Python 没有内置的矩阵字面量,通常用嵌套列表交给 np.array:A = np.array([[1,2,3],[4,5,6]])。两者语义一致,但 Python 的方括号层级更多,且逗号不可省略。

特殊矩阵的构造函数也值得对照记忆:单位阵 MATLAB 用 eye(n),Python 用 np.eye(n);全零、全一分别用 zeros/ones 与 np.zeros/np.ones;等差序列 MATLAB 用 1:0.5:3(起点:步长:终点),Python 用 np.arange(1, 3.5, 0.5)(起点, 终点(不含), 步长),注意 Python 的终点默认不包含,且参数顺序是“起点、终点、步长”,与 MATLAB 的“起点、步长、终点”正好错位,这是初学者最常写错的地方。若需要按点数等分,MATLAB 用 linspace(a,b,n),Python 用 np.linspace(a,b,n),两者一致。

关于 numpy.matrix:它是历史遗留类型,强制二维、* 表示矩阵乘法。官方已不推荐在新代码中使用,建议统一用 ndarray 配合 @ 做矩阵乘法。本节示例均以 ndarray 为准。

二、索引:0 基与 1 基的根本差异

这是两种环境最本质的区别:Python 索引从 0 开始,MATLAB 从 1 开始。取 A 的第 1 行第 2 列元素,MATLAB 写 A(1,2),Python 写 A[0,1]。Python 用方括号、逗号分隔维度;MATLAB 用圆括号、逗号分隔。Python 还支持负索引,A[-1,-1] 取右下角元素,MATLAB 的 end 关键字承担类似作用,如 A(end,end)。

整行整列的提取:MATLAB 用冒号占位,A(2,:) 取第 2 行,A(:,3) 取第 3 列;Python 用 A[1,:] 与 A[:,2]。需要特别提醒:Python 中 A[1] 取的是第 2 行(一维视图),而 MATLAB 中 A(1) 是按列优先的线性索引,取的是第 1 个元素,二者含义完全不同,切勿混用。

三、切片:区间端点是否包含

MATLAB 切片 A(2:4, :) 取第 2 到第 4 行,两端都包含。Python 切片 A[1:4, :] 取索引 1、2、3 行,即第 2 到第 4 行,左闭右开。因此把 MATLAB 的 2:4 翻译成 Python 要写成 1:4,终点不变、起点减一。步长切片同理:MATLAB A(1:2:end,:) 取奇数行,Python 写作 A[0::2,:]。

另一个高频陷阱是视图与副本。Python 的基本切片返回的是视图,修改切片会影响原数组:

import numpy as np
A = np.arange(12).reshape(3, 4)
B = A[0:2, 1:3]      # 视图
B[0, 0] = 99
print(A[0, 1])       # 输出 99,原数组被改动
C = A[0:2, 1:3].copy()  # 显式副本,互不影响

MATLAB 的切片赋值语义是“写回原数组”,不存在视图概念上的歧义,但 Python 若不注意这一点,很容易在无意中污染原始数据。需要独立副本时务必调用 .copy()。

四、拼接与变形

水平拼接:MATLAB 用 [A B] 或 horzcat(A,B),Python 用 np.hstack((A,B)) 或 np.concatenate((A,B), axis=1)。垂直拼接:MATLAB 用 [A; B],Python 用 np.vstack((A,B)) 或 np.concatenate((A,B), axis=0)。拼接要求除拼接轴外的维度一致,否则报错,这一点两边相同。

变形方面,MATLAB 用 reshape(A, m, n),Python 用 A.reshape(m, n)。关键差异在填充顺序:MATLAB 默认列优先(先填满一列再填下一列),Python 默认行优先(先填满一行)。对 1:6 变形为 2×3,MATLAB 得到 [1 3 5; 2 4 6],Python 的 np.arange(1,7).reshape(2,3) 得到 [[1,2,3],[4,5,6]]。若要在 Python 中复现 MATLAB 的列优先结果,可加参数 order='F'。转置则两边都简单:MATLAB 用 A',Python 用 A.T。

五、常用属性:shape / size / ndim

MATLAB 用 size(A) 返回各维长度向量,size(A,1) 取行数,ndims(A) 取维数,numel(A) 取元素总数。Python 对应:A.shape 返回元组如 (2,3),A.ndim 返回维数,A.size 返回元素总数。注意命名错位:MATLAB 的 size 对应 Python 的 shape,而 Python 的 size 对应 MATLAB 的 numel。写代码时按“我要的是形状还是元素个数”来选,而不是按名字硬套。

同一矩阵 A = [[1,2,3],[4,5,6]] 的两种操作路径 Python / NumPy(0 基、行优先) A = np.array([[1,2,3],[4,5,6]]) 创建:嵌套列表 → ndarray A[0,1] → 2 ;A[-1,-1] → 6 索引:0 基,负索引取末尾 A[0:2, 1:3] → 左闭右开 切片:返回视图,改切片会改原数组 np.vstack / np.hstack / concatenate 拼接:axis=0 纵向,axis=1 横向 A.reshape(3,2) → 行优先填充 变形:order='F' 可切换列优先 A.shape=(2,3) A.ndim=2 A.size=6 MATLAB(1 基、列优先) A = [1 2 3; 4 5 6] 创建:空格分列,分号分行 A(1,2) → 2 ;A(end,end) → 6 索引:1 基,end 取末尾 A(1:2, 2:3) → 两端都包含 切片:2:4 对应 Python 的 1:4 [A B] 横向 ;[A; B] 纵向 拼接:方括号直接并置 reshape(A, 3, 2) → 列优先填充 变形:先填满一列再填下一列 size(A)=[2 3] ndims(A)=2 numel(A)=6 对照

六、一次完整的对照练习

下面用同一目标验证上述差异:从 1 到 12 构造 3×4 矩阵,取第 2 行、第 2 到 3 列的子块,再把它垂直拼接到原矩阵下方。

import numpy as np
A = np.arange(1, 13).reshape(3, 4)   # 行优先
sub = A[1:2, 1:3]                    # 第2行,第2~3列 → [[6,7]]
result = np.vstack((A, sub))         # 4×4
print(A.shape, A.ndim, A.size)       # (3, 4) 2 12
print(result)

对应 MATLAB:

A = reshape(1:12, 3, 4);   % 列优先,内容与上面不同
sub = A(2, 2:3);
result = [A; sub];

注意这里 reshape 的填充顺序不同,导致两个 A 的内容并不相同——这正是需要显式确认的地方。判断结果是否正确,可以看三点:result 的行数是否等于原行数加子块行数;子块内容是否与 A 对应位置一致;shape/size 是否与预期维度吻合。若 Python 中 sub 被后续修改而 A 也跟着变,说明拿到的是视图,应改用 .copy()。

记忆口诀:Python 索引“从 0 起、右端不含、切片是视图”;MATLAB 索引“从 1 起、两端都含、reshape 列优先”。把 MATLAB 的区间 a:b 翻译成 Python 时,写成 a-1:b 即可。
不要在新代码中使用 numpy.matrix。它会让 * 变成矩阵乘法、强制二维,并与 ndarray 的广播规则冲突。统一用 ndarray 加 @ 运算符,可避免大量隐式行为带来的排错成本。

把创建、索引、切片、拼接、变形和属性这六件事在两种环境中各写一遍并对照输出,是掌握矩阵操作最有效的方式。真正需要养成的习惯是:每次写索引前先确认基准是 0 还是 1,每次切片后确认拿到的是视图还是副本,每次变形后确认填充顺序是否符合预期。