NumPy: 形状操作

形状操作

1. 你将学到

❶ reshape 不复制数据,只改元数据 ❷ -1 自动推断维度大小 ❸ flatten vs ravel:副本 vs 视图 ❹ transpose / .T 转置原理 ❺ newaxis 增维与 squeeze 降维


2. 一个开发者的真实故事

(1) 痛点

Bob 把 12 个数据从 3×4 变成 4×3,以为是"重新排列内存"——每次 reshape 都在拷贝数据,内存和速度都扛不住。

(2) 解法

Alice 用 Mermaid 图展示:"reshape 只改了元数据,数据一字节都没动——这就是 NumPy 快的秘密。" 只要操作返回的是视图,就不会有任何数据复制。

(3) 收益

理解"视图 vs 副本"后,Bob 写的形状操作代码内存占用降了一个数量级,速度也快了数倍。只有 flatten 是真的复制,其余 reshape/ravel/transpose/swapaxes 全是视图。


3. 形状操作详解

(1) reshape 原理

reshape 改变数组的形状,但不复制数据。它只修改数组的元数据(shape 和 strides),原始数据在内存中不变。

PYTHON
import numpy as np

a = np.arange(12)
b = a.reshape(3, 4)

print(a.shape)   # (12,)
print(b.shape)   # (3, 4)
print(b.base is a)  # True — b is a view of a
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。

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

reshape 内存图解

100%
graph LR
    A["Original Array<br/>shape=(12,)"] -->|"reshape(3,4)"| B["View<br/>shape=(3,4)"]
    A -->|"reshape(4,3)"| C["View<br/>shape=(4,3)"]
    A -->|"reshape(2,6)"| D["View<br/>shape=(2,6)"]

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

核心要点:A、B、C、D 共享同一块内存,只是"解读方式"不同。


(2) -1 自动推断

reshape 中可以用 -1 让 NumPy 自动计算该维度的大小。

PYTHON
a = np.arange(12)

# NumPy infers: 12 / 3 = 4
b = a.reshape(3, -1)     # shape = (3, 4)

# NumPy infers: 12 / 4 = 3
c = a.reshape(-1, 4)     # shape = (3, 4)

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

规则:总元素数 = 各维度乘积-1 位置由 NumPy 自动推算。


(3) C 顺序 vs F 顺序

特性 C 顺序(行优先) F 顺序(列优先)
关键字 order='C' order='F'
遍历方向 最后一个维度变化最快 第一个维度变化最快
内存布局 行连续 列连续
默认 ✅ NumPy 默认
适用场景 大多数情况 与 Fortran/MATLAB 交互
PYTHON
a = np.arange(6).reshape(2, 3, order='C')
# [[0 1 2]
#  [3 4 5]]  — row-major

b = np.arange(6).reshape(2, 3, order='F')
# [[0 2 4]
#  [1 3 5]]  — column-major
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


(4) flatten vs ravel

特性 flatten() ravel()
返回值 副本(copy) 视图(view,如果可能)
修改结果 不影响原数组 影响原数组
内存占用 高(新分配) 低(共享)
性能 较慢 较快
安全性 ✅ 安全 ⚠️ 修改会传播
PYTHON
a = np.arange(12).reshape(3, 4)

f = a.flatten()   # copy
r = a.ravel()     # view (usually)

f[0] = 999
print(a[0, 0])  # 0 — flatten is a copy, a unchanged

r[0] = 888
print(a[0, 0])  # 888 — ravel is a view, a changed!
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


(5) transpose / .T

转置交换轴的顺序。对于 2D 数组,就是行列互换。

PYTHON
a = np.arange(12).reshape(3, 4)
# shape = (3, 4)

b = a.T          # shape = (4, 3)
c = a.transpose()  # same as .T

# For 3D arrays, specify axis order
d = np.arange(24).reshape(2, 3, 4)
e = d.transpose(2, 0, 1)  # axes reordered: (4, 2, 3)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
方式 用法 适用场景
.T a.T 2D 简洁转置
transpose() a.transpose() 2D 转置
transpose(axes) a.transpose(2, 0, 1) 多维自定义轴序
swapaxes() a.swapaxes(0, 1) 交换指定两个轴

