☰
Numpy入门:吃透数组形状与轴,打通广播和矩阵运算
2026/10/3 7:57:10 网站建设 项目流程

早就该写一篇这样的Numpy入门了。网上Numpy教程多如牛毛,但绝大多数要么罗列API让人记不住,要么一上来就甩线性代数公式直接劝退。我陆陆续续带过不少新人,发现大家学到一半卡住的地方出奇一致:不是数组创建和运算不会,而是根本不懂数组的“形状”和“轴”到底怎么回事,后面学广播、学矩阵运算就全懵了。所以这篇不会逐条抄官方文档,而是按一个正常的学习路径来拆——每一步都告诉你为什么要这样用,踩过的坑也一并标出来。如果你是刚接触Python、被数据分析或机器学习课程逼着学Numpy,或者之前零零散散看过一点但一直没打通,这篇可以帮你把短板一次补齐。

1. 环境安装与第一个数组:比想象中简单,但坑也不少

1.1 安装最稳妥的方式

先说安装。无论你是Windows、macOS还是Ubuntu,最省心的做法都是用pip装:

pip install numpy

如果你用的是Anaconda环境,那大概率已经内置了Numpy,打开终端输入python再执行import numpy as np,不报错就直接用。需要注意的是Linux发行版之间差异很大,Ubuntu系统上如果直接用apt install python3-numpy,装出来的版本可能偏旧,跟我后面讲的某些新特性对不上。所以统一建议用pip装,并且建议在虚拟环境里装,避免搞乱系统Python。

装好后验证一下版本:

import numpy as np print(np.__version__)

正常会输出类似2.2.5这样的版本号。如果你用的是旧代码库,大概率会碰到AttributeError: module 'numpy' has no attribute 'float'这个报错。这背后的原因是Numpy 1.24之后把np.float、np.int这些Python原生类型的别名给移除了,老代码里的np.float已经失效。解决办法很简单——直接把np.float改成float,或者改成np.float64。

另外一个很常见的报错长这样:

UserWarning: Failed to initialize NumPy: No module named 'numpy'

别急着怀疑Numpy本身,这个提示几乎都是因为当前Python解释器和安装Numpy的解释器不是同一个。比如你在系统Python里装好了库,但用PyCharm时却选了一个独立的虚拟环境,就会这样。处理方式就是确认解释器路径,再用该解释器对应的pip重新安装一次。

1.2 创建你的第一个数组

装好之后,先建立对数组的基本感知。Numpy里最核心的数据结构是ndarray,也就是N维数组。你可以把它理解成一个高性能的、有形状的容器,里面的所有元素必须是同一种数据类型。

import numpy as np a = np.array([1, 2, 3, 4, 5]) print(a) print(type(a)) # <class 'numpy.ndarray'> print(a.shape) # (5,) print(a.dtype) # int64 或 int32,取决于系统

这里shape返回的就是数组的形状,一维数组显示(5,),别漏掉后面那个逗号——它是用来区分二维(1,5)的,后面你会经常跟它打交道。dtype是元素类型,Numpy对类型非常较真,不同类型之间运算时有一套自动转换规则,以后会用到。

多维数组的创建方式类似:

b = np.array([[1, 2, 3], [4, 5, 6]]) print(b.shape) # (2, 3)

两行三列,一切正常。到这一步,你已经知道Numpy数组长什么样子了。接下来要做的,是掌握各种创建数组的方式——因为后面你从文件、网络、数据库拿到的数据,一律都要先变成数组才能进入计算环节,创建这一步的效率直接影响后续所有操作的效率。

2. 创建数组的方式决定你后面顺不顺手

2.1 zeros、ones、eye与空数组

很多场景下你需要先“挖坑”再填数据。比如初始化一个全零的矩阵来累加结果,或者生成一个单位矩阵做矩阵运算。Numpy提供了非常直白的函数:

z = np.zeros((3, 4)) # 3行4列的全零数组 o = np.ones((2, 3)) # 2行3列的全1数组 e = np.eye(4) # 4x4单位矩阵

需要留个心眼的地方在于np.zeros((3, 4))里面那层括号。如果写成np.zeros(3, 4),它会认为你想创建一个形状是3的数组,但第二个参数被当成了dtype,直接报错。我刚学的时候这个错犯过好几次,现在仍然经常在答疑群里看到有人问,说明这个细节非常容易踩。

还有一个函数需要特别提防:np.empty。

empty_arr = np.empty((2, 2))

