NumPy: dtype 数据类型
dtype 数据类型
1. 你将学到
- dtype 体系与分类
- 指定 dtype 创建数组
- 类型转换与溢出风险
- 结构化 dtype
- dtype 对内存、精度、速度的权衡
2. 故事
Alice 训练模型时准确率从 95% 暴跌到 12%。排查发现:float64 转 int8 时超过 127 的值全部溢出变负数。"dtype 不是小问题——选错类型,数据就毁了。"
3. 知识点
(1) dtype 体系
NumPy 的 dtype(data type)是数组中每个元素的类型描述。每个 ndarray 都有一个 .dtype 属性。
np.int32 # 32-bit signed integer
np.float64 # 64-bit floating point
np.bool_ # boolean
np.str_ # unicode string
np.object_ # Python object
dtype 包含两个字段:
| 字段 | 含义 | 示例 |
|---|---|---|
kind |
类型类别 | 'i'=int, 'f'=float, 'u'=uint, 'b'=bool |
itemsize |
单个元素字节数 | 4 for int32 |
import numpy as np
a = np.array([1, 2, 3])
print(a.dtype) # int64 (on 64-bit platforms)
print(a.dtype.kind) # 'i'
print(a.dtype.itemsize) # 8
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) 整数类型对比
| 类型 | 范围 | 字节 | 别名 |
|---|---|---|---|
int8 |
-128 ~ 127 | 1 | — |
int16 |
-32768 ~ 32767 | 2 | — |
int32 |
-2147483648 ~ 2147483647 | 4 | intp (32-bit) |
int64 |
-9223372036854775808 ~ 9223372036854775807 | 8 | intp (64-bit) |
uint8 |
0 ~ 255 | 1 | — |
uint16 |
0 ~ 65535 | 2 | — |
uint32 |
0 ~ 4294967295 | 4 | — |
uint64 |
0 ~ 18446744073709551615 | 8 | — |
(3) 浮点类型对比
| 类型 | 精度(有效位) | 范围 | 字节 |
|---|---|---|---|
float16 |
~3 位十进制 | ±65504 | 2 |
float32 |
~7 位十进制 | ±3.4e38 | 4 |
float64 |
~15 位十进制 | ±1.8e308 | 8 |
longdouble |
~18-19 位十进制 | 平台相关 | 8/12/16 |
(4) 指定 dtype 创建数组
import numpy as np
a = np.array([1, 2, 3], dtype=np.int8)
b = np.array([1.0, 2.5], dtype=np.float32)
c = np.arange(10, dtype=np.uint8)
d = np.zeros(5, dtype=np.complex128)
e = np.array([1, 2, 3], dtype='f4') # string shorthand for float32
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
常用字符串简写:
| 简写 | 等价 | 简写 | 等价 |
|---|---|---|---|
'i1' |
int8 | 'u1' |
uint8 |
'i2' |
int16 | 'u2' |
uint16 |
'i4' |
int32 | 'u4' |
uint32 |
'f4' |
float32 | 'f8' |
float64 |
(5) 类型转换与溢出风险
.astype() 用于类型转换,不会修改原数组,而是返回新数组。
import numpy as np
a = np.array([1.7, 2.3, 3.9])
b = a.astype(np.int32) # truncates toward zero
print(b) # [1 2 3]
print(a) # [1.7 2.3 3.9] -- original unchanged
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
类型转换风险表:
| 转换方向 | 风险 | 说明 |
|---|---|---|
| float → int | 精度丢失 | 小数部分被截断 |
| int64 → int8 | 溢出 | 超出范围则回绕 |
| float64 → float32 | 精度丢失 | 有效位从 15 降至 7 |
| int → float | 精度丢失 | 大整数可能不精确 |
| uint → int | 符号错误 | 高位被解释为符号位 |
(6) 溢出行为
NumPy 整数溢出不会报错,而是回绕(wrap around):
import numpy as np
a = np.array([127], dtype=np.int8)
a[0] = a[0] + 1
print(a[0]) # -128 (wrapped!)
b = np.array([200], dtype=np.uint8)
print(b) # [200] OK for uint8
c = np.array([300 % 256], dtype=np.uint8)
print(c) # [44] 300 mod 256 安全写法(NumPy 2.x 默认拒绝越界)
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
用 np.iinfo() 和 np.finfo() 查询范围:
import numpy as np
print(np.iinfo(np.int8)) # min=-128, max=127
print(np.iinfo(np.uint16)) # min=0, max=65535
print(np.finfo(np.float32)) # min=-3.4028235e+38, max=3.4028235e+38
print(np.finfo(np.float32).eps) # 1.1920929e-07 machine epsilon
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(7) dtype 自动提升链
当不同 dtype 的数组运算时,NumPy 会自动提升到更"宽"的类型:
graph LR
A[bool] --> B[int8]
B --> C[int16]
C --> D[int32]
D --> E[int64]
E --> F[float16]
F --> G[float32]
G --> H[float64]
I[uint8] --> J[uint16]
J --> K[uint32]
K --> L[uint64]
A --> I
L --> D
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
import numpy as np
a = np.array([1], dtype=np.int8)
b = np.array([1], dtype=np.float32)
c = a + b
print(c.dtype) # float32
d = np.array([1], dtype=np.int32)
e = np.array([1], dtype=np.int64)
f = d + e
print(f.dtype) # int64
print(np.promote_types(np.int8, np.float32)) # float32
print(np.promote_types(np.uint8, np.int8)) # int16
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(8) 结构化 dtype
结构化 dtype 允许数组中每个元素包含多个命名字段,类似 C 结构体或数据库行。
import numpy as np
dt = np.dtype([
('name', 'U20'), # unicode string, max 20 chars
('age', 'i4'), # 32-bit int
('score', 'f4'), # 32-bit float
])
students = np.array([
('Alice', 20, 92.5),
('Bob', 22, 88.0),
], dtype=dt)
print(students['name']) # ['Alice' 'Bob']
print(students['age']) # [20 22]
print(students[0]) # ('Alice', 20, 92.5)
print(students[0]['score']) # 92.5
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
结构化 vs 普通 dtype:
| 特性 | 普通 dtype | 结构化 dtype |
|---|---|---|
| 每元素字段数 | 1 | 多个 |
| 字段命名 | 无 | 有 |
| 内存布局 | 连续同类型 | 按字段排列 |
| 访问方式 | 索引 | 字段名 + 索引 |
| 典型用途 | 数值计算 | 表格/记录数据 |
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:不同 dtype 内存对比(难度⭐)
import numpy as np
n = 1_000_000
a_int8 = np.zeros(n, dtype=np.int8)
a_int64 = np.zeros(n, dtype=np.int64)
a_float32 = np.zeros(n, dtype=np.float32)
a_float64 = np.zeros(n, dtype=np.float64)
print(f"int8 : {a_int8.nbytes / 1024 / 1024:.2f} MB")
print(f"int64 : {a_int64.nbytes / 1024 / 1024:.2f} MB")
print(f"float32 : {a_float32.nbytes / 1024 / 1024:.2f} MB")
print(f"float64 : {a_float64.nbytes / 1024 / 1024:.2f} MB")
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
输出:
int8 : 0.95 MB
int64 : 7.63 MB
float32 : 3.81 MB
float64 : 7.63 MB
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:int8 溢出(难度⭐⭐)
import numpy as np
import numpy as np
import warnings
warnings.simplefilter("ignore")
# 安全写法:先把范围限制在 int8 范围内,再转 int8
a = np.array([120, 125, 127, ((128 + 256) % 256) - 256, ((130 + 256) % 256) - 256], dtype=np.int8)
# 128 wraps to -128, 130 wraps to -126
print(a) # [ 120 125 127 -128 -126]
# NumPy 2.x 直接传入越界值会抛 OverflowError;按上面方式绕开
info = np.iinfo(np.int8)
print(f"int8 range: [{info.min}, {info.max}]")
# int8 range: [-128, 127]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:float32 vs float64 精度(难度⭐⭐)
import numpy as np
pi_32 = np.float32(np.pi)
pi_64 = np.float64(np.pi)
print(f"float32 pi: {pi_32:.20f}")
print(f"float64 pi: {pi_64:.20f}")
# float32 has ~7 decimal digits of precision
# float64 has ~15 decimal digits of precision
a = np.array([0.1], dtype=np.float32)
b = np.array([0.1], dtype=np.float64)
print(f"float32 0.1: {a[0]:.25f}")
print(f"float64 0.1: {b[0]:.25f}")
# float32 loses precision after ~7 digits
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:结构化数组(难度⭐⭐⭐)
import numpy as np
dt = np.dtype([
('id', 'i4'),
('x', 'f8'),
('y', 'f8'),
('label', 'U10'),
])
points = np.array([
(1, 1.5, 2.3, 'A'),
(2, 3.1, 4.7, 'B'),
(3, 0.8, 1.2, 'A'),
], dtype=dt)
# Access fields
print(points['x']) # [1.5 3.1 0.8]
print(points['label']) # ['A' 'B' 'A']
# Filter
mask = points['label'] == 'A'
print(points[mask]) # rows where label is 'A'
# Compute on field
print(f"Mean x: {points['x'].mean():.2f}")
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:astype 数据丢失(难度⭐⭐)
import numpy as np
# float -> int: truncation
a = np.array([1.9, 2.1, -3.7, 0.5])
b = a.astype(np.int32)
print(f"Original : {a}")
print(f"As int32 : {b}") # [ 1 2 -3 0]
# int64 -> int8: overflow
c = np.array([100, 200, 300], dtype=np.int64)
d = c.astype(np.int8)
print(f"Original : {c}")
print(f"As int8 : {d}") # [100 -56 44]
# float64 -> float32: precision loss
e = np.array([1.123456789012345], dtype=np.float64)
f = e.astype(np.float32)
print(f"float64: {e[0]:.16f}")
print(f"float32: {f[0]:.16f}")
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
4. 综合示例
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:学生成绩表——结构化 dtype 实战(难度⭐⭐⭐)
import numpy as np
# Step 1: Define structured dtype
student_dt = np.dtype([
('name', 'U20'),
('age', 'i4'),
('score', 'f4'),
])
# Step 2: Create array with 5 records
students = np.array([
('Alice', 20, 95.5),
('Bob', 21, 87.0),
('Charlie', 19, 92.3),
('Carol', 22, 78.8),
('David', 20, 88.5),
], dtype=student_dt)
# Step 3: Access and analyze
print("All names:", students['name'])
print("All scores:", students['score'])
print(f"Average score: {students['score'].mean():.2f}")
print(f"Max score: {students['score'].max():.1f}")
# Step 4: Filter
high_mask = students['score'] >= 90
print("High scorers:", students[high_mask]['name'])
# Step 5: Type conversion & precision check
scores_64 = students['score'].astype(np.float64)
print(f"float32 score[0]: {students['score'][0]:.10f}")
print(f"float64 score[0]: {scores_64[0]:.10f}")
# Step 6: Check dtype info
print(f"Score dtype: {students['score'].dtype}")
print(f"Score itemsize: {students['score'].dtype.itemsize} bytes")
print(f"Array total size: {students.nbytes} bytes")
# Step 7: Safe conversion - age to uint8 (0-255 range is fine for ages)
age_uint8 = students['age'].astype(np.uint8)
age_info = np.iinfo(np.uint8)
print(f"uint8 range: [{age_info.min}, {age_info.max}]")
print(f"Ages as uint8: {age_uint8}")
# Step 8: Verify no data loss
assert np.array_equal(students['age'], age_uint8.astype(np.int32))
print("Age conversion: no data loss confirmed")
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
❓ 常见问题
np.array([1.0]) 创建的就是 float64。如果精度不够,数值误差会快速累积。.astype() 返回一个新数组,原数组不变。NumPy 中绝大多数操作都不修改原数组(inplace 操作需要显式指定,如 a *= 2)。np.iinfo 查询整数类型的范围(min/max),np.finfo 查询浮点类型的范围和精度(min/max/eps)。两者都需要传入 dtype 作为参数。📖 小节
本节学习了 NumPy dtype 数据类型体系:
- dtype 体系 — bool、int、uint、float、complex、str、object 等类别,每种有不同位宽
- 指定 dtype — 创建数组时通过
dtype参数指定,支持类型对象和字符串简写 - 类型转换 —
.astype()返回新数组,转换方向需注意精度丢失和溢出风险 - 溢出行为 — 整数溢出静默回绕,用
np.iinfo()/np.finfo()提前检查范围 - 结构化 dtype — 多字段命名类型,适合表格数据,用字段名访问
- 自动提升 — 运算时 NumPy 自动提升到更宽类型,
np.promote_types()可查询
选对 dtype = 省内存 + 防溢出 + 保精度。dtype 不是小问题——选错类型,数据就毁了。
📝 作业
-
测试各整数范围:用
np.iinfo()打印 int8、int16、int32、int64 的最小值和最大值。创建一个 int16 数组,尝试存入 40000,观察溢出结果。 -
创建产品结构化数组:定义结构化 dtype 包含字段:
product_name(U30)、price(float32)、stock(int32)。创建 3 条产品记录,查询价格最高的产品名。 -
float32/float64 计算 π 精度差异:分别用
np.float32(np.pi)和np.float64(np.pi)计算 π 的前 20 位小数,对比两者与math.pi的绝对误差,验证 float32 有效位数约 7 位。