(6) newaxis 增维

np.newaxis(等价于 None)用于在指定位置增加长度为 1 的维度。

PYTHON
a = np.array([1, 2, 3])       # shape = (3,)

# Add column dimension
b = a[:, np.newaxis]           # shape = (3, 1)

# Add row dimension
c = a[np.newaxis, :]           # shape = (1, 3)

# Both ways produce the same result
d = a[:, None]                 # same as a[:, np.newaxis]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
增维方式 写法 说明
np.newaxis a[:, np.newaxis] 可读性好
None a[:, None] 简短等价写法
reshape a.reshape(-1, 1) 显式指定形状
expand_dims np.expand_dims(a, axis=1) 函数式写法

(7) squeeze 降维

squeeze 移除长度为 1 的维度。

PYTHON
a = np.arange(12).reshape(1, 3, 4, 1)
# shape = (1, 3, 4, 1)

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

(8) swapaxes 交换轴

swapaxes 交换两个指定轴,是 transpose 的简化版本。

PYTHON
a = np.arange(24).reshape(2, 3, 4)
# shape = (2, 3, 4)

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

▶ 示例

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

:reshape 多种变形(难度⭐)

PYTHON
import numpy as np

a = np.arange(12)
print(f"Original: shape={a.shape}")

# Various valid reshapes
print(f"reshape(3,4):  {a.reshape(3, 4).shape}")
print(f"reshape(4,3):  {a.reshape(4, 3).shape}")
print(f"reshape(2,6):  {a.reshape(2, 6).shape}")
print(f"reshape(6,2):  {a.reshape(6, 2).shape}")
print(f"reshape(2,2,3): {a.reshape(2, 2, 3).shape}")
print(f"reshape(3,2,2): {a.reshape(3, 2, 2).shape}")

# All share the same memory
b = a.reshape(3, 4)
b[0, 0] = -1
print(f"a[0] after modifying b: {a[0]}")  # -1
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


▶ 示例

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

:-1 自动推断(难度⭐)

PYTHON
import numpy as np

a = np.arange(24)

# Let NumPy infer one dimension
print(a.reshape(4, -1).shape)      # (4, 6)
print(a.reshape(-1, 8).shape)      # (3, 8)
print(a.reshape(2, -1, 4).shape)   # (2, 3, 4)
print(a.reshape(-1, 2, 2, 2).shape) # (3, 2, 2, 2)

# Invalid: more than one -1
try:
    a.reshape(-1, -1, 4)  # ERROR: can only specify one unknown dimension
except ValueError as e:
    print(f"Error: {e}")
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


▶ 示例

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

:flatten vs ravel 与 base 追踪(难度⭐⭐)

PYTHON
import numpy as np

a = np.arange(12).reshape(3, 4)

f = a.flatten()
r = a.ravel()

# Check who owns the data
print(f"flatten base is a? {f.base is a}")   # False — copy
print(f"ravel  base is a? {r.base is a}")     # True  — view

# Modify the flattened copy
f[0] = 9999
print(f"After f[0]=9999, a[0,0]={a[0, 0]}")  # 0 — unchanged

# Modify the raveled view
r[0] = 8888
print(f"After r[0]=8888, a[0,0]={a[0, 0]}")  # 8888 — changed!

# For non-contiguous arrays, ravel may also copy
a_f = np.arange(12).reshape(3, 4, order='F')
r_f = a_f.ravel(order='C')
print(f"F-order ravel C-order base: {r_f.base is a_f}")  # may be False
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


▶ 示例

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

:transpose / .T 多维转置(难度⭐⭐)

PYTHON
import numpy as np

# 2D transpose
a = np.arange(12).reshape(3, 4)
print(f"Original shape: {a.shape}")       # (3, 4)
print(f"a.T shape:      {a.T.shape}")     # (4, 3)
print(f"a[1,2] = {a[1, 2]}")             # 6
print(f"a.T[2,1] = {a.T[2, 1]}")         # 6 — same data

