返回首页

Roadmap
更新于 2026年10月25日 全文 1840 字未阅读

第 2.3 节 常见问题#

这一节收集了初学者在 NumPy 取数阶段最常卡的 10 个问题。每个问题都给出直接答案,能跑代码的都附上完整示例。

怎么用这一节 不用通读。遇到问题用 Ctrl+F 搜关键词,直接跳到对应那条。

准备一下数据,下面每题都会用到:

import numpy as np

scores = np.array([80, 90, 75, 100, 60])

matrix = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
])

Q1:NumPy 数组的索引为什么从 0 开始?#

答: 因为 NumPy 沿用了 C 语言的规矩,而 Python 列表也一样。

原因很朴素:下标表示的是「往后数几个」。

  • 第 1 个元素,往后数 0 个 → 下标 0;
  • 第 2 个元素,往后数 1 个 → 下标 1;
  • 第 5 个元素,往后数 4 个 → 下标 4。

这样算起来最省事,NumPy 底层做偏移计算也最快。

那 arr[1] 到底是第 1 个还是第 2 个? 第 2 个。 arr[1] 是「下标为 1 的那个」,也就是第 2 个元素。 建议养成习惯,念的时候说「下标 1」而不是「第 1 个」,就不会绕晕。

print(scores[0])    # 80  ← 第 1 个元素
print(scores[1])    # 90  ← 第 2 个元素

一个必须记住的差别:NumPy 的切片起点包含、终点不包含,和 Python 的 range 一致。

print(scores[1:4])   # [ 90  75 100]  下标 1、2、3

Q2:如何访问二维数组中的指定行、列或单个元素?#

答: 逗号分隔:逗号前面管行,逗号后面管列。

print(matrix[0])         # [1 2 3]      第 0 行,整行
print(matrix[0, 1])      # 2           第 0 行,第 1 列
print(matrix[-1, -1])    # 9           最后一行,最后一列
print(matrix[:, 1])      # [2 5 8]     所有行,第 1 列
print(matrix[1, :])      # [4 5 6]     第 1 行,所有列

记忆方法:: 的意思是「这一维全都要」。

写法意思
matrix[0]第 0 行
matrix[0, 1]第 0 行、第 1 列的那个数
matrix[:, 1]所有行、第 1 列
matrix[1, :]第 1 行、所有列
matrix[:, :]整个数组(等价于 matrix)

别把 matrix[0][1] 和 matrix[0, 1] 搞混。 前者是「先取第 0 行(得到一个新数组),再取它的第 1 个」; 后者是「直接定位到第 0 行第 1 列」。 结果一样,但后者更快,而且对多维数组更清晰。

Q3:如何使用切片提取数组的一部分?#

答: 用 arr[起:止],起点包含、终点不包含。

print(scores[1:4])     # [ 90  75 100]   下标 1、2、3
print(scores[:3])      # [ 80  90  75]   开头到下标 2
print(scores[2:])      # [ 75 100  60]   下标 2 到最后
print(scores[:])       # 全部

省略号 ... 不需要记,只要记住冒号的三种写法:

  • arr[:n] —— 前 n 个;
  • arr[n:] —— 从第 n 个到最后;
  • arr[:] —— 全部(复制一份)。

切片越界不会报错。

print(scores[2:100])   # [ 75 100  60]  只返回能取到的,不报错

但单个索引越界会报错:

print(scores[5])
IndexError: index 5 is out of bounds for axis 0 with size 5

Q4:NumPy 切片中的起始位置、结束位置和步长分别有什么作用?#

答: 完整写法是 arr[起始:结束:步长],三段都能省略。

位置名字作用记法
第 1 段起始位置从哪儿开始含
第 2 段结束位置到哪儿为止不含
第 3 段步长隔几个取一个省略就是 1
print(scores[::2])    # [80 75 60]    隔一个取一个
print(scores[1::2])   # [ 90 100]    从下标 1 开始,隔一个取一个
print(scores[::-1])   # [ 60 100  75  90  80]   倒过来

