不用NumPy,怎么用Python原生列表实现矩阵乘积

小林子
小林子
发布时间:2026-11-09| 9人浏览过
原创

详细讲解如何在不依赖NumPy的情况下,仅使用Python原生嵌套列表实现二维矩阵乘法。包含维度校验、经典三重循环、zip转置优化及列表推导式实现,并附图解原理与边界测试用例。

不用NumPy,怎么用Python原生列表实现矩阵乘积
图 0:Python 原生列表实现矩阵乘法概述与运算架构
在无第三方依赖的部署环境、嵌入式脚本或纯标准库开发中,无法使用 NumPy 处理线性代数任务。通过 Python 原生嵌套列表(List[List[float]])实现矩阵乘积并不复杂,其核心逻辑在于将第一个矩阵的行向量与第二个矩阵的列向量对齐,并逐项执行对应位置乘加。本文将从矩阵乘法的前提条件与校验机制切入,讲解三重循环基础实现、利用 zip(*B) 转置的优化方案,以及紧凑的列表推导式写法,助你掌握兼具稳健性与可读性的纯 Python 矩阵运算技巧。

一、矩阵乘法成立的前提与输入校验

在线性代数中,两个矩阵能够相乘有严格的前提约束。设矩阵 A 的尺寸为 M × K(即 M 行 K 列),矩阵 B 的尺寸为 K × N(即 K 行 N 列),两者能够相乘的充要条件是:矩阵 A 的列数必须严格等于矩阵 B 的行数。乘积生成的新矩阵 C 尺寸为 M × N。

在 Python 中,原生矩阵通常使用嵌套列表表达,例如 [[1, 2], [3, 4]]。不同于 NumPy 拥有严格的形状(shape)检查机制,原生列表属于动态引用结构,容易出现外层为空、内层某行长度不一(即锯齿状列表)等隐患。在执行乘法前,构建稳固的前置校验能避免后续索引越界(IndexError)。

不用NumPy,怎么用Python原生列表实现矩阵乘积
图 1:矩阵乘法维度匹配机制与规整性检查要点

以下是具备完整防御性检查的校验逻辑实现:

def validate_matrices(A, B):
    """
    校验矩阵 A 与 B 是否符合乘法前提条件
    """
    # 1. 基础非空校验
    if not isinstance(A, list) or not isinstance(B, list):
        raise TypeError("输入矩阵必须为 Python 嵌套列表类型")
    if len(A) == 0 or len(B) == 0:
        raise ValueError("输入矩阵不能为空列表")
    if not isinstance(A[0], list) or not isinstance(B[0], list) or len(A[0]) == 0 or len(B[0]) == 0:
        raise ValueError("矩阵内部不能包含空行")

    # 2. 检查每行长度是否一致(排查锯齿列表)
    k_a = len(A[0])
    for row in A:
        if not isinstance(row, list) or len(row) != k_a:
            raise ValueError("矩阵 A 各行长度不一致,不是合法的规整二维矩阵")

    n_b = len(B[0])
    for row in B:
        if not isinstance(row, list) or len(row) != n_b:
            raise ValueError("矩阵 B 各行长度不一致,不是合法的规整二维矩阵")

    # 3. 维度匹配:A 的列数必须等于 B 的行数
    if k_a != len(B):
        raise ValueError(
            f"矩阵维度不兼容,无法相乘: A 列数为 {k_a},但 B 行数为 {len(B)}"
        )

二、最直观的做法:经典三重嵌套循环

按照数学定义,结果矩阵 C 中的每个元素 C[i][j],都是矩阵 A 的第 i 行与矩阵 B 的第 j 列的点积(内积):

C[i][j] = ∑k=0K-1 A[i][k] × B[k][j]

要实现这个数学逻辑,最自然直观的方式就是使用三重循环:

  • 外层循环 i:遍历矩阵 A 的每一行(范围 0 到 M-1)。
  • 中层循环 j:遍历矩阵 B 的每一列(范围 0 到 N-1)。
  • 内层循环 k:遍历中间公共维度(范围 0 到 K-1),累加乘积求和。