# 3D transpose with custom axis order
b = np.arange(24).reshape(2, 3, 4)
print(f"b.shape = {b.shape}")                    # (2, 3, 4)

c = b.transpose(2, 0, 1)
print(f"b.transpose(2,0,1) = {c.shape}")         # (4, 2, 3)

# Verify element correspondence
print(f"b[1, 2, 3] = {b[1, 2, 3]}")              # 23
print(f"c[3, 1, 2] = {c[3, 1, 2]}")              # 23
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


▶ 示例

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

:newaxis 增维与广播(难度⭐⭐)

PYTHON
import numpy as np

a = np.array([1, 2, 3])       # shape (3,)
b = np.array([10, 20, 30, 40]) # shape (4,)

# Outer product via newaxis
outer = a[:, np.newaxis] * b[np.newaxis, :]
print(f"Outer product shape: {outer.shape}")  # (3, 4)
print(outer)
# [[ 10  20  30  40]
#  [ 20  40  60  80]
#  [ 30  60  90 120]]

# Equivalent with reshape
outer2 = a.reshape(-1, 1) * b.reshape(1, -1)
print(f"Same result? {np.array_equal(outer, outer2)}")  # True

# expand_dims is the function version
c = np.expand_dims(a, axis=0)   # shape (1, 3)
d = np.expand_dims(a, axis=1)   # shape (3, 1)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


▶ 示例

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

:squeeze 与 swapaxes(难度⭐⭐)

PYTHON
import numpy as np

# squeeze: remove size-1 dimensions
a = np.arange(12).reshape(1, 3, 4, 1)
print(f"Before squeeze: {a.shape}")                    # (1, 3, 4, 1)
print(f"After squeeze all: {np.squeeze(a).shape}")     # (3, 4)
print(f"Squeeze axis=0: {np.squeeze(a, 0).shape}")    # (3, 4, 1)
print(f"Squeeze axis=3: {np.squeeze(a, 3).shape}")    # (1, 3, 4)

# swapaxes: swap two specific axes
b = np.arange(24).reshape(2, 3, 4)
print(f"Original:    {b.shape}")                       # (2, 3, 4)
print(f"swapaxes(0,1): {b.swapaxes(0, 1).shape}")     # (3, 2, 4)
print(f"swapaxes(1,2): {b.swapaxes(1, 2).shape}")     # (2, 4, 3)

# swapaxes is its own inverse
c = b.swapaxes(0, 1).swapaxes(0, 1)
print(f"Double swap back: {np.shares_memory(b, c)}")  # True
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


4. 综合示例

▶ 示例

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

:8×8 灰度图像全流程(难度⭐⭐⭐)

场景:Charlie 有一张 8×8 灰度图像数据,依次做 reshape→flatten→ravel→transpose,全程验证内存共享。

PYTHON
import numpy as np

# Charlie's 8x8 grayscale image (64 pixels)
img = np.arange(64, dtype=np.float32).reshape(8, 8)
print(f"1. Original image shape: {img.shape}")
print(f"   img[0,0]={img[0,0]}, img[7,7]={img[7,7]}")

# Step 1: reshape to 4x16 (4 rows, 16 cols)
img_4x16 = img.reshape(4, 16)
print(f"\n2. After reshape(4,16): {img_4x16.shape}")
print(f"   Shares memory with original? {np.shares_memory(img, img_4x16)}")

# Step 2: reshape to 4x4x4 (4 blocks, 4 rows, 4 cols)
img_4x4x4 = img.reshape(4, 4, 4)
print(f"\n3. After reshape(4,4,4): {img_4x4x4.shape}")
print(f"   Shares memory with original? {np.shares_memory(img, img_4x4x4)}")

# Step 3: ravel (view)
img_ravel = img.ravel()
print(f"\n4. After ravel: {img_ravel.shape}")
print(f"   Shares memory with original? {np.shares_memory(img, img_ravel)}")

# Step 4: flatten (copy)
img_flatten = img.flatten()
print(f"\n5. After flatten: {img_flatten.shape}")
print(f"   Shares memory with original? {np.shares_memory(img, img_flatten)}")

