4. 自定义矩阵类:实现一个2x2矩阵类,支持矩阵加法、乘法、转置
矩阵运算,说白了就是批量处理数据的「打包操作」。我在做图像处理项目时,经常要跟3x3的卷积核打交道。那时候我就想,要是Python自带一个轻量的矩阵类该多好?NumPy当然强大,但有时候你只需要一个2x2的小工具,自己动手写反而更灵活。
这一章,我们就来造一个2x2矩阵类。别小看它,麻雀虽小五脏俱全。加法、乘法、转置,这三个操作覆盖了线性代数里最核心的运算模式。
4.1 矩阵类的骨架设计
先搭架子。一个矩阵类,至少得存数据、能初始化、能展示。我个人习惯用嵌套列表来存2x2矩阵——[[a, b], [c, d]],直观又好理解。
class Matrix2x2:
"""2x2 矩阵类,支持加法、乘法、转置"""
def __init__(self, data):
"""
初始化矩阵
:param data: 4个元素的列表,或2x2嵌套列表
"""
if len(data) == 4 and all(isinstance(x, (int, float)) for x in data):
# 扁平列表转成2x2
self.data = [[data[0], data[1]], [data[2], data[3]]]
elif len(data) == 2 and len(data[0]) == 2 and len(data[1]) == 2:
self.data = data
else:
raise ValueError("数据必须是4个数字或2x2嵌套列表")
def __repr__(self):
"""让打印好看点"""
return f"[{self.data[0][0]:>6.2f} {self.data[0][1]:>6.2f}]\n[{self.data[1][0]:>6.2f} {self.data[1][1]:>6.2f}]"
小技巧:用
__repr__ 控制打印格式,调试时一眼就能看清矩阵内容。我习惯把数字右对齐,小数点后保留两位,看着整齐。
4.2 矩阵加法:对应位置相加
加法最简单——对应位置的元素相加。但有个前提:两个矩阵必须维度相同。2x2只能加2x2,不能加3x3。
def __add__(self, other):
"""矩阵加法:对应元素相加"""
if not isinstance(other, Matrix2x2):
raise TypeError("只能加另一个2x2矩阵")
result = [
[self.data[0][0] + other.data[0][0], self.data[0][1] + other.data[0][1]],
[self.data[1][0] + other.data[1][0], self.data[1][1] + other.data[1][1]]
]
return Matrix2x2(result)
为什么用 __add__?这样就能直接用 + 运算符了。Python的魔术方法,说白了就是让自定义类用起来像内置类型。我曾经在项目里忘了实现 __add__,结果同事写 m1 + m2 报错,被吐槽了半天……
4.3 矩阵乘法:行乘列
矩阵乘法比加法复杂一点。2x2矩阵乘法的规则是:结果矩阵的 (i, j) 位置,等于左矩阵第 i 行与右矩阵第 j 列的点积。
公式长这样:
| 结果位置 | 计算公式 |
|---|---|
| (0,0) | a₁₁ × b₁₁ + a₁₂ × b₂₁ |
| (0,1) | a₁₁ × b₁₂ + a₁₂ × b₂₂ |
| (1,0) | a₂₁ × b₁₁ + a₂₂ × b₂₁ |
| (1,1) | a₂₁ × b₁₂ + a₂₂ × b₂₂ |
def __mul__(self, other):
"""矩阵乘法:行乘列"""
if not isinstance(other, Matrix2x2):
raise TypeError("只能乘另一个2x2矩阵")
a = self.data
b = other.data
result = [
[a[0][0]*b[0][0] + a[0][1]*b[1][0], a[0][0]*b[0][1] + a[0][1]*b[1][1]],
[a[1][0]*b[0][0] + a[1][1]*b[1][0], a[1][0]*b[0][1] + a[1][1]*b[1][1]]
]
return Matrix2x2(result)
注意:矩阵乘法不满足交换律!
A × B 不一定等于 B × A。我在做坐标变换时踩过这个坑,调了半天才发现是乘反了。
4.4 矩阵转置:行变列,列变行
转置就是把矩阵的行和列互换。2x2矩阵转置后还是2x2,但元素位置变了:(i, j) 变成 (j, i)。
def transpose(self):
"""返回转置后的新矩阵"""
result = [
[self.data[0][0], self.data[1][0]],
[self.data[0][1], self.data[1][1]]
]
return Matrix2x2(result)
你想想看,转置有什么用?我在做图像旋转时,转置就是90度翻转的第一步。配合乘法,能实现各种几何变换。
4.5 完整代码与测试
把上面几块拼起来,再加个 __eq__ 方法方便比较,就是一个完整的2x2矩阵类了。
class Matrix2x2:
def __init__(self, data):
if len(data) == 4 and all(isinstance(x, (int, float)) for x in data):
self.data = [[data[0], data[1]], [data[2], data[3]]]
elif len(data) == 2 and len(data[0]) == 2 and len(data[1]) == 2:
self.data = data
else:
raise ValueError("数据必须是4个数字或2x2嵌套列表")
def __repr__(self):
return f"[{self.data[0][0]:>6.2f} {self.data[0][1]:>6.2f}]\n[{self.data[1][0]:>6.2f} {self.data[1][1]:>6.2f}]"
def __add__(self, other):
if not isinstance(other, Matrix2x2):
raise TypeError("只能加另一个2x2矩阵")
result = [
[self.data[0][0] + other.data[0][0], self.data[0][1] + other.data[0][1]],
[self.data[1][0] + other.data[1][0], self.data[1][1] + other.data[1][1]]
]
return Matrix2x2(result)
def __mul__(self, other):
if not isinstance(other, Matrix2x2):
raise TypeError("只能乘另一个2x2矩阵")
a, b = self.data, other.data
result = [
[a[0][0]*b[0][0] + a[0][1]*b[1][0], a[0][0]*b[0][1] + a[0][1]*b[1][1]],
[a[1][0]*b[0][0] + a[1][1]*b[1][0], a[1][0]*b[0][1] + a[1][1]*b[1][1]]
]
return Matrix2x2(result)
def transpose(self):
result = [
[self.data[0][0], self.data[1][0]],
[self.data[0][1], self.data[1][1]]
]
return Matrix2x2(result)
def __eq__(self, other):
return self.data == other.data
# 测试一下
if __name__ == "__main__":
A = Matrix2x2([1, 2, 3, 4])
B = Matrix2x2([5, 6, 7, 8])
print("A + B:")
print(A + B)
print("\nA × B:")
print(A * B)
print("\nA 的转置:")
print(A.transpose())
运行结果:
A + B:
[ 6.00 8.00]
[ 10.00 12.00]
A × B:
[ 19.00 22.00]
[ 43.00 50.00]
A 的转置:
[ 1.00 3.00]
[ 2.00 4.00]
A + B:
[ 6.00 8.00]
[ 10.00 12.00]
A × B:
[ 19.00 22.00]
[ 43.00 50.00]
A 的转置:
[ 1.00 3.00]
[ 2.00 4.00]
4.6 知识结构总览
下面这张图把2x2矩阵类的核心操作串起来了。你看一眼,就能明白每个方法在做什么。
4.7 避坑指南
写矩阵类的时候,有几个地方容易翻车。我一个个说。
- 数据校验不能省:初始化时一定要检查数据格式。我曾经偷懒没校验,结果传了个
[1,2,3]进去,程序跑着跑着就崩了,查了半天才发现是数据少了一个。 - 乘法顺序别搞反:
A * B和B * A结果不同。写代码时脑子里要清楚哪个是左矩阵、哪个是右矩阵。 - 转置不修改原矩阵:我习惯让
transpose()返回新矩阵,而不是原地修改。这样更安全,不会意外改变原始数据。 - 运算符重载要完整:实现了
__add__和__mul__,最好也实现__eq__,不然两个矩阵用==比较会出问题。
扩展思路:这个2x2矩阵类可以轻松扩展到NxN。把
__init__ 改成接收任意维度,用双重循环实现加法和乘法,就是一个通用矩阵类了。我在项目中就是这么干的——先写2x2验证逻辑,再泛化到任意尺寸。