4.1 基本切片为何总是视图


文档摘要

4.1 基本切片为何总是视图 本节摘要:冒号切片(含负索引、跨步)不复制数据,只构造一个新的描述:数据指针前移起点偏移、strides 按跨步缩放、shape 按区间重算。因此切片纳秒级完成,但写入切片就是写入原数组。本节拆解这套机制,演练切片赋值的广播规则与防御写法。 切片的语法与机器动作 先复习语法,再看每条语法在内存层面做了什么: 机器动作只有三步:起点指针加偏移、步长乘以跨步、形状按区间长度重算。验证一下: 切片构造视图的机制 切片构造视图的机制 切片赋值:视图的另一面 切片是视图,所以对切片赋值等于隔着窗口改原件。这既是陷阱也是利器: 防御性写法小结:需要独立副本时 ;函数内部要改又不许影响外部时,入口先 copy(第 2.3 节的 clean 案例)。

4.1 基本切片为何总是视图

本节摘要:冒号切片(含负索引、跨步)不复制数据,只构造一个新的描述:数据指针前移起点偏移、strides 按跨步缩放、shape 按区间重算。因此切片纳秒级完成,但写入切片就是写入原数组。本节拆解这套机制,演练切片赋值的广播规则与防御写法。

切片的语法与机器动作

先复习语法,再看每条语法在内存层面做了什么:

import numpy as np a = np.arange(10) print(a[2:7]) # [2 3 4 5 6],起含止不含 print(a[:4]) # [0 1 2 3] print(a[::2]) # [0 2 4 6 8],跨步2 print(a[::-1]) # [9 8 7 6 5 4 3 2 1 0],负跨步=倒着看 print(a[-3:]) # [7 8 9],负索引从尾部数

机器动作只有三步:起点指针加偏移、步长乘以跨步、形状按区间长度重算。验证一下:

s = a[3:8:2] # 取下标3、5、7 print(s) # [3 5 7] print("shape:", s.shape) # (3,) print("strides:", s.strides) # (16,) —— 原来是8,乘以跨步2 print("共享内存:", np.shares_memory(s, a)) # True # 多维时每维独立切片,逗号分隔 m = np.arange(12).reshape(3, 4) sub = m[0:2, 1:3] print(sub) # [[1 2] # [5 6]] print(sub.strides) # (16, 8),行维与列维的切片区间都连续地映射回原块

切片构造视图的机制

切片构造视图的机制

切片赋值:视图的另一面

切片是视图,所以对切片赋值等于隔着窗口改原件。这既是陷阱也是利器:

import numpy as np m = np.zeros((3, 4), dtype=np.int32) m[1, :] = 9 # 整行赋值 print(m) # [[0 0 0 0] # [9 9 9 9] # [0 0 0 0]] m[:, ::2] = np.array([[1], [2], [3]]) # 广播赋值到隔列 print(m) # [[1 0 1 0] # [2 9 2 9] # [3 0 3 0]] # 负跨步切片也能赋值,倒序视图同样是窗口 r = np.arange(5) r[::-1] = np.array([10, 20, 30, 40, 50]) print(r) # [50 40 30 20 10]

防御性写法小结:需要独立副本时 a[1:5].copy();函数内部要改又不许影响外部时,入口先 copy(第 2.3 节的 clean 案例)。反过来,想批量修改大片数据时,切片赋值比循环逐个改快百倍,是应该主动使用的工具。

案例:给棋盘矩阵涂色

背景:生成 8×8 国际棋盘(0 白 1 黑交替),这是练习"切片加跨步"的绝佳素材。操作:

import numpy as np board = np.zeros((8, 8), dtype=np.uint8) board[::2, ::2] = 1 # 偶行偶列 board[1::2, 1::2] = 1 # 奇行奇列 print(board) # [[1 0 1 0 1 0 1 0] # [0 1 0 1 0 1 0 1] # [1 0 1 0 1 0 1 0] # [0 1 0 1 0 1 0 1] # [1 0 1 0 1 0 1 0] # [0 1 0 1 0 1 0 1] # [1 0 1 0 1 0 1 0] # [0 1 0 1 0 1 0 1]]

结果:四行代码、零循环完成涂色。解读:每个切片赋值只生成一次视图加一次批量写入,两次赋值覆盖全部黑格。变式:三维棋盘、任意周期涂色只需改跨步与相位,比如 a[::3] 涂每隔两格的位置。

边界行为备忘

import numpy as np a = np.arange(5) # 越界的"切片"自动收敛,不报错(与直接索引不同) print(a[2:100]) # [2 3 4] print(a[10:20]) # [],空数组合法 # 但单个下标越界立刻报错 # a[10] # IndexError # 空 dtype 视图可参与形状运算 print(a[10:20].shape) # (0,)

💡 关键直觉:把切片想成"调整取景器"——镜头挪位置、改变焦,但风景(数据)只有一份。按快门(写入)动的是风景本身。

多维切片的维度自由组合

二维以上的切片,每个维度都可以独立选择"单个下标、区间、跨步"三种模式,组合后的形状按维度逐个计算:

import numpy as np m = np.arange(60).reshape(3, 4, 5) # 三批 4x5 case1 = m[0, 1:3, ::2] # 维0取定值(降维),维1区间,维2跨步 print(case1.shape) # (2, 2) —— 定值下标把该维消掉 case2 = m[:, :, -1] # 每批每行取最后一列 print(case2.shape) # (3, 4) case3 = m[..., 0] # 省略号代表"前面所有维全取" print(case3.shape) # (3, 4),与 m[:,: ,0] 等价但更简洁

两个新语法值得记住:单个整数下标会把该维度消掉(shape 少一维),省略号 ... 是"其余维度全取"的通配。case2 与 case3 这类写法在图像处理里极为常见——取某个通道、取最后一帧,都是"部分维度定值、部分维度全取"的组合。

还要提醒一点:切片视图可以长期持有,但它会让原数组无法回收(2.3 节的 base 链)。从大数组取一小块长期使用时,老规矩——补 copy。

本节要点回顾

  • 三步构造:切片 = 指针偏移 + 步长缩放 + 形状重算,数据零移动
  • 读写同源:读切片便宜,写切片穿透到原数组
  • 切片赋值会广播:右侧形状能广播到切片窗口即可整块写入
  • 负跨步倒序:a[::-1] 仍是视图,反转不花拷贝
  • 边界差异:切片越界静默收敛,单下标越界报错
  • 维度组合:单下标消掉该维,省略号通配其余维,多维切片逐维独立
  • 长期持切片:记得 copy 切断 base 链,别拖住大块内存
  • 一句话总结:切片永远只是换了个角度看同一份数据

下一节看"下标不连续"的情形:花式索引与布尔掩码。它们放弃视图换来安全,代价是一次真实搬运。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U