NumPy: 索引与切片

1. 你将学到


2. 故事

Charlie 正在分析一份 10K 行的传感器数据矩阵。他用 data[0:3] 取出前 3 行做异常值清洗,顺手将缺失值设为 -1:

PYTHON
subset = data[0:3]
subset[subset == -999] = -1
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

清洗完打印 subset,数据很漂亮。但当他回头查看 data 时,发现原始数据的前 3 行也被改了!

"切片返回的是视图,不是副本!" Alice 一语道破。

她教 Charlie 用 subset = data[0:3].copy() 才能安全操作,不影响原始数组。Charlie 深刻体会到:NumPy 切片默认共享内存,这是高效之源,也是陷阱之门。


3. 知识点

(1) 基本索引

一维数组的索引与 Python 列表一致:从 0 开始,负索引从 -1 倒数。

PYTHON
import numpy as np

a = np.array([10, 20, 30, 40, 50])
a[0]    # 10
a[-1]   # 50
a[2]    # 30
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) 切片语法

切片格式为 start:stop:step,三个部分均可省略:

省略形式 等价写法 含义
a[:] a[0:len(a):1] 全部元素
a[2:] a[2:len(a):1] 从第 3 个到末尾
a[:3] a[0:3:1] 前 3 个元素
a[::2] a[0:len(a):2] 隔一个取一个
a[::-1] a[len(a)-1::-1] 整体反转
⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
a = np.arange(10)  # [0 1 2 3 4 5 6 7 8 9]
a[2:7]             # [2 3 4 5 6]
a[::3]             # [0 3 6 9]
a[::-1]            # [9 8 7 6 5 4 3 2 1 0]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(3) 负索引

负索引从数组末尾计数,-1 是最后一个元素,-2 是倒数第二个,以此类推。

PYTHON
a = np.array([10, 20, 30, 40, 50])
a[-1]   # 50
a[-2]   # 40
a[-3:]  # [30 40 50]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(4) 多维切片

二维数组的索引为 a[row, col],切片在每一维独立应用:

PYTHON
b = np.arange(12).reshape(3, 4)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

b[1, 2]     # 6   (row 1, col 2)
b[0, :]     # [0 1 2 3]  (entire row 0)
b[:, 1]     # [1 5 9]    (entire col 1)
b[0:2, 1:3] # [[1 2], [5 6]]  (2x2 sub-matrix)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

三维数组在二维基础上再增加一个维度(深度/通道),索引为 a[depth, row, col]

PYTHON
c = np.arange(24).reshape(2, 3, 4)
# c[0] is the first 3x4 plane
# c[1] is the second 3x4 plane
c[0, 1, 2]     # 6  (depth 0, row 1, col 2)
c[:, 0:2, :]   # shape (2, 2, 4) - first 2 rows of each plane
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(5) 省略号

当数组维度很高时,...(省略号)可以代替连续的 :,使代码更简洁:

PYTHON
c = np.arange(24).reshape(2, 3, 4)
c[..., 0]   # equivalent to c[:, :, 0] -> shape (2, 3)
c[0, ...]   # equivalent to c[0, :, :] -> shape (3, 4)
c[0, ..., 1]  # equivalent to c[0, :, 1] -> shape (3,)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

省略号只能出现一次,且会自动填充到缺失的所有维度的完整切片。

(6) 视图 vs 副本

这是 NumPy 切片最重要的特性:切片返回视图(view),索引返回标量或副本。

操作 返回类型 修改是否影响原数组
a[1:4] 视图
a[0] 标量 否(标量不可变)
a[[0, 2]] 副本 否(花式索引返回副本)
a[a > 0] 副本 否(布尔索引返回副本)
a[1:4].copy() 副本
PYTHON
a = np.arange(6)  # [0 1 2 3 4 5]
s = a[2:5]        # view: [2 3 4]
s[:] = 100
# a is now [  0   1 100 100 100   5]

b = a[2:5].copy()  # copy: independent
b[:] = 0
# a unchanged
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(7) 切片赋值

切片是视图,因此对切片赋值会直接修改原数组。这是高效操作大数组的利器:

PYTHON
a = np.zeros(10)
a[3:7] = 99
# a -> [ 0.  0.  0. 99. 99. 99. 99.  0.  0.  0.]

a[::2] = -1
# a -> [-1.  0. -1. 99. -1. 99. -1.  0. -1.  0.]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) 切片内存模型

