返回首页

Roadmap
更新于 2026年10月25日 全文 1591 字未阅读

第 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],
])

怎么一眼读懂 shape shape 就是一个「每层有几个」的自上而下的清单:

  • (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]]

两个必须知道的细节:

  1. 形状要用元组:np.zeros((2, 3)) ✅,np.zeros(2, 3) ❌(会报 TypeError: Cannot interpret '3' as a data type);
  2. 默认是浮点数,要整数得显式写:
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_rng

rng = 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 个问题,其实都围绕**「怎么造一个数组」和「这个数组是什么」**:

  1. 数组 vs 列表——同类型、能整体运算、快;
  2. np.array(嵌套列表)——几层嵌套几维,shape 从左往右读;
  3. zeros / ones / full——形状用元组,默认浮点;
  4. arange(数几个、不含终点)vs linspace(分几份、含终点);
  5. dtype 创建时指定,转换时用 .astype();
  6. 随机数四个函数,randint 左闭右开;
  7. seed 让结果可复现;
  8. reshape 总数守恒,-1 帮你算;
  9. flatten 给副本,ravel 给视图。

下一章预告 数组造好了,接下来就是怎么从里面取数据。arr[0]、arr[1:3]、arr[[0, 2]]、arr[arr > 10]——这四种写法返回的东西完全不同,还藏着一个「改了切片结果原数组也变了」的经典陷阱。第 2 章讲透。

阅读 --