不用NumPy,怎么用Python原生列表实现矩阵乘积
图 2:三重循环中单个元素 C[i][j] 的行与列点积计算拆解

完整代码实现如下:

def matmul_basic(A, B):
    """
    使用原生三重循环实现矩阵乘法
    时间复杂度: O(M * K * N)
    空间复杂度: O(M * N) 用于保存结果
    """
    # 执行维度与格式校验
    validate_matrices(A, B)

    M = len(A)
    K = len(A[0])
    N = len(B[0])

    # 预先分配结果矩阵 C,所有元素初始化为 0
    # 注意:切勿使用 [[0] * N] * M,否则会导致行浅拷贝
    C = [[0.0 for _ in range(N)] for _ in range(M)]

    # 三重嵌套循环计算点积
    for i in range(M):
        for j in range(N):
            current_sum = 0.0
            for k in range(K):
                current_sum += A[i][k] * B[k][j]
            C[i][j] = current_sum

    return C
注意事项:二维列表初始化的浅拷贝陷阱
许多初学者会习惯写 C = [[0] * N] * M。这在 Python 中是极危险的错误![0] * N 只创建了一个列表对象,乘 M 只是把该列表的引用复制了 M 次。修改 C[0][0] 会导致所有行的第 0 列同时被修改。因此必须使用列表推导式 [[0.0 for _ in range(N)] for _ in range(M)] 独立分配每一行。

三、进阶优化:利用 zip(*B) 转置与行行点积

在基础三重循环中,获取矩阵 B 的第 j 列时,代码每次都要通过 B[k][j] 访问。由于 Python 二维列表是“由列表组成的列表”(行优先存储),垂直按列提取元素时,每次循环都需要经过一层外层寻址,效率较低。

如果能把矩阵 B 进行转置(Transpose),将原先的纵向列转换为横向行,那么计算 C[i][j] 就变成了:直接取 A 的第 i 行与转置后 B_T 的第 j 行做列表点积。

在 Python 原生语法中,有一个极其优雅的转置技巧:list(zip(*B))。通过星号解包 *B,将 B 的每一行解包为独立的位置参数传给 zip,zip 每次从各个子列表中提取相同索引的元素,打包为元组,从而完成了瞬间转置。

不用NumPy,怎么用Python原生列表实现矩阵乘积
图 3:基于 zip(*B) 的转置流程与行向量对接机制

优化后的转置点积函数如下:

def matmul_transposed(A, B):
    """
    利用矩阵转置优化的原生矩阵乘法
    """
    validate_matrices(A, B)

    # 利用 zip(*B) 获得转置矩阵 B_T
    # 此时 B_T 的每一行就是原矩阵 B 的每一列
    B_T = list(zip(*B))

    C = []
    for row_a in A:
        row_c = []
        for col_b in B_T:
            # zip 组合两个等长一维列表,在 C 底层完成迭代乘加
            dot_product = sum(x * y for x, y in zip(row_a, col_b))
            row_c.append(dot_product)
        C.append(row_c)

    return C

一行推导式实现

将上述转置逻辑与双层列表推导式结合,可以写出极其凝练的单行实现:

def matmul_comprehension(A, B):
    """单行列表推导式实现矩阵乘法"""
    validate_matrices(A, B)
    B_T = list(zip(*B))
    return [[sum(x * y for x, y in zip(row_a, col_b)) for col_b in B_T] for row_a in A]

列表推导式在字节码层面直接调用 LIST_APPEND,相比手动调用 list.append() 节省了方法查找的开销,不仅简洁,对于中小规模矩阵也是原生实现中执行最快的。

四、完整测试用例与边界验证

为了验证原生实现的准确性,我们设计包含典型矩形矩阵相乘、单位矩阵性质验证以及异常捕获的完整测试脚本:

def run_tests():
    # 测试案例 1:常规非方阵乘法 (2x3 乘 3x2 -> 2x2)
    # A = [ [1, 2, 3],
    #       [4, 5, 6] ]
    # B = [ [7, 8],
    #       [9, 1],
    #       [2, 3] ]
    # C 应为: [ [1*7+2*9+3*2, 1*8+2*1+3*3], [4*7+5*9+6*2, 4*8+5*1+6*3] ]
    #        = [ [31, 19], [85, 55] ]
    mat_a = [
        [1, 2, 3],
        [4, 5, 6]
    ]
    mat_b = [
        [7, 8],
        [9, 1],
        [2, 3]
    ]

    res_basic = matmul_basic(mat_a, mat_b)
    res_trans = matmul_transposed(mat_a, mat_b)
    res_comp = matmul_comprehension(mat_a, mat_b)

    expected = [
        [31, 19],
        [85, 55]
    ]

    print("基础三重循环输出:", res_basic)
    print("转置优化输出:    ", res_trans)
    print("列表推导式输出:  ", res_comp)
    assert res_basic == expected, "基础实现结果不匹配"
    assert res_trans == expected, "转置实现结果不匹配"
    assert res_comp == expected, "推导式实现结果不匹配"
    print("✓ 测试案例 1 验证通过!\n")

    # 测试案例 2:单位矩阵测试 (A * I == A)
    identity = [
        [1, 0, 0],
        [0, 1, 0],
        [0, 0, 1]
    ]
    res_identity = matmul_transposed(mat_a, identity)
    assert res_identity == mat_a, "单位矩阵相乘后不等于原矩阵"
    print("✓ 测试案例 2 (单位矩阵验证) 通过!\n")

    # 测试案例 3:异常维度捕获
    incompatible_b = [[1, 2], [3, 4]]  # 2x2 无法与 2x3 相乘
    try:
        matmul_basic(mat_a, incompatible_b)
        print("✗ 异常捕获失败")
    except ValueError as e:
        print("✓ 测试案例 3 (异常维度拦截) 正确触发异常:", e)

if __name__ == "__main__":
    run_tests()
示例运行输出
基础三重循环输出: [[31.0, 19.0], [85.0, 55.0]]
转置优化输出: [[31, 19], [85, 55]]
列表推导式输出: [[31, 19], [85, 55]]
✓ 测试案例 1 验证通过!

✓ 测试案例 2 (单位矩阵验证) 通过!

✓ 测试案例 3 (异常维度拦截) 正确触发异常: 矩阵维度不兼容,无法相乘: A 列数为 3,但 B 行数为 2

五、方案对比与原生实现的使用边界

在实际工程中,为什么我们在很多科学计算场景中离不开 NumPy,但又常常需要在纯 Python 环境下编写原生实现?这源于两者在架构理念和依赖成本上的不同。

不用NumPy,怎么用Python原生列表实现矩阵乘积
图 4:原生实现与 NumPy 技术方案对比与选型指南

原生列表方案的核心优势在于零外部依赖、极低的环境启动成本与完全的代码透明度:

  • 适用场景:AWS Lambda 等无服务器函数冷启动、轻量级命令行工具、微型配置处理、高校算法课程学习与演示、规模在 100 × 100 以内的中小矩阵变换。
  • 性能瓶颈:Python 原生对象具有指针包装与动态类型检查成本,数据无法在 CPU 缓存中连续存放;而 NumPy 内部依托底层的 C/Fortran 连续内存数组与 BLAS/LAPACK 库(如 OpenBLAS、Intel MKL),支持多核并行与 SIMD 向量化指令集。

理解了矩阵乘法的本质在于“行与列的点乘”,在缺少第三方库的场景下,熟练使用 list(zip(*B)) 转置配合列表推导式,便能写出既优雅又高效的 Python 矩阵运算代码。

相关专题:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。