第 3 章 索引与切片
学习目标
- 掌握一维数组的正向与负向索引
- 掌握一维切片
start:stop:step - 掌握二维数组的行、列、子块索引
- 理解「切片是视图」,能区分视图与副本
3.1 一维索引
索引是用下标取出数组中的单个元素。下标从 0 开始;负下标从末尾倒数,-1 是最后一个元素:
python
import numpy as np
a = np.array([10, 20, 30, 40, 50])
print(a[0]) # 第一个
print(a[2]) # 第三个
print(a[-1]) # 最后一个
print(a[-2]) # 倒数第二个输出:
10
30
50
40越界访问会报 IndexError:
>>> a[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: index 5 is out of bounds for axis 0 with size 5解读:axis 0(第 0 轴)上数组大小是 5,下标 5 越界。合法下标是 0~4(或 -1~-5)。
3.2 一维切片
切片用 数组[起始:结束:步长] 取出一段连续(或按步长跳跃)的元素。规则与 Python 列表一致:
结束不包含起始省略表示从头,结束省略表示到尾步长省略表示 1
python
import numpy as np
a = np.array([10, 20, 30, 40, 50])
print(a[1:4]) # 下标 1 到 3
print(a[:3]) # 前 3 个
print(a[2:]) # 从下标 2 到最后
print(a[::2]) # 隔一个取一个
print(a[::-1]) # 反转输出:
[20 30 40]
[10 20 30]
[30 40 50]
[10 30 50]
[50 40 30 20 10]步长为负数时方向反转:[::-1] 是倒序,[7:2:-2] 从下标 7 往回取到下标 3(不含 2),步长 -2:
python
import numpy as np
print(np.arange(10)[7:2:-2]) # 7, 5, 3输出:
[7 5 3]3.3 二维索引
二维数组 a[r, c]:第一个下标是行,第二个是列。省略第二个下标取整行。
python
import numpy as np
b = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(b[0]) # 第 0 行
print(b[0, 1]) # 第 0 行第 1 列 → 2
print(b[1, 1]) # 第 1 行第 1 列 → 5
print(b[:, 1]) # 所有行的第 1 列
print(b[1:, :2]) # 第 1 行起,前 2 列输出:
[1 2 3]
2
5
[2 5 8]
[[4 5]
[7 8]]重点理解 b[:, 1]:冒号在行的位置表示「所有行」,1 在列的位置表示第 1 列,合起来就是「所有行的第 1 列」——取出整列。这是二维数组最常用的索引写法之一。
3.4 切片是视图,不是副本
这是 NumPy 与 Python 列表最重要的区别之一。
视图(view)是原数组的一块「窗口」:切片得到的新数组共享底层数据。修改视图,原数组跟着变。
python
import numpy as np
a = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
sub = a[2:5] # 视图:指向 a 的 2~4 号元素
print(sub)
sub[0] = 99 # 改视图
print(a) # 原数组也被改了!输出:
[2 3 4]
[ 0 1 99 3 4 5 6 7 8 9]sub[0] 与 a[2] 是同一块内存。视图机制让切片几乎零成本(不用复制数据),但初学者常被「改切片影响了原数组」吓到。规则只有一条:凡是用 切片 或 整数下标+切片 取出来的都是视图;显式调用 .copy() 才得到独立副本。
二维同理:
python
import numpy as np
m = np.arange(12).reshape(3, 4)
row = m[1] # 第 1 行,是视图
row[0] = 99
print(m)输出:
[[ 0 1 2 3]
[99 5 6 7]
[ 8 9 10 11]]需要独立副本时:
python
import numpy as np
a = np.arange(10)
c = a[2:5].copy() # 独立副本
c[0] = -1
print(a) # 原数组不变
print(c)输出:
[0 1 2 3 4 5 6 7 8 9]
[-1 3 4]3.5 用切片赋值
既然切片是视图,给它赋值就是「批量修改原数组的对应位置」:
python
import numpy as np
m = np.arange(12).reshape(3, 4)
m[:, 1] = 0 # 第 1 列全部置 0
print(m)输出:
[[ 0 0 2 3]
[ 4 0 6 7]
[ 8 0 10 11]]这是数据清洗里最常用的操作之一:批量把某一列(或某一段)替换成固定值。
3.6 花式索引与布尔索引(预告)
除了下标和切片,NumPy 还支持两类更强大的索引,先混个脸熟,细节分别在后续章节展开:
- 花式索引:用整数数组当下标,取不连续的元素,得到的是副本(不是视图):
python
import numpy as np
a = np.arange(10)
idx = np.array([0, 2, 4])
print(a[idx])输出:
[0 2 4]- 布尔索引:用布尔数组过滤,留下
True对应的元素(第 8 章正式讲):
python
import numpy as np
a = np.array([10, 20, 30, 40, 50])
print(a[a > 30])输出:
[40 50]动手实践
- 创建
np.arange(20),用切片取出:前 5 个、后 5 个、偶数下标元素、倒序。 - 创建 4×4 数组
np.arange(16).reshape(4, 4),分别取出:第 2 行、第 3 列、2~3 行、右上角 2×2 子块。 - 验证视图:取一行切片并修改它,打印原数组,确认被改动;再用
.copy()重复一次,确认不被改动。 - 用切片赋值把 4×4 数组的对角线全部置为 0(提示:对角线元素是
[0,0]、[1,1]、[2,2]、[3,3],可以用np.arange(4)做行下标与列下标)。
常见错误
| 错误写法 | 现象 | 原因 |
|---|---|---|
a[5](数组长度 5) | IndexError: index 5 is out of bounds | 合法下标 0~4;越界了 |
a[1:4] 期望包含下标 4 | 结果不含 a[4] | 切片 结束 不包含 |
| 修改切片后惊讶「原数组变了」 | 原数组被连带修改 | 切片是视图,共享数据;需要独立用 .copy() |
b[1][1] 与 b[1, 1] 混淆 | 两者结果相同 | 二维建议统一写 b[1, 1],语义更清楚 |
b[:] 复制数组 | 修改它影响了原数组 | b[:] 是视图;复制用 b.copy() |
m[1, ] 多写一个逗号 | 行为异常或报错 | 单个下标取行即可,写成 m[1] |
章末练习
基础
- 对
a = np.array([5, 10, 15, 20, 25]),用索引分别取出 5、25、15 三个元素。 - 用切片取出
a[1:4]、a[:3]、a[::2],并写出各自的结果。 - 创建
np.arange(9).reshape(3, 3),取出第 0 行、第 2 列、[1:, :2]子块。
提高
- 创建 5×5 数组
np.arange(25).reshape(5, 5),用切片赋值把最外层边框(第 0 行、第 4 行、第 0 列、第 4 列)全部改为 -1,打印结果。 - 解释:为什么
sub = a[2:5]; sub[0] = 99会改变a?如何避免?
挑战
- 不用循环,写出一段代码把 4×4 数组的主对角线元素全部加 10(提示:
i = np.arange(4),用m[i, i] += 10试试)。 - 用
np.arange(24).reshape(4, 6)取出「第 1 行、第 2~4 行、隔列取」等组合切片,验证视图与副本的区别,并总结:哪些索引方式返回视图,哪些返回副本。
章末自测
每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。
a = np.array([10, 20, 30]); a[-1]的结果是?- A.
10 - B.
30 - C.
20 - D. 报错
- A.
np.arange(10)[2:5]的结果是?- A.
[2, 3, 4] - B.
[2, 3, 4, 5] - C.
[0, 1, 2] - D.
[5, 6, 7]
- A.
np.arange(10)[::2]的结果是?- A.
[0, 2, 4, 6, 8] - B.
[1, 3, 5, 7, 9] - C.
[0, 1, 2, 3, 4] - D.
[9, 7, 5, 3, 1]
- A.
b = np.array([[1, 2], [3, 4]]); b[0, 1]的结果是?- A.
1 - B.
2 - C.
3 - D.
[1, 2]
- A.
- 取出二维数组所有行的第 0 列,写法是?
- A.
b[0] - B.
b[:, 0] - C.
b[0, :] - D.
b[0, 0]
- A.
- 关于切片视图,正确的说法是?
- A. 切片总是返回独立副本
- B. 切片返回视图,修改它会改变原数组
- C. 切片不能用于二维数组
- D. 视图与副本没有区别
- 获得数组的独立副本,正确写法是?
- A.
a[:] - B.
a.copy() - C.
a.view() - D.
a.clone()
- A.
np.arange(10)[7:2:-2]的结果是?- A.
[7, 5, 3] - B.
[7, 5, 3, 2] - C.
[5, 3] - D.
[]
- A.
- 越界索引
a[5](长度 5)会抛出?- A.
ValueError - B.
IndexError - C.
TypeError - D.
KeyError
- A.
m[:, 1] = 0的作用是?- A. 把第 1 行全部置 0
- B. 把第 1 列全部置 0
- C. 报错
- D. 把对角线置 0