100%
graph LR
    A["Original array a<br/>[0, 1, 2, 3, 4, 5]"] -->|"a[2:5] creates view"| B["View s<br/>[2, 3, 4]"]
    B -->|"s shares memory with a"| A
    B -->|"s[:] = 100"| C["Modified a<br/>[0, 1, 100, 100, 100, 5]"]
    A -.->|".copy() creates independent copy"| D["Copy b<br/>[2, 3, 4]"]
    D -->|"b[:] = 0"| E["Copy b<br/>[0, 0, 0]"]
    E -.->|"No effect on a"| A
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(3) 对比:list 切片 vs NumPy 切片

特性 Python list NumPy ndarray
a[1:4] 返回 新列表(副本) 视图(共享内存)
修改切片 不影响原列表 影响原数组
获取副本 默认就是 .copy()
多维索引 不支持 a[row, col]
省略号 不支持 a[..., 0]
步长切片 支持 支持
切片赋值 不支持 支持

(4) 对比:1D / 2D / 3D 索引语法

维度 索引取值 取一行/一列 子区域切片
1D a[i] a[2:5]
2D a[i, j] a[i, :] / a[:, j] a[0:2, 1:3]
3D a[d, i, j] a[d, i, :] / a[d, :, j] a[0:1, 0:2, 1:3]

(5) 对比:视图 vs 副本

判断方式 视图(共享内存) 副本(独立内存)
np.shares_memory(a, s) True False
s.base is a True False
修改 sa 变化
内存开销 极小 与原数组相当

(6) 对比:常见切片模式

模式 语法 含义
前n个 a[:n] 索引 0 到 n-1
后n个 a[-n:] 最后 n 个元素
去首尾 a[1:-1] 去掉第1个和最后1个
偶数位置 a[::2] 步长2从头开始
奇数位置 a[1::2] 步长2从索引1开始
反转 a[::-1] 步长-1,整体翻转
隔2取1 a[::3] 步长3