负数步长就是倒着走,::-1 是最常用的「反转」写法。

二维数组切片也是两个维度各切各的:

print(matrix[0:2, 0:2])    # [[1 2]
                           #  [4 5]]

注意结果的形状会变小:matrix[0:2, 0:2] 的 shape 是 (2, 2),不是 (3, 3)。

Q5:什么是 Fancy Indexing?它与普通切片有什么区别?#

答: Fancy Indexing 就是「用一组下标,一次性取多个元素」——传进去的是列表或数组,不是一个整数或切片。

# 普通切片:取一段连续的
print(scores[1:4])       # [ 90  75 100]

# Fancy Indexing:取任意几个位置
print(scores[[0, 2, 4]])  # [80 75 60]

两个关键区别:

切片 arr[1:4]Fancy Indexing arr[[0, 2, 4]]
取的位置必须连续可以任意乱序、可以不连续
返回的是视图(改了会动原数组)副本(改了原数组不变)
能不能重复取不能能,arr[[0, 0, 1]]
print(scores[[0, 0, 2]])   # [80 80 75]   同一个位置取两次

记住这条就够用了: 切片给你「窗口」(视图),Fancy Indexing 给你「复印件」(副本)。 想清楚自己要的是窗口还是复印件,就不会出错了。

Q6:如何使用整数数组一次性提取多个不连续位置的元素?#

答: 把下标装进一个列表或数组,用 arr[[...]] 取。

# 用列表
print(scores[[0, 2, 4]])        # [80 75 60]

# 用数组也一样
idx = np.array([1, 3])
print(scores[idx])              # [ 90 100]

还能用它「挑行挑列」:

print(matrix[[0, 2]])      # 取第 0 行和第 2 行
[[1 2 3]
 [7 8 9]]

注意 arr[[0, 2]] 和 arr[0, 2] 完全不同!

print(matrix[0, 2])    # 3      ← 第 0 行第 2 列,一个数
print(matrix[[0, 2]])  # 两行  ← 取第 0 行和第 2 行

一个判断方法:看方括号里有几个逗号、逗号两边是不是都用 [ ] 包起来。 [[0, 2]] 是 Fancy Indexing(列表套列表),[0, 2] 是二维索引。

Q7:什么是布尔掩码?如何使用多个条件进行筛选?#

答: 布尔掩码就是先用比较运算算出的一张 True/False 表,再用它当索引筛选。

mask = scores > 75
print(mask)          # [ True  True False  True False]
print(scores[mask])  # [ 80  90 100]

可以直接写在一行里,效果一样:

print(scores[scores > 75])   # [ 80  90 100]

多个条件要用 &(并且)和 |(或者):

print(scores[(scores > 75) & (scores < 95)])    # [80 90]
print(scores[(scores > 90) | (scores < 70)])    # [100  60]
print(scores[~np.isin(scores, [75, 90])])       # [ 80 100  60]

不能用 Python 的 and 和 or!

high = scores > 75
low  = scores < 85

scores[high and low]     # ❌ 会报错
scores[high & low]      # ✅

and / or 是给单个布尔值用的(返回 True/False),而这里的 high、low 是数组, Python 会以为你要做逻辑运算,结果报这个错:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

口诀:数组上的逻辑运算,一律用 & | ~。

三个符号对照:

想要写法说明
并且a & b两个都满足
或者a | b满足一个就行
取反~a把 True/False 互换

Q8:如何提取数组中满足条件的元素及其对应索引?#

答: 用 np.where(),它一次给你位置和元素。

pos = np.where(scores > 85)
print(pos)
(array([1, 3]),)

返回的是一个元组,里面装的是下标数组。二维数组上,返回的下标是二维的:

print(np.where(matrix > 4))
(array([1, 1, 2, 2, 2]), array([1, 2, 0, 1, 2]))

读法:第一个数组是「第几行」,第二个是「第几列」,一一对应。