它不会把内存清零,而是直接返回一段未初始化的内存,里面的值是你根本猜不到的垃圾数据。为什么会有这种设计?为了性能。有些场景你马上就要循环里逐元素赋值,那初始化清零是纯浪费;但如果你初始化完忘了给它赋值,等到后面使用时就可能复制出莫名其妙的结果,而且这种Bug非常隐蔽,因为数据不一定每次都不对。所以我的建议是:拿不准自己会不会漏赋值,就老老实实用zeros。

2.2 arange与linspace:生成序列的两大类需求

数据处理里最常见的需求之一就是生成序列。Numpy有两个高频函数,但它们的用途完全不同:

  • np.arange(start, stop, step):按步长生成整数或浮点数序列,步长可以不是整数。
  • np.linspace(start, stop, num):在闭区间[start, stop]内生成num个均匀分布的数,终点一定包含。

看个例子就明白:

arr1 = np.arange(0, 10, 2) # [0 2 4 6 8] arr2 = np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ]

arange是左闭右开,跟Python原生range一致,所以0到10步长2,最大值是8。而linspace是闭区间,所以0到1分成5个点,最后一个一定是1。

这里有个非常重要的坑:不要用arange生成浮点数序列。原因在于浮点数在计算机里的表示天生不精确,np.arange(0, 1, 0.1)返回的数据末尾可能不是0.9而是0.8999999999999999,并且元素个数可能跟你预期不一样。只要你需要的是“固定个数、均匀分布”,一律用linspace,谁用谁知道。

绘图时linspace也几乎是标配。比如用Matplotlib画正弦波时:

import matplotlib.pyplot as plt t = np.linspace(0, 2 * np.pi, 100) y = np.sin(t) plt.plot(t, y)

这里100个点覆盖一个完整周期,曲线看起来就平滑了。如果你用过Excel的画图功能,就能类比出来:横坐标的密度决定曲线的平滑程度,Numpy在这里扮演的角色就是生成这些密集的横坐标。

2.3 随机数数组:模型实验的数据源头

很多场景下我们需要仿真数据或初始化神经网络的权重,这时候就要用到np.random模块。

r1 = np.random.rand(3, 2) # 均匀分布 [0, 1),3行2列 r2 = np.random.randn(3, 2) # 标准正态分布 r3 = np.random.randint(0, 10, size=(3, 2)) # 整数均匀分布 [0, 10)

注意np.random.rand和np.random.randn传入的参数直接就是维度,不需要加括号,但np.random.randint需要显式写size=。这些不一致在Numpy的老接口里很常见,记的时候多留个神就好。

为了让结果可复现,通常还要设置随机种子:

np.random.seed(42)

后面再跑代码,无论执行多少次,生成的随机数都一样。这对于调试和写教程非常关键——不然每次运行结果都不一样,就很难判断代码到底是修好了还是瞎碰碰对的。

2.4 reshape、ravel与dtype转换

创建完数组,下一步往往是改形状。reshape可能是你用得最频繁的方法之一:

a = np.arange(12) b = a.reshape(3, 4) # 变成3行4列

reshape返回的是原数组的一个视图,而不是数据拷贝。也就是说,你改b里的元素,a也会跟着变。如果你要的是独立的数据,必须显式调用.copy():

c = a.reshape(3, 4).copy()

这个“视图和拷贝”的区别,是新手最容易踩的暗坑。排查半天数据怎么被改了,最后发现是视图引用在作祟。后面讲到索引的时候还会再碰到它。

如果要把多维数组展平成一维,可以用ravel()或flatten()两者。区别在于ravel返回视图(修改会影响原数组),而flatten返回拷贝(修改不影响原数组)。从内存效率上看ravel更优,但如果你后续要修改展平后的数据且不想动原始数组,就得用flatten。

数据的类型转换也是高频操作,比如从文件里读到的是整数,但计算均值时需要浮点数:

a = np.array([1, 2, 3]) b = a.astype(np.float64)

astype会生成一个新数组,不会原地修改原始数组,所以记得把结果接收下来。

3. 索引与切片:从“真难用”到“真香”

3.1 一维数组索引:左闭右开是唯一原则

一维数组的索引和列表几乎一模一样,唯一需要死死记住的就是:切片是左闭右开。