4. 示例

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:1D 索引与切片(难度⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

a = np.arange(10, 20)  # [10 11 12 13 14 15 16 17 18 19]

# Basic indexing
print(a[0])    # 10
print(a[-1])   # 19
print(a[5])    # 15

# Slicing
print(a[2:6])  # [12 13 14 15]
print(a[:4])   # [10 11 12 13]
print(a[7:])   # [17 18 19]
print(a[::2])  # [10 12 14 16 18]
print(a[::-1]) # [19 18 17 16 15 14 13 12 11 10]

# Negative index slicing
print(a[-3:])  # [17 18 19]
print(a[-5:-2])# [15 16 17]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:2D 行列与子矩阵(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

b = np.arange(20).reshape(4, 5)
# [[ 0  1  2  3  4]
#  [ 5  6  7  8  9]
#  [10 11 12 13 14]
#  [15 16 17 18 19]]

# Single element
print(b[1, 2])      # 7

# Entire row
print(b[2, :])      # [10 11 12 13 14]
print(b[2])         # same result, row index alone

# Entire column
print(b[:, 3])      # [ 3  8 13 18]

# Sub-matrix (rows 1-2, cols 2-4)
print(b[1:3, 2:5])
# [[ 7  8  9]
#  [12 13 14]]

# First and last rows
print(b[[0, -1], :])
# [[ 0  1  2  3  4]
#  [15 16 17 18 19]]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:步长切片与负步长(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

a = np.arange(15)  # [0 1 2 ... 14]

# Step slicing
print(a[1:12:3])   # [1 4 7 10]   start=1, step=3
print(a[::4])      # [0 4 8 12]   start=0, step=4

# Negative step: traverses from end to start
print(a[::-1])     # [14 13 12 ... 1 0]
print(a[::-2])     # [14 12 10 8 6 4 2 0]

# Negative step with start/stop
print(a[12:3:-2])  # [12 10 8 6 4]  from index 12 down to 4 (stop=3 excluded)
print(a[-1:5:-3])  # [14 11 8]      from last to index 6 (stop=5 excluded)

# 2D step slicing
b = np.arange(20).reshape(4, 5)
print(b[::2, ::2])
# [[ 0  2  4]
#  [10 12 14]]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:切片赋值与视图验证(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

# View: slice shares memory
a = np.arange(8)  # [0 1 2 3 4 5 6 7]
s = a[2:5]        # view -> [2 3 4]

print(np.shares_memory(a, s))  # True
print(s.base is a)             # True

# Modify view -> original changes
s[:] = 99
print(a)  # [ 0  1 99 99 99  5  6  7]

# Slice assignment to original
a[4:7] = -1
print(a)  # [ 0  1 99 99 -1 -1 -1  7]

# Copy: independent
b = a[2:5].copy()
print(np.shares_memory(a, b))  # False
b[:] = 0
print(a)  # [ 0  1 99 99 -1 -1 -1  7]  unchanged
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:省略号索引(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

c = np.arange(24).reshape(2, 3, 4)
# Shape: (2, 3, 4) -> depth, rows, cols

# Ellipsis replaces all middle dimensions
print(c[..., 0])      # shape (2, 3), same as c[:, :, 0]
# [[ 0  4  8]
#  [12 16 20]]

print(c[0, ...])      # shape (3, 4), same as c[0, :, :]
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(c[0, ..., 1])   # shape (3,), same as c[0, :, 1]
# [1 5 9]

# Compare explicit vs ellipsis
print(np.all(c[..., 2] == c[:, :, 2]))  # True
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:综合——5×5 棋盘矩阵四象限/对角线/视图验证(难度⭐⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

# Build a 5x5 checkerboard-style matrix with unique values
board = np.arange(25).reshape(5, 5)
# [[ 0  1  2  3  4]
#  [ 5  6  7  8  9]
#  [10 11 12 13 14]
#  [15 16 17 18 19]
#  [20 21 22 23 24]]

# --- Four quadrants (center row/col = index 2) ---
top_left     = board[:2, :2]    # [[0 1], [5 6]]
top_right    = board[:2, 3:]    # [[3 4], [8 9]]
bottom_left  = board[3:, :2]    # [[15 16], [20 21]]
bottom_right = board[3:, 3:]    # [[18 19], [23 24]]

print("Top-Left:\n", top_left)
print("Top-Right:\n", top_right)
print("Bottom-Left:\n", bottom_left)
print("Bottom-Right:\n", bottom_right)

# --- Main diagonal ---
diag = np.diag(board)
print("Main diagonal:", diag)  # [0 6 12 18 24]

# --- Anti-diagonal ---
anti_diag = np.diag(np.fliplr(board))
print("Anti-diagonal:", anti_diag)  # [4 8 12 16 20]

# --- Center 3x3 ---
center = board[1:4, 1:4]
print("Center 3x3:\n", center)
# [[ 6  7  8]
#  [11 12 13]
#  [16 17 18]]

# --- Verify view behavior ---
view_slice = board[1:4, 1:4]
print("Is view?", np.shares_memory(board, view_slice))  # True

view_slice[0, 0] = 999
print("Original after modifying view:")
print(board)
# Row 1, col 1 is now 999

# --- Safe copy ---
safe_copy = board[1:4, 1:4].copy()
safe_copy[:, :] = 0
print("Original after modifying copy (unchanged):")
print(board)  # Only the 999 change persists; copy modification has no effect
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

❓ 常见问题

Q NumPy 切片和 Python list 切片有什么区别?
A Python list 切片返回新列表(副本),修改不影响原列表;NumPy 切片返回视图(共享内存),修改会直接影响原数组。这是 NumPy 为节省内存和提升性能的设计选择。
Q 为什么切片赋值会改变原数组?
A 因为切片是视图,与原数组共享同一块内存。对视图的任何修改都是在原数组的内存上直接操作。使用 .copy() 可以创建独立副本,修改副本不会影响原数组。
Q 负步长 [::-1] 怎么理解?
A 负步长表示从右向左遍历。a[::-1] 等价于从最后一个元素开始,步长 -1 向前遍历,结果就是反转。a[12:3:-2] 表示从索引 12 开始,每次向左跳 2 步,到索引 3(不含)停止。注意负步长时 start > stop。
Q 省略号 ... 什么时候用?
A 当数组维度较高,且你只想对最后几个维度做索引时,用 ... 代替前面的所有 :。例如 4D 数组 a[:, :, :, 0] 可简写为 a[..., 0]。省略号只能出现一次,且自动填充到中间所有缺失的维度。
Q 如何安全地复制切片,避免视图修改原数组?
A 使用 .copy() 方法:safe_slice = a[2:5].copy()。这样得到的副本与原数组完全独立,修改副本不会影响原数组。也可以用 np.array(a[2:5]) 构造新数组,效果相同。
Q 花式索引(如 a[[0, 2, 4]])返回视图还是副本?
A 花式索引返回副本,不是视图。因此修改花式索引的结果不会影响原数组。这是花式索引与基本切片的关键区别之一。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 5×5 的矩阵(元素 0~24),提取中心 3×3 子矩阵。打印结果并验证其 shape。

  2. 基础题(难度⭐):给定 a = np.arange(20),用步长切片分别取出:① 所有偶数索引位置的元素 ② 所有奇数索引位置的元素 ③ 反转后的数组。

  3. 进阶题(难度⭐⭐):创建一个 6×6 的数组,先用切片取第 2~4 行(索引 2、3、4),修改切片的值为 0,验证原数组也被修改。然后用 .copy() 重新取相同的切片,修改副本为 -1,验证原数组不变。用 np.shares_memory() 辅助验证。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