rows, cols = np.where(matrix > 4)
print(rows)    # [1 1 2 2 2]   行号
print(cols)    # [1 2 0 1 2]   列号

两两配对读:第 1 个满足条件的元素在「第 1 行第 1 列」,第 2 个在「第 1 行第 2 列」……

如果只想知道位置,不要元素,用 np.argwhere() 更直观:

print(np.argwhere(matrix > 4))
[[1 1]
 [1 2]
 [2 0]
 [2 1]
 [2 2]]

一句话记住区别

  • np.where(条件) → 给你「下标」,用来定位;
  • arr[条件] → 给你「元素」,用来取数。
  • np.argwhere(条件) → 给你「坐标对」,最直观。

Q9:为什么修改切片得到的数组可能会影响原数组,而修改高级索引得到的数组通常不会?#

答: 因为切片返回的是「视图」,Fancy Indexing 和布尔掩码返回的是「副本」。

先看现象:

a = np.array([10, 20, 30, 40, 50])

part = a[1:3]        # 切片
part[0] = 999
print(a)
[ 10 999  30  40  50]

**原数组被改了!**换成高级索引:

a = np.array([10, 20, 30, 40, 50])

part = a[[1, 2]]     # Fancy Indexing
part[0] = 999
print(a)
[10 20 30 40 50]

原数组没变。

为什么? NumPy 把数组存在一块连续的内存里。

  • 切片只是「从这块内存里截一段出来看」,不复制数据,所以叫视图(view)——改视图就是改那块内存;
  • Fancy Indexing 和布尔掩码的位置不连续,没法用「一段连续内存」表示,NumPy 必须重新复制一份,所以叫副本(copy)。

怎么确认? 看 .base 属性:

a = np.array([10, 20, 30, 40, 50])
print(a[1:3].base is not None)      # True   ← 是视图
print(a[[1, 2]].base is not None)   # False  ← 是副本

一句话总结 切片是「透过窗户看」,改窗户里的东西,屋里也跟着变; 高级索引是「拍照复印一份」,改复印件跟原件没关系。

Q10:如何避免数组赋值时意外修改原始数据?#

答: 三条路,按场景选。

办法一:想改原数组,就明确写出来。 用 arr[切片] = 值 直接赋值,别绕道中间变量。

a[1:3] = 999

办法二:想要副本,加 .copy()。

r = a[1:3].copy()    # 保险写法
r[0] = 999
print(a)              # 原数组安全

办法三:用 np.array() 也能拿副本。

r = np.array(a[1:3])

最保险的一条:

a[1:3] = a[1:3] * 2     # 取出来、算、再放回去

这种「取出来算完再写回去」的写法,无论中间是视图还是副本,结果都一样, 而且不用动脑子想它到底是不是视图。

小结#

这一节的 10 个问题,其实都围绕**「怎么从数组里精准取数」**:

  1. 下标从 0 开始——下标 = 往后数几个;
  2. 二维用逗号分隔,逗号前管行、后管列,: 表示全都要;
  3. 切片 arr[起:止],起点含、终点不含;
  4. 切片三件套:起始(含)、结束(不含)、步长(隔几个);
  5. Fancy Indexing 用下标列表取任意位置;
  6. arr[[0,2]] 是「取几行」,arr[0,2] 是「第 0 行第 2 列」;
  7. 布尔掩码 + & | ~ 做多条件筛选,别用 and or;
  8. np.where() 给位置,arr[条件] 给元素;
  9. 切片是视图、高级索引是副本——这是「改动会传染」的根源;
  10. 保险做法:.copy(),或「取出来算完写回去」。

下一章预告 现在你能精准取数了。但还有个更厉害的东西等着你——形状完全不同的两个数组,居然能直接相乘相减,NumPy 会自动帮你「补齐」。这就是广播机制:sales * city_factor[:, None] 一行搞定「每个城市乘自己的系数」。第 3 章讲透。

阅读 --