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]

4.6 知识结构总览

下面这张图把2x2矩阵类的核心操作串起来了。你看一眼,就能明白每个方法在做什么。

2x2 矩阵类核心操作 Matrix2x2 类 加法 __add__ 乘法 __mul__ 转置 transpose 操作说明: • 加法:对应位置元素相加,要求两个矩阵维度相同 • 乘法:左矩阵的行 × 右矩阵的列,不满足交换律 • 转置:行变列、列变行,2x2 转置后仍是 2x2 • 所有操作返回新矩阵,不修改原矩阵(不可变风格)

4.7 避坑指南

写矩阵类的时候,有几个地方容易翻车。我一个个说。

  • 数据校验不能省:初始化时一定要检查数据格式。我曾经偷懒没校验,结果传了个 [1,2,3] 进去,程序跑着跑着就崩了,查了半天才发现是数据少了一个。
  • 乘法顺序别搞反:A * B 和 B * A 结果不同。写代码时脑子里要清楚哪个是左矩阵、哪个是右矩阵。
  • 转置不修改原矩阵:我习惯让 transpose() 返回新矩阵,而不是原地修改。这样更安全,不会意外改变原始数据。
  • 运算符重载要完整:实现了 __add__ 和 __mul__,最好也实现 __eq__,不然两个矩阵用 == 比较会出问题。
扩展思路:这个2x2矩阵类可以轻松扩展到NxN。把 __init__ 改成接收任意维度,用双重循环实现加法和乘法,就是一个通用矩阵类了。我在项目中就是这么干的——先写2x2验证逻辑,再泛化到任意尺寸。