# Step 5: transpose the reshaped view
img_4x16_T = img_4x16.T
print(f"\n6. After transpose of (4,16): {img_4x16_T.shape}")
print(f"   Shares memory with img_4x16? {np.shares_memory(img_4x16, img_4x16_T)}")

# Step 6: Modify through view chain
img_ravel[0] = 999.0
print(f"\n7. After ravel[0]=999.0:")
print(f"   img[0,0]={img[0,0]}")       # 999.0 — view chain propagated
print(f"   img_4x16[0,0]={img_4x16[0,0]}")  # 999.0

img_flatten[1] = 777.0
print(f"   img[0,1]={img[0,1]}")       # 1.0 — flatten is a copy, unchanged

# Step 7: Restore and swapaxes
img[0, 0] = 0.0
img_swapped = img.reshape(8, 8, 1).swapaxes(0, 2)
print(f"\n8. After swapaxes(0,2) on (8,8,1): {img_swapped.shape}")
print(f"   Shares memory? {np.shares_memory(img, img_swapped)}")

# Summary table
print("\n=== Memory Sharing Summary ===")
ops = {
    'reshape(4,16)': img_4x16,
    'reshape(4,4,4)': img_4x4x4,
    'ravel()': img_ravel,
    'flatten()': img_flatten,
    'transpose(.T)': img_4x16_T,
    'swapaxes(0,2)': img_swapped,
}
for name, arr in ops.items():
    shared = np.shares_memory(img, arr)
    print(f"  {name:20s} | shares_memory={shared} | view={'Yes' if shared else 'No (copy)'}")
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。


❓ 常见问题

Q reshape 什么时候会报错?
A 当新形状的元素总数与原数组不一致时。例如 np.arange(12).reshape(5, 3) 会报 ValueError,因为 5×3=15 ≠ 12。
Q flatten 和 ravel 用哪个?
A 默认用 ravel()——省内存、速度快。只有当你需要确保修改不会影响原数组时,才用 flatten()
Q -1 只能用一次吗?
A 是的。reshape 中最多只能出现一个 -1,否则 NumPy 无法唯一确定每个维度的大小,会抛出 ValueError
Q 多维 transpose 怎么理解?
A a.transpose(i, j, k) 把原来的轴 0→i 位置、轴 1→j 位置、轴 2→k 位置。等价地,result[x_i, x_j, x_k] = a[x_0, x_1, x_2]。可以理解为"重排轴的顺序"。
Q newaxis 和 reshape(-1,1) 有什么区别?
A 效果相同,都产生视图。newaxis 更直观(在切片中插入维度),reshape 更显式(直接指定目标形状)。选择看个人偏好。
Q ravel 一定返回视图吗?
A 不一定。当数组在内存中不连续(如转置后的数组用 C 顺序 ravel),NumPy 可能被迫返回副本。可通过 np.shares_memory() 检查。

📖 小节

核心心法:reshape/ravel/transpose/swapaxes 都是"换眼镜不换桌子"——数据不动,只是解读方式变了。只有 flatten 是真的"复印一份"。


📝 作业

(1) 6 种 reshape

np.arange(60),写出 6 种不同的合法 reshape 形状(如 (6,10)),并打印每种结果的 shape。

(2) 验证 ravel 视图与 flatten 副本

  1. 创建 a = np.arange(20).reshape(4, 5)
  2. 分别调用 r = a.ravel()f = a.flatten()
  3. 修改 r[0] = 999,检查 a[0, 0] 是否变化
  4. 修改 f[1] = 777,检查 a[0, 1] 是否变化
  5. np.shares_memory() 验证

(3) newaxis 1D→3D

  1. 创建 a = np.array([10, 20, 30, 40, 50])(shape (5,)
  2. newaxis 将其变为 shape (1, 5, 1)
  3. 再用 squeeze 还原为 (5,)
  4. 验证每步的 shape 和内存共享情况
Web-Tutorial.com

Web-Tutorial 技术团队

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

100%

🙏 帮我们做得更好

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

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