a = np.arange(10) # a: [0 1 2 3 4 5 6 7 8 9] print(a[2]) # 2 print(a[2:5]) # [2 3 4] print(a[:4]) # [0 1 2 3] print(a[::2]) # [0 2 4 6 8] print(a[::-1]) # [9 8 7 6 5 4 3 2 1 0]

最后一个[::-1]是逆序操作,写起来爽,读起来也直观。很多算法题里有用到它来反转数组的,效率比循环高太多。

3.2 二维数组索引:第一个轴是行,第二个轴是列

二维数组的索引规则是a[行, 列],中间用逗号隔开:

a = np.array([[1, 2, 3], [4, 5, 6]]) print(a[0, 1]) # 2,第0行第1列 print(a[1]) # [4 5 6],第1行整行 print(a[:, 1]) # [2 5],第1列整列 print(a[0:2, 1:3]) # [[2 3], [5 6]],子矩阵

这里的冒号有“全部”的意思。a[:, 1]意思是所有行、第1列,所以取出来的是整列。等号赋值也同样适用:

a[:, 1] = 0 # 第1列变为0

这种操作在数据清洗中非常常见,但必须注意:对切片的赋值会直接修改原数组,因为它本质上是一个视图。如果你不想改原始数据,先.copy()出来再改。

3.3 花式索引与布尔索引:数据筛选的两大利器

除了冒号切片,还有两种高级索引方式在真实项目中几乎天天用。

花式索引:传入一个整数列表或数组,按位置取值。

a = np.array([10, 20, 30, 40, 50]) indices = [0, 2, 4] print(a[indices]) # [10 30 50]

布尔索引:传入一个布尔数组,条件为True的位置留下,False的位置丢弃。这是Numpy最强大的功能之一。

a = np.array([1, 2, 3, 4, 5]) mask = a > 3 print(mask) # [False False False True True] print(a[mask]) # [4 5]

甚至可以连续写:

print(a[(a > 2) & (a % 2 == 0)]) # [4],大于2且是偶数

注意一点:在Numpy里,逻辑“且”必须用&,逻辑“或”用|,并且必须给每个条件加括号。直接用Python的and或者or会直接报错,因为Numpy没法对一个数组做真值判断。这个报错信息是“The truth value of an array with more than one element is ambiguous”,看到它不要慌,检查一下是不是条件没有加括号。

3.4 axis这个参数到底什么含义

说到二维数组,就必须讲axis。这是Numpy新人最容易卡住的概念,但用一个生活化的类比就能讲清楚。

把二维数组想象成一张Excel表格:

  • axis=0代表“沿着行的方向运动”,也就是跨行操作,结果是每一列的一个汇总。
  • axis=1代表“沿着列的方向运动”,也就是跨列操作,结果是每一行的一个汇总。

看代码:

a = np.array([[1, 2, 3], [4, 5, 6]]) print(a.sum(axis=0)) # [5 7 9],每一列求和 print(a.sum(axis=1)) # [ 6 15],每一行求和

axis=0时,第一列的1+4=5,第二列的2+5=7,第三列的3+6=9。axis=1时,第一行的1+2+3=6,第二行的4+5+6=15。

高维数组的axis更大,原则一样:axis=n就是对第n个维度方向做操作。三维数组里axis=0往往代表“对每个通道分别操作”,比如图像数组的形状是(高度, 宽度, 通道数),想按通道做归一化就会用到axis=2。

4. 广播机制:最反直觉也最省心的功能

4.1 为什么形状不同的数组还能运算

假设你要给数组的每一行都加上同一个向量。最笨的办法是用循环,但Numpy有更好的方式:

a = np.array([[1, 2, 3], [4, 5, 6]]) b = np.array([10, 20, 30]) print(a + b) # [[11 22 33] # [14 25 36]]

形状是(2, 3)的数组加形状是(3,)的数组,Numpy自动把b“拉伸”成与a相同的形状再运算。这个过程叫做广播。

再比如缩放:

print(a * 2) # [[ 2 4 6] # [ 8 10 12]]

标量2被广播到每一个元素上。这种写法不仅代码简洁,而且运行效率远高于Python循环——Numpy的底层C语言实现会把这些操作向量化,避免Python解释器的循环开销。

4.2 广播到底怎么判断能不能用

广播的判断规则其实不复杂,两条:

  1. 从最后一个维度往前比对。
  2. 如果两个维度相等,继续往前比;如果其中一个维度是1,则拉伸这个维度到另一个维度的大小;如果两个都不相等且都不为1,就无法广播。

看几个例子:

a = np.zeros((3, 4)) b = np.ones((4,)) a + b # 可以,b的形状(4,)与a的最后一个维度相等 c = np.ones((3, 1)) a + c # 可以,(3,1)从后往前分别是1和4,维度1被广播成4,然后是3和3相等 d = np.ones((5,)) a + d # 报错,(5,)与(4,)最后一个维度不相等

第三个例子是新手最常犯的错。想给a的每一列加同一个向量?这跟想象中不一样,列的方向对应的维度是0而不是1,所以直接加(5,)形状的向量会错得莫名其妙。正确的做法是用b[:, np.newaxis]先把形状从(5,)变成(5, 1),再参与广播。

d = np.ones((4,)) e = d[:, np.newaxis] # 形状变成 (4, 1) a + e # 可以

np.newaxis这个名字可能有点吓人,你完全可以把它理解成“在这个位置插入一个长度为1的新维度”。它是理解广播和维度操作的一把钥匙,花几分钟把它弄明白,后面学深度学习时也能少掉不少头发。

4.3 广播带来的隐式Bug

广播虽然省事,但它也会悄悄掩盖你的错误。比如你打算给每一列减均值,结果代码写成了对每一行减均值,Numpy不会报错,数据也“计算”完了,但结果完全不是你要的。这种Bug特别难发现,因为程序不会崩,打印出来数字也像模像样。

我的习惯是:做任何带广播的高维运算前,先打印出参与运算的数组的shape确认一遍。代价很小,但能省掉大量排查时间。另一个好习惯是给关键变量起名时带上形状信息,比如counts_3x4,一看到变量名就知道它的尺寸,心里有底。

5. 数组运算、聚合与线性代数:把“循环”改成“量子力学”

5.1 ufunc:矢量化运算到底比循环快多少

Numpy的数学运算全部基于通用函数(ufunc),意思是它对数组里的每个元素做同样操作,并且是底层用C语言实现的。你写a + b时,Numpy会直接操作连续内存块,而不像Python循环那样一个元素一个元素地解释执行。

拿计算平方来说:

a = np.arange(1000000) %timeit a ** 2 %timeit [x ** 2 for x in a]

实测下来,矢量化运算通常比Python循环快几十倍到上百倍。为什么?因为Python是动态类型语言,解释器每执行一次x**2都要做一堆类型检查;而Numpy把整个操作推给C层,一次跑完。理解了这一层,你就明白了为什么所有Python科学计算和机器学习库的底层都是Numpy——它相当于是Python的“速度外挂”。

常见的ufunc包括:

  • 算术运算:+ - * / // % **
  • 比较运算:> < >= <= == !=
  • 数学函数:np.abs, np.sqrt, np.exp, np.log, np.sin, np.cos

它们的行为都一样:对一个数组逐元素操作,返回一个新数组。

5.2 统计聚合:数据概览的最快入口

拿到一堆数据,第一反应往往是想知道它的均值、方差、最大值、最小值。Numpy把这套聚合操作做成数组方法了。

data = np.array([1, 2, 3, 4, 5]) print(data.mean()) # 3.0 print(data.sum()) # 15 print(data.max()) # 5 print(data.min()) # 1 print(data.std()) # 1.41421356... print(data.argmax()) # 4,最大值所在位置

上面这些方法几乎都支持axis参数,用来控制是按行还是按列聚合。对于二维数据,data.mean(axis=0)是每一列的平均数,data.mean(axis=1)是每一行的平均数。这个在前面将axis时已经演练过了,原理完全一致。

还有几个特殊的聚合函数需要认识一下:

a = np.array([[1, np.nan, 3], [4, 5, 6]]) print(np.nansum(a)) # 19,忽略NaN后求和 print(np.nanmean(a)) # 3.8,忽略NaN后求平均

NaN在Numpy里代表缺失值,直接对它求和或平均,结果永远是NaN,因为数学上含缺失值的运算结果就是未知。np.nan*系列函数会让你在数据有缺失时也能继续计算,但前提是你明确知道缺失值不影响你要的分析目标。如果缺失值本身就有含义(比如“未统计到”和“数值为零”是两种完全不同的业务状态),那处理方式就又不一样了。

5.3 线性代数:Numpy里藏着一个MATLAB

Numpy的np.linalg模块提供了几乎所有常用的线性代数运算,机器学习公式里那些矩阵乘法、转置、求逆,全在这里。

矩阵乘法用@或np.dot:

A = np.array([[1, 2], [3, 4]]) B = np.array([[5, 6], [7, 8]]) C = A @ B # [[19 22] # [43 50]]

注意*是逐元素乘法,也就是Hadamard积,跟矩阵乘法完全不是一回事。这一步无数人踩坑:A * B是逐元素相乘,A @ B才是线性代数里的矩阵乘法。我刚带人入门时,每次都要强调这一点的原因。

转置、求逆、行列式、特征值也都是高频操作:

print(A.T) # 转置 print(np.linalg.inv(A)) # 逆矩阵 print(np.linalg.det(A)) # 行列式 print(np.linalg.eig(A)) # 特征值和特征向量

矩阵乘法在神经网络前向传播里就是权重矩阵 @ 输入向量,在图像处理里就是变换矩阵作用在坐标上。这一块是Numpy和机器学习库之间衔接最紧密的地方,看不懂公式的时候,先手动算一遍小矩阵就都通了。

6. 文件读写与数据清洗:让Numpy真正干活

6.1 保存与加载数组

把计算结果保存下来,下次直接加载,能省去大量重复计算时间。

np.save('data.npy', array) loaded = np.load('data.npy')

如果想一次保存多个数组,可以用np.savez或np.savez_compressed:

np.savez('data.npz', a=a, b=b) data = np.load('data.npz') print(data['a']) print(data['b'])

这比用Python原生pickle保存Numpy数组要高效得多,因为它保留的是二进制内存布局,加载时不需要做任何转换。

普通文本文件则用np.loadtxt和np.savetxt,适合与Excel或CSV交互:

np.savetxt('data.csv', array, delimiter=',') loaded_csv = np.loadtxt('data.csv', delimiter=',')

遇到带有表头或缺失值的CSV,loadtxt会显得吃力,这时候不妨直接用np.genfromtxt,它可以处理缺失值和指定列的数据类型。不过说实话,遇到复杂CSV我更推荐直接上Pandas,Numpy负责数学部分,Pandas负责表结构部分,各司其职。

6.2 一个能直接抄的数据清洗范例

下面做个综合性小演练,把前面讲过的功能串起来。假设你有一个包含成绩的二维数组,行是学生,列是科目,其中部分成绩缺失用np.nan表示:

scores = np.array([ [90, 85, np.nan, 78], [88, np.nan, 92, 80], [75, 90, 85, np.nan], [np.nan, 82, 88, 91] ])

第一步,看看缺失值分布:

missing_mask = np.isnan(scores) print(missing_mask.sum()) # 缺失值总数 print(np.where(missing_mask)) # 缺失值的具体位置

第二步,决定填充策略。最简单的是用列均值填充:

col_means = np.nanmean(scores, axis=0) for col in range(scores.shape[1]): mask_col = np.isnan(scores[:, col]) scores[mask_col, col] = col_means[col]

填充完后,计算每个学生的平均分、每门课的平均分:

student_avg = scores.mean(axis=1) course_avg = scores.mean(axis=0)

找出有没有不及格的科目:

failed = np.any(scores < 60, axis=1) print(failed)

最后把清洗好的数据保存:

np.save("cleaned_scores.npy", scores)

这个流程虽然短,但里面揉进了np.isnan、np.where、np.nanmean、布尔索引、axis、条件过滤、文件保存等一整套必备技能。你能独立跑通这个演练,Numpy就算是入门成功了。

6.3 报错排查手册

最后聊几个高频报错,也算是一份速查表:

