第 1.3 节 常见问题#
这一节收集了初学者在 NumPy 入门阶段最常卡的 10 个问题。每个问题都给出直接答案,能跑代码的都附上完整示例。
怎么用这一节 不用通读。遇到问题用 Ctrl+F 搜关键词,直接跳到对应那条。
准备一下数据,下面每题都会用到:
import numpy as np
scores = np.array([80, 90, 75, 100])
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])Q1:NumPy 数组与 Python 列表有什么区别?#
答: 五个字:类型统一、运算整体、速度快。
| Python 列表 | NumPy 数组 | |
|---|---|---|
| 元素类型 | 可以混着放(数字、文字、对象) | 必须全部同类型 |
| 整体运算 | 不支持,要写循环 | 支持,arr * 2 逐元素乘 2 |
| 内存 | 分散存储,每个元素是独立对象 | 连续存储的一块内存 |
| 速度 | 慢(解释执行,逐个处理) | 快几十倍(底层批量运算) |
| 多维 | 用嵌套列表凑,很别扭 | 原生支持 shape / ndim |
a_list = [1, 2, 3]
a_array = np.array([1, 2, 3])
print(a_list * 2) # [1, 2, 3, 1, 2, 3] ← 复制两遍
print(a_array * 2) # [2 4 6] ← 每个数乘 2实际用起来的差别,比如你想把一列价格打八折:
# 列表:得写循环
prices_list = [100, 200, 300]
discounted = [p * 0.8 for p in prices_list]
# 数组:一行
prices = np.array([100, 200, 300])
discounted = prices * 0.8初学阶段最容易踩的坑 你写
arr = np.array([1, 2, 3])之后,就再也别用arr + [1]这种写法了—— 列表的+是拼接,数组的+是逐元素相加,语义完全不同。 一旦数据变成数组,就全程用数组的逻辑。
Q2:如何使用 np.array() 创建一维、二维和多维数组?#
答: 传几层嵌套就是几维。
# 一维
a = np.array([1, 2, 3])
print(a.shape) # (3,)
# 二维:外层是行,内层是列
b = np.array([[1, 2, 3], [4, 5, 6]])
print(b.shape) # (2, 3)
# 三维
c = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(c.shape) # (2, 2, 2)所有行必须一样长,否则会报错:
np.array([[1, 2], [3, 4, 5]])
# ValueError: setting an array element with a sequence.
# The requested array has an inhomogeneous shape after 1 dimensions.
# The detected shape was (2,) + inhomogeneous part.一行写不开就换行写,NumPy 不在意排版:
c = np.array([
[1, 2, 3],
[4, 5, 6],
])怎么一眼读懂
shapeshape就是一个「每层有几个」的自上而下的清单:
(5, 4)= 5 行,每行 4 个;(2, 3, 4)= 2 块,每块 3 行,每行 4 个。从左往右读,第一个数就是最外层(行数)。
Q3:np.zeros()、np.ones() 和 np.full() 有什么区别?#
答: 只是「填什么值」不一样,形状参数完全一样。
print(np.zeros((2, 3))) # 全 0
print(np.ones((2, 3))) # 全 1
print(np.full((2, 3), 7.5)) # 全 7.5输出:
[[0. 0. 0.]
[0. 0. 0.]]
[[1. 1. 1.]
[1. 1. 1.]]
[[7.5 7.5 7.5]
[7.5 7.5 7.5]]两个必须知道的细节:
- 形状要用元组:
np.zeros((2, 3))✅,np.zeros(2, 3)❌(会报TypeError: Cannot interpret '3' as a data type); - 默认是浮点数,要整数得显式写:
np.zeros((2, 3), dtype=int)什么时候用哪个?
zeros—— 当「初始值都是 0」,比如累计求和的缓冲区、占位矩阵;ones—— 当「初始值都是 1」,比如每项权重的起点;full—— 当你心里有个具体的初始值,比如「先全填 -1 表示无效数据」。另外还有
np.empty((2, 3)):只占内存不赋值,里面是随机垃圾值。别拿它当zeros用。
Q4:np.arange() 和 np.linspace() 分别适用于什么场景?#
答: 看你是想指定间隔还是指定个数。
np.arange(start, stop, step) | np.linspace(start, stop, num) | |
|---|---|---|
| 你指定的是 | 间隔(step) | 个数(num) |
| 包含终点 | ❌ 不包含 | ✅ 包含 |
| 输出类型 | 整数或浮点 | 一定是浮点 |
| 典型场景 | 索引、循环计数、整数序列 | 坐标轴刻度、插值点、百分比 |
np.arange(0, 10, 2) # [0 2 4 6 8]
np.linspace(0, 10, 5) # [ 0. 2.5 5. 7.5 10. ]判断方法:
- 「我要 5 个数,从 0 到 1」→
linspace(0, 1, 5) - 「我要 步长 0.5 的数」→
arange(0, 1, 0.5) - 「我要索引 0 到 n」→
arange(n)
linspace的一个隐藏细节 默认包含终点。如果你想「把 0~1 这段分成 5 份」(而不是取 5 个点),加endpoint=False:np.linspace(0, 1, 5, endpoint=False) # [0. 0.2 0.4 0.6 0.8]画图时这个参数经常用到——不然最后一个点会跟下一个区间重叠。
Q5:np.arange() 的结束值是否包含在生成的序列中?#
答: 不包含。 和 Python 的 range() 完全一样。
print(np.arange(0, 10, 2)) # [0 2 4 6 8] —— 10 不在里面
print(np.arange(5)) # [0 1 2 3 4] —— 只有 0~4想包含终点,就把终点加一:
print(np.arange(0, 11)) # [0 1 2 3 4 5 6 7 8 9 10]
print(np.arange(10, 31, 5)) # [10 15 20 25 30]这是初学阶段最常见的 off-by-one。 「我要 1 到 10 共 10 个数」的正确写法:
np.arange(1, 11) # ✅ np.arange(1, 10) # ❌ 只有 9 个数(1~9)数个数就用
np.arange(n)——从 0 开始数 n 个,永远不会错。
Q6:如何指定 NumPy 数组的数据类型 dtype?#
答: 在创建的时候传 dtype=。
np.array([1, 2, 3], dtype=np.float64).dtype # float64
np.array([1, 2, 3], dtype=np.int32).dtype # int32
np.array([1, 2, 3], dtype=str).dtype # <U1
np.zeros((2, 3), dtype=int) # 整数数组常见类型速查:
| 类型 | 说明 |
|---|---|
int64 / int32 | 整数 |
float64 / float32 | 浮点数(float64 是默认) |
bool | 布尔值,True / False |
str | 字符串 |
转换成别的类型用 .astype():
a = np.array([1, 2, 3])
print(a.astype(float).dtype) # float64
print(a.astype(str)) # ['1' '2' '3']两个实用技巧
# 1. 强制整数(会截断,不是四舍五入) np.array([1.9, 2.7]).astype(int) # [1 2] # 2. 存文件时省内存:float32 是 float64 的一半 big = np.random.rand(1000, 1000).astype(np.float32)数据量大的时候,
float32能让内存占用直接减半,机器学习里非常常用。
Q7:如何生成指定范围内的随机整数和随机浮点数?#
答: 四个常用函数。
# 随机整数:[0, 10),含 0 不含 10
np.random.randint(0, 10, size=5)
# 随机浮点数:[0, 1)
np.random.rand(5)
# 随机浮点数:[10, 20)
np.random.uniform(10, 20, size=5)
# 正态分布:中心 100,标准差 15
np.random.normal(loc=100, scale=15, size=5)指定形状用 size=(行, 列):
np.random.randint(1, 100, size=(3, 4)) # 3 行 4 列
np.random.rand(3, 4) # 3 行 4 列
randint的边界规则是「左闭右开」:randint(0, 10)给的是 0 到 9,永远给不出 10。 想要 1 到 100 的闭区间,写np.random.randint(1, 101)。
Q8:如何设置随机种子,使随机结果可以复现?#
答: np.random.seed(42)。
np.random.seed(42)
print(np.random.rand(3))[0.37454012 0.95071431 0.73199394]再设一次同样的种子,结果一模一样:
np.random.seed(42)
print(np.random.rand(3))[0.37454012 0.95071431 0.73199394]为什么这很重要?
- 「昨天跑出来 0.92,今天跑出来 0.71」——设了种子就知道不是随机的问题;
- 你的作业、教程、博客文章,别人跑出来应该跟你一样;
- 调试时能确定「是逻辑错了」还是「只是随机」。
新版 NumPy 的推荐写法:
default_rngrng = np.random.default_rng(42) print(rng.random(3)) print(rng.integers(0, 10, size=5))好处是每个生成器各管各的,不会互相影响:
rng1 = np.random.default_rng(42) # 拿数据的随机性 rng2 = np.random.default_rng(7) # 初始化的随机性老写法
np.random.seed()依然能用,只是种子是「全局」的。两种都行,关键是记得设。
Q9:reshape() 对数组的元素总数有什么要求?#
答: 必须完全相等。 总数守恒,多一个少一个都不行。
nums = np.arange(12)
nums.reshape(3, 4) # ✅ 3 × 4 = 12
nums.reshape(5, 3) # ❌ 5 × 3 = 15 ≠ 12报错:
ValueError: cannot reshape array of size 12 into shape (5,3)12 个元素能摆成的形状:
12 → (12,) (6,2) (4,3) (3,4) (2,6) (2,2,3) ...用 -1 让 NumPy 帮你算那一维:
nums.reshape(3, -1).shape # (3, 4)
nums.reshape(-1, 2).shape # (6, 2)
nums.reshape(-1, 3).shape # (4, 3)一个实用技巧 「把这 20 个数摆成 5 行 4 列」——你根本不用算:
flat = np.arange(20) flat.reshape(5, 4)写错了 NumPy 会立刻告诉你总数不对,比你自己算靠谱。
Q10:reshape()、flatten() 和 ravel() 分别有什么作用?#
答: 三个都是变形,但方向和内存行为不同。
| 方法 | 干什么 | 返回什么 | 改结果会改原数组吗 |
|---|---|---|---|
reshape(shape) | 换形状(元素总数不变) | 视图(尽量不复制) | 可能会 |
flatten() | 拍扁成一维 | 一定是新副本 | 不会 |
ravel() | 拍扁成一维 | 通常是视图 | 可能会 |
m = np.array([[1, 2], [3, 4]])
print(m.reshape(4)) # [1 2 3 4]
print(m.flatten()) # [1 2 3 4]
print(m.ravel()) # [1 2 3 4]内存上的区别:
m = np.array([[1, 2], [3, 4]])
f = m.flatten()
f[0] = 99
print(m) # [[1 2] [3 4]] ← 原数组没变
r = m.ravel()
r[0] = 99
print(m) # [[99 2] [3 4]] ← 原数组被改了!需要副本就写
.copy()。r = m.ravel().copy() # 保险写法记住一句话:切片和 reshape 给你的是「透过窗户看」,flatten 给你的是「复制一份带走」。 这个坑第 2 章会专门展开讲,因为它比看起来严重得多。
小结#
这一节的 10 个问题,其实都围绕**「怎么造一个数组」和「这个数组是什么」**:
- 数组 vs 列表——同类型、能整体运算、快;
np.array(嵌套列表)——几层嵌套几维,shape从左往右读;zeros/ones/full——形状用元组,默认浮点;arange(数几个、不含终点)vslinspace(分几份、含终点);dtype创建时指定,转换时用.astype();- 随机数四个函数,
randint左闭右开; seed让结果可复现;reshape总数守恒,-1帮你算;flatten给副本,ravel给视图。
下一章预告 数组造好了,接下来就是怎么从里面取数据。
arr[0]、arr[1:3]、arr[[0, 2]]、arr[arr > 10]——这四种写法返回的东西完全不同,还藏着一个「改了切片结果原数组也变了」的经典陷阱。第 2 章讲透。