
numpy 通过 `array.data`(底层 memoryview)隐式记录视图的内存起始地址,而非依赖 `base` 属性;`shape` 和 `strides` 均作用于该 `data` 缓冲区,从而实现零拷贝切片——`b = a[1]` 的起始元素 `4` 由 `b.data` 指向 `a` 数据内存中偏移 24 字节的位置决定。
在 NumPy 中,视图(view)的本质是共享底层内存缓冲区,但拥有独立的元数据描述。关键在于:base 属性仅用于追溯原始数组(主要用于调试或内存生命周期管理),而真正决定数据读取起点和布局的是 data 属性所指向的内存地址,以及配套的 shape 和 strides。
以你的示例为例:
import numpy as np
a = np.arange(1, 7, dtype=np.int64).reshape(2, 3) # int64 → 每个元素占 8 字节
print("a.shape:", a.shape) # (2, 3)
print("a.strides:", a.strides) # (24, 8) → 行跨步=3×8=24字节,列跨步=8字节
print("a.data:", a.data) # 当执行 b = a[1] 时,NumPy 并未复制数据,而是:
- 计算索引 1 对应的字节偏移量:offset = 1 * a.strides[0] = 1 × 24 = 24 字节;
- 将 b.data 设置为从 a.data 起始地址向后偏移 24 字节的新 memoryview;
- 设置 b.shape = (3,),b.strides = (8,) —— 这表示:从新起点开始,每步跳 8 字节取一个元素,共取 3 个。
验证偏移关系:
# 手动计算 b 的起始地址偏移(需使用 ctypes 获取实际地址)
import ctypes
a_ptr = a.__array_interface__['data'][0] # 原始数据首地址(int64)
b_ptr = b.__array_interface__['data'][0] # 视图数据首地址
print(f"a data address: 0x{a_ptr:x}")
print(f"b data address: 0x{b_ptr:x}")
print(f"Offset: {b_ptr - a_ptr} bytes") # 输出:24⚠️ 注意事项:
- b.base is a 为 True,但这不意味着 b 的数据解释基于 a 的 strides;b 完全按自身 shape 和 strides 解析 b.data;
- 修改 b 会直接影响 a(因共享内存),这是视图的核心特性;
- data 是只读属性,不可直接赋值;若需显式控制偏移,可使用 np.ndarray 构造函数配合 buffer 和 offset 参数(高级用法,需谨慎)。
总结:NumPy 的“偏移”并非存储在某个公开字段中,而是编码在 data memoryview 的内部指针里。strides 和 shape 是解码规则,data 是待解码的原始字节流——三者协同,构成了高效、灵活的内存视图机制。