报错信息原因解决办法
module 'numpy' has no attribute 'float'Numpy 1.24+移除了np.float等别名改成float或np.float64
Failed to initialize NumPyPython解释器和Numpy环境不匹配检查解释器路径,用对应pip重装
The truth value of an array is ambiguous对数组用了布尔判断(if arr:)用any()或all(),或用&/`
operands could not be broadcast together广播规则不满足检查两边shape,用reshape或newaxis对齐
index 5 is out of bounds索引超出数组范围检查shape,确认索引在0 ~ shape-1之间
setting an array element with a sequence给数组某个位置塞了形状不匹配的对象用astype或np.array(..., dtype=...)统一类型

遇到报错时,先看的是shape,再查dtype,最后才怀疑算法逻辑。这个排查顺序能帮你少走很多弯路。

7. 从Numpy走向科学计算生态:新一代的图景

7.1 Numpy在数据科学生态中的位置

Numpy本身解决了数值计算的核心问题,但单打独斗的场景并不多。它更常见的是作为底层引擎被Pandas、Matplotlib、Scikit-learn、TensorFlow、PyTorch等库调用。

举个例子,用Pandas读一份CSV,Pandas底层会把数据存成Numpy数组,你用df.values就能拿到这个数组;然后你用Matplotlib画图,无论是散点图、折线图还是热力图,传进去的仍然是Numpy数组;再拿去做机器学习,Scikit-learn接口里约定的输入格式就是二维Numpy数组。可以说,你只要在Python里做数据处理这一行,就绕不开Numpy。

所以学Numpy时,眼光要放远一点:它能干的不只是算个均值方差,而是整个Python科学计算体系的“地基”。但反过来,初学阶段先别贪多,把最核心的数组操作(创建、索引、变形、聚合、广播、矩阵乘法)弄扎实,后面切换到Pandas时你会发现很多东西就是换个马甲。

7.2 值得注意的新趋势

Numpy 2.x出来之后,有一些值得关注的改动。比如API更干净了,删掉了一批历史遗留的别名,也提升了部分操作的性能。如果你之前一直在用1.x版本,升级后老代码可能会碰壁,建议升级前先把官方迁移指南过一遍。

但说实话,Numpy这种基础库的好处就是:核心用法十年如一日。只要掌握了思维方式和核心API,无论版本怎么变,你花在这上面的时间都不会浪费。

还有一个趋势是和其他库的融合越来越深。Numba可以加速你的Numpy代码,Dask可以并行处理超大数据集,CuPy用GPU替代CPU来跑Numpy的API。这些进阶工具的共同点都是把Numpy的数组接口当成标准,所以即使将来你开始涉足大数据、高性能计算或者深度学习,现在学的Numpy照样是基石。

8. 入门之后,怎样练才算真正掌握

8.1 动手顺序与学习节奏

如果这篇教程你从头读到这里,说明你已经有了一整套知识骨架。接下来别急着往下学Pandas,先花几天时间刷一刷基础练习。我推荐的刷题顺序是:

  1. 创建数组:手动创建各种形状的数组(标量、向量、矩阵),打印shape。
  2. 索引切片:用切片、花式索引、布尔索引提取目标数据。
  3. 形状变换:reshape、ravel、np.newaxis、转置混着用。
  4. 聚合与广播:对二维数组按行按列求均值;自己构造一个能广播和不能广播的例子。
  5. 矩阵运算:手算一个2x2矩阵的乘法,再用Numpy验证。

这个顺序对应了大脑建立空间认知的过程:先感受“形状”,再用“形状”去理解运算,最后用运算去解决问题。

8.2 值得收藏的查漏补缺工具

如果你发现某些概念还是模糊,最快的查漏方式是用官方文档的Quickstart教程,把它从头到尾跑一遍。它的例子虽然简单,但是覆盖面很全。另一个实用的技巧是:在Jupyter Notebook里用np.info(np.函数名)查看函数的文档和示例,非常方便。

再有就是看别人的代码。GitHub上大量的数据处理项目、Kaggle上的开源Notebook,里面Numpy的使用方式非常实务。你看到看不懂的地方,就去文档里查——带着问题学比漫无目的地刷文档高效得多。

8.3 一些个人的心得体会

带新人这几年,我发现Numpy入门真正要迈过的坎不是语法,而是思维方式的转变。刚写Python时,你习惯用循环处理数据,做一件事脑子里先想到“遍历”;学了Numpy之后,这种习惯要改过来——先想到“整体操作”,再想到“批量计算”。第一批数据“全部加一”“每列求均值”“筛选大于阈值的所有行”,这些操作在Numpy里都是一句代码的事,不需要任何循环。

这种思维转换一开始会有点别扭,因为人脑天然倾向于一步步执行。但往深了说,学会“用整体代替局部、用声明代替循环”,不仅是在学Numpy,也是在建立一种更接近计算机底层计算模式的思维方式。

从一个实际经历说起吧。我前段时间帮人排查一段数据处理脚本,对方用循环处理了一万行数据,跑了将近半分钟;我用广播和布尔索引改写后,耗时降到零点零几秒。那瞬间对方说了句“原来Numpy是这样用的”。其实这哪是什么高深技巧,就是对Numpy最基本的理解而已。之所以想写这篇入门,就是希望更多人能早一点体会到这种感觉——原来困扰你的性能问题、代码冗长问题,往往只是因为你还没切换到Numpy的思考方式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询