不用NumPy怎么用Python类实现矩阵运算
本文介绍如何脱离NumPy库,使用纯Python二维列表和面向对象类封装实现矩阵的初始化、加法、标量乘法、矩阵乘法与转置运算。
脱离第三方数学计算库时,用Python原生语言实现矩阵运算的核心方案是:以二维嵌套列表(list[list])作为基础内存容器,利用面向对象类封装其行列维度,并通过重载Python运算符特殊方法(如 __add__、__mul__、__matmul__)来提供直观的矩阵代数语法。
一、数据结构选型与设计边界
Python标准库并没有直接提供类似 ndarray 的多维连续内存数组。最直接且易维护的底层载体是嵌套列表,即“外层列表代表行,内层列表代表列”。
在使用原生列表封装矩阵时,必须注意 Python 引用的浅拷贝问题。如果使用类似 [[0] * c] * r 的写法,外层列表存储的实际上是指向同一个内层列表的引用,修改一个元素会导致整列被联动修改。正确的做法是通过列表推导式独立创建每一行。
二、基础类构建与初始化检验
初始化矩阵对象时,不能简单保存传入的二维数组,需要校验传入数据的规整性:外层必须是非空列表,每行的列数必须一致,且内部元素必须支持数值计算。
class Matrix:
def __init__(self, data):
# 校验外层是否为非空列表
if not isinstance(data, list) or len(data) == 0:
raise ValueError("矩阵数据必须为非空二维列表")
# 校验第一行是否为列表
if not isinstance(data[0], list) or len(data[0]) == 0:
raise ValueError("矩阵行数据必须为非空列表")
self.rows = len(data)
self.cols = len(data[0])
# 深度复制并检查每一行的列数是否一致
self.data = []
for row in data:
if not isinstance(row, list) or len(row) != self.cols:
raise ValueError("所有行的列数必须保持严格一致")
self.data.append([float(x) for x in row])
@property
def shape(self):
return (self.rows, self.cols)
def __repr__(self):
# 格式化输出方便在终端调试观察
lines = ["[" + ", ".join(f"{val:8.2f}" for val in row) + "]" for row in self.data]
return "Matrix([\n " + ",\n ".join(lines) + "\n])"
float(x) 强制类型转换,一方面保证了整型和浮点型均可参与统一的数学计算,另一方面排除了非数值类型输入。使用推导式重建列表也避免了外部修改原列表影响类内部状态。
三、矩阵加法与标量乘法实现
矩阵加法的先决条件是两矩阵维度完全一致。加法是对应位置元素相加(Element-wise Addition)。我们通过实现 Python 的 __add__ 魔法方法来启用 + 操作符。
数乘运算(标量乘法)中,标量与矩阵内的每个数值相乘。通常我们希望同时支持 matrix * 2 和 2 * matrix,这就需要一并重载 __mul__ 与 __rmul__。
def __add__(self, other):
if not isinstance(other, Matrix):
raise TypeError("加法操作只支持两个 Matrix 对象之间进行")
if self.shape != other.shape:
raise ValueError(f"矩阵维度不兼容,无法相加: {self.shape} vs {other.shape}")
result = [
[self.data[i][j] + other.data[i][j] for j in range(self.cols)]
for i in range(self.rows)
]
return Matrix(result)
def __mul__(self, scalar):
# 实现标量数乘
if not isinstance(scalar, (int, float)):
raise TypeError("标量乘法仅支持与数字进行")
result = [
[self.data[i][j] * scalar for j in range(self.cols)]
for i in range(self.rows)
]
return Matrix(result)
def __rmul__(self, scalar):
# 允许标量位于左侧,如 3 * A
return self.__mul__(scalar)
四、核心算法:矩阵乘法与转置
在严格的代数定义中,矩阵乘法(Matrix Multiplication)并非对应元素相乘,而是行与列的点积。设矩阵 $A$ 维度为 $m \times k$,$B$ 维度为 $k \times n$,则乘积结果矩阵 $C$ 维度为 $m \times n$。
Python 3.5 引入了专门用于矩阵乘法的符号 @,对应魔法方法为 __matmul__。遵循约定,我们将点乘/矩阵积放在 __matmul__ 中实现,避免与数乘逻辑混淆。
def __matmul__(self, other):
if not isinstance(other, Matrix):
raise TypeError("矩阵乘法运算必须在两个 Matrix 对象之间进行")
# 校验维度: A 的列数必须等于 B 的行数
if self.cols != other.rows:
raise ValueError(f"维度不匹配,无法相乘: 左矩阵列数 {self.cols} != 右矩阵行数 {other.rows}")
# 创建 m 行 n 列的全零临时矩阵
result = [[0.0 for _ in range(other.cols)] for _ in range(self.rows)]
# 三重循环计算内积
for i in range(self.rows):
for j in range(other.cols):
total = 0.0
for k in range(self.cols):
total += self.data[i][k] * other.data[k][j]
result[i][j] = total
return Matrix(result)
def transpose(self):
"""转置矩阵:行列互换"""
result = [
[self.data[r][c] for r in range(self.rows)]
for c in range(self.cols)
]
return Matrix(result)
@property
def T(self):
return self.transpose()
五、完整代码与执行验证
为了验证实现的有效性,我们使用一个可独立运行的代码块,执行矩阵加法、标量乘法、转置以及矩阵相乘。
if __name__ == "__main__":
# 初始化两个 2x3 矩阵
A = Matrix([
[1, 2, 3],
[4, 5, 6]
])
B = Matrix([
[7, 8, 9],
[1, 2, 3]
])
print("--- 矩阵 A ---")
print(A)
print("\n--- 矩阵加法 (A + B) ---")
print(A + B)
print("\n--- 标量乘法 (A * 2) ---")
print(A * 2)
print("\n--- 矩阵转置 (A.T) ---")
print(A.T)
print("\n--- 矩阵乘法 (A @ A.T) ---")
# A 为 2x3, A.T 为 3x2, 相乘结果应为 2x2
print(A @ A.T)
六、原生实现的性能特征与适用边界
完全采用 Python 原生类实现矩阵运算,代码直观、无第三方依赖、便于深入理解底层数学规则。但在生产环境或大规模数据场景下,需要清楚其客观性能限制。
原生实现的矩阵乘法复杂度为 $O(n^3)$,受限于解释器开销与列表内存布局的不连续性,当矩阵维度上升到数百级别时耗时会显著上升。但这套封装对于不便于引入庞大依赖库(如受限的嵌入式环境、云函数冷启动限制环境)、基础教学以及纯算法验证来说,已经提供了足够完备且符合数学直觉的运算能力。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









