返回首页

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

第 1.1 节 核心知识点#

预计学习时长: 35 分钟

本节目标:

  • 会用 np.array() 把列表变成数组,也知道二维数组怎么造;
  • 会用 zeros / ones / full 生成指定形状的数组;
  • 分得清 arange()(数几个)和 linspace()(间隔多大);
  • 会生成随机整数和随机浮点数,并且能锁住随机种子让结果复现;
  • 会用 reshape() 变形,并且知道它对元素总数有什么硬要求。

先说清楚一件事:NumPy 数组到底是什么#

在动手之前,先建立一个直觉,不然每个函数都会显得很突兀。

NumPy 数组(ndarray)就是「一堆同类型数字排成的方块」。

拿 Excel 打个比方:

  • Excel 的一个单元格,可以放文字、数字、日期;
  • NumPy 数组的每一个格子只能是数字(或者全是文字,或者全是布尔值),不能混着来。

这个限制听起来很烦,但换来的是巨大的速度优势——因为所有元素类型一样,NumPy 就能把它们打包成一块连续的内存,用 C 语言那种「批量算」的方式一次处理 thousands 个数字,而不是像 Python 循环那样一个一个调用。

一句话记住三个必查属性

arr.ndim    # 几维
arr.shape   # 每个维度多长
arr.dtype   # 元素类型

任何一个陌生的 NumPy 数组,先打这三行,八成就知道它是什么了。

顺便提一句常见的「兄弟」:

结构是什么长什么样
np.ndarray多维数组[120., 135., 150.]
np.array(一维时)就是普通 ndarrayshape = (3,)
Python list什么都能装的清单[120, 135, 150]

数组和列表最大的区别:数组里所有元素类型必须一样,而且它支持「整体运算」——arr * 2 是每个数都乘 2,而 list * 2 是把列表复制两份。

知识点 1:从列表创建数组(Create Arrays from Lists)#

最常见的做法:np.array()#

import numpy as np

scores = np.array([80, 90, 75, 100])
print(scores)

输出:

[ 80  90  75 100]

注意输出里没有逗号——这是 NumPy 数组和 Python 列表最直观的区别。

看看它的属性:

print(scores.shape)    # (4,)  一维,4 个元素
print(scores.ndim)     # 1     一维
print(scores.dtype)    # int64  整数

shape 的读法 (4,) 读作「4 个,每个一维」。逗号是关键:

  • (4,) —— 一维数组,4 个元素;
  • (4, 1) —— 二维数组,4 行 1 列;
  • (4, 3) —— 二维数组,4 行 3 列。

一维数组的 shape 里那个逗号不能省,这就是为什么 np.array([1,2,3]).shape 是 (3,) 而不是 (3)。

造二维数组:嵌套列表#

matrix = np.array([
    [1, 2, 3],
    [4, 5, 6],
])
print(matrix)

输出:

[[1 2 3]
 [4 5 6]]

关键点:外层列表有几个元素,就是几行;每个内层列表有几个元素,就是几列。

print(matrix.shape)    # (2, 3)  2 行 3 列
print(matrix.ndim)     # 2

三维数组:再套一层#

cube = np.array([
    [[1, 2], [3, 4]],
    [[5, 6], [7, 8]],
])
print(cube.shape)    # (2, 2, 2)

读法:「2 块,每块 2 行,每行 2 个」。

本课程要用的示例表#

后面 4 章一直用它,先建好:

# 5 个城市 × 4 个季度的销售额(万元)
sales = np.array([
    [120.0, 135.0, 150.0, 165.0],
    [ 80.0,  92.0,  88.0, 105.0],
    [200.0, 210.0, 195.0, 230.0],
    [ 60.0,  72.0,  85.0,  78.0],
    [150.0, 145.0, 160.0, 175.0],
])

city_names    = np.array(["北京", "上海", "广州", "深圳", "成都"])
quarter_names = np.array(["Q1", "Q2", "Q3", "Q4"])

print(sales.shape)    # (5, 4)
print(sales.dtype)    # float64

数组和列表的运算差别有多大#

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

这是初学者最容易懵的地方。 列表的 * 是「重复」,数组的 * 是「逐元素相乘」。

再加一个:

print(a_list + a_list)   # [1, 2, 3, 1, 2, 3]  ← 拼接
print(a_array + a_array) # [2 4 6]              ← 每个数加一遍

从列表转数组的正确姿势

np.array(my_list)              # ✅
np.asarray(my_list)            # ✅ 如果已经是数组就不复制
np.array(my_list, dtype=float) # ✅ 顺便指定类型

记住一句话:np.array() 接受「像数组一样的东西」,包括嵌套列表。

指定数据类型 dtype#

print(np.array([1, 2, 3], dtype=np.float64).dtype)   # float64
print(np.array([1, 2, 3], dtype=np.int32).dtype)      # int32
print(np.array([1, 2, 3], dtype=str).dtype)           # <U1

一个真实的坑

np.array([1, 2, 3.5])   # dtype 是 float64

只要数组里有一个小数,整个数组都会变成浮点数。这是 NumPy「所有元素同类型」的直接后果。 你的金额列如果混进了一个 None 或者空字符串,NumPy 会变成 object 类型(装 Python 对象的万能类型),后面所有运算都会变慢变难写。所以造数组前先确认数据是干净的。

知识点 2:创建全零数组与全一数组(Zeros and Ones)#

有时候你需要先开一块「空地」,往里面填东西。

np.zeros()#

print(np.zeros((3, 4)))

输出:

[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

注意参数是元组 (3, 4)——括号不能省,写成 np.zeros(3, 4) 会被理解成两个参数,是另一个意思。

np.ones()#

print(np.ones((2, 3)))

输出:

[[1. 1. 1.]
 [1. 1. 1.]]

np.full()#

指定一个固定值:

print(np.full((2, 3), 7.5))

输出:

[[7.5 7.5 7.5]
 [7.5 7.5 7.5]]

三个函数的对比#

函数填什么例子
np.zeros(shape)全 0np.zeros((3, 4))
np.ones(shape)全 1np.ones((3, 4))
np.full(shape, value)全是你给的值np.full((3, 4), 9)

默认类型是浮点数#

print(np.zeros((2,)).dtype)    # float64
print(np.ones((2,)).dtype)     # float64

想要整数数组,显式写 dtype

np.zeros((3, 4), dtype=int)
np.ones((3, 4), dtype=np.int32)

np.full((2, 2), 3, dtype=int) 也是同理。

np.empty():只占位不赋值#

print(np.empty((2, 2)))

输出(每次可能不同):

[[1.2e-34 3.4e+38]
 [1.2e-34 3.4e+38]]

empty 只是申请一块内存,里面的内容是上次用过的垃圾数据。

别把 empty 当 zeros 用。 它的速度优势(省一次清零)在实际业务里几乎没用,但「读到随机垃圾值」的 bug 会让你查半天。初学阶段统一用 zeros。

知识点 3:生成等差数列与等间隔序列(Arange and Linspace)#

np.arange():我告诉你数几个#

print(np.arange(5))

输出:

[0 1 2 3 4]

带起点和步长:

print(np.arange(0, 10, 2))

输出:

[0 2 4 6 8]

也可以只给终点:

print(np.arange(3, 7))

输出:

[3 4 5 6]

arange() 的结束值不包含在内(和 Python 的 range() 一样)。

list(range(0, 10, 2))    # [0, 2, 4, 6, 8]
np.arange(0, 10, 2)      # [0 2 4 6 8]

想数清楚「从 0 到 10(含 10)一共 11 个」这类问题,永远用 np.arange(0, 11),别用 np.arange(0, 10) 然后硬算。

np.linspace():我告诉你间隔多大#

print(np.linspace(0, 1, 5))

输出:

[0.   0.25 0.5  0.75 1.  ]

参数含义:linspace(起点, 终点, 要几个数)。

它保证首尾一定包含、相邻间隔一定相等。所以:

print(np.linspace(0, 10, 5))
[ 0.   2.5  5.   7.5 10. ]

两个怎么选?#

np.arange(start, stop, step)np.linspace(start, stop, num)
你指定的是间隔(step)个数(num)
包含终点吗不包含包含
输出类型整数或浮点一定是浮点

记忆方法:

  • arange —— 像 range,「从 0 数到几」,数几个;
  • linspace —— line space,把线段等分成几份。
np.arange(0, 10, 2)      # 5 个数:0 2 4 6 8
np.linspace(0, 10, 5)    # 5 个数:0 2.5 5 7.5 10

还有一个细节:linspace 默认包含终点。如果你想「等分成 5 段」而不是「取 5 个点」,加 endpoint=False:

print(np.linspace(0, 1, 5, endpoint=False))
# [0.  0.2 0.4 0.6 0.8]

知识点 4:生成随机数组(Random Array Generation)#

先设个种子,后面所有数字才好对照 随机数默认每次跑都不一样,你没法判断「我看到的输出对不对」。 所以本文下面每个随机例子的第一行都写了 np.random.seed(42)—— 你原样复制过去跑,数字跟我写的一模一样。

随机整数:np.random.randint()#

np.random.seed(42)
print(np.random.randint(0, 10, size=5))

输出:

[6 3 7 4 6]

三个参数:randint(下限, 上限, 个数)。下限包含,上限不包含——和 range 一个规矩。

指定形状:

np.random.seed(42)
print(np.random.randint(1, 100, size=(3, 4)))

输出:

[[52 93 15 72]
 [61 21 83 87]
 [75 75 88 24]]

随机浮点数:np.random.rand()#

np.random.seed(42)
print(np.random.rand(3))

输出:

[0.37454012 0.95071431 0.73199394]

rand 生成的是 0 到 1 之间的浮点数,不给下限上限。

想要任意区间,用 uniform():

np.random.seed(42)
print(np.random.uniform(10, 20, size=5))

输出:

[13.74540119 19.50714306 17.31993942 15.98658484 11.5601864 ]

正态分布:np.random.normal()#

真实数据里很多指标是「中间多、两头少」的,比如人的身高、测量误差。这时候用正态分布更像真的:

np.random.seed(42)
print(np.random.normal(loc=100, scale=15, size=5))

输出:

[107.4507123   97.92603548 109.71532807 122.84544785  96.48769938]
  • loc 是中心(这里是 100);
  • scale 是标准差(这里是 15)。

让随机结果可以复现:np.random.seed()#

上面加了 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]

种子的价值:让 bug 可复现。 「我这个模型昨天跑出来 0.92,今天跑出来 0.71」——如果设了种子,两次结果一定一样,你就能确定是数据变了而不是「随机」。 写实验代码、做课程作业、跟别人协作,第一行就写 np.random.seed(42),这个习惯能省你很多时间。

新版 NumPy 的推荐写法 NumPy 1.17 之后推荐用独立的随机数生成器,好处是不同实验可以用不同的种子,互不干扰:

rng = np.random.default_rng(42)
print(rng.random(3))
print(rng.integers(0, 10, size=5))

老写法 np.random.seed() 依然能用,只是全局的种子会互相影响。初学阶段两种都可以,记住「设了种子结果就固定」这一点就行。

知识点 5:调整数组形状(Reshaping Arrays)#

reshape():换个角度看同一堆数字#

nums = np.arange(12)
print(nums)
[ 0  1  2  3  4  5  6  7  8  9 10 11]

把它摆成 3 行 4 列:

print(nums.reshape(3, 4))

输出:

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

元素总数必须完全对得上。 12 个元素变 3×4 = 12 ✓。

如果对不上,会直接报错:

nums.reshape(5, 3)
ValueError: cannot reshape array of size 12 into shape (5,3)

reshape 的硬规则:元素总数必须守恒。 12 个元素能摆成的形状:(12,)、(6,2)、(4,3)、(3,4)、(2,6)、(2,2,3)…… 不能摆成 (5,3)(15 个)。 报错了先算一下「总数 ÷ 行数」能不能整除。

用 -1 代替「我不知道」的那个维度#

12 个元素,你想摆成 3 行几列?不用算:

print(nums.reshape(3, -1).shape)    # (3, 4)

-1 的意思是「这一维你帮我算」,一个 -1 就够了,不能出现两个。

reshape 和 resize 的区别#

a = np.arange(6)
b = a.reshape(2, 3)     # b 是新「视角」,元素还是那 6 个
print(b)
[[0 1 2]
 [3 4 5]]

reshape 不会改变元素总数,也不会修改原数组(它只是换个姿势看同一批数据,底层内存还是那块)。

而 resize() 是真的往里塞东西或丢东西:

a = np.arange(6)
a.resize((3, 3))       # 元素不够会补 0,多了会丢
print(a)
[[0 1 2]
 [3 4 5]
 [0 0 0]]

初学阶段只用 reshape,别碰 resize。 resize 会真的改数据(可能补零可能截断),一不小心就出 bug。

flatten() 和 ravel():拍扁成一条#

m = np.array([[1, 2], [3, 4]])
print(m.flatten())    # [1 2 3 4]
print(m.ravel())      # [1 2 3 4]

两个输出一样,区别在内存:

flatten()ravel()
返回一定是一份新副本通常是原数组的视图(能省则省)
改结果会改原数组吗不会可能会
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]] ← 原数组被改了!

需要副本就写 flatten(),或者显式 .copy()。

r = m.ravel().copy()    # 保险写法

这个「视图 / 副本」的区别是 NumPy 最大的坑之一,第 2 章会专门讲透。

顺便认识一下转置#

print(sales.T.shape)    # (4, 5)  5 行 4 列 → 4 行 5 列

.T 是「转置」的意思,把行和列对调。这个在第 4 章会经常用到。

本节小结#

  • 三个必查属性:ndim(几维)、shape(每维多长)、dtype(元素类型)。
  • np.array(列表) 造数组,嵌套几层就是几维;np.array([1,2,3]).shape 是 (3,),逗号不能省。
  • np.zeros(shape) / np.ones(shape) / np.full(shape, 值),默认是浮点数,要整数显式写 dtype=int。
  • np.arange(start, stop, step) 数几个、不含终点;np.linspace(start, stop, num) 分几份、含终点。
  • 随机数:randint(低, 高, size)、rand(n)、uniform(低, 高, n)、normal(loc, scale, n)。
  • np.random.seed(42) 让结果可复现——写实验代码时第一行就写它。
  • reshape 要求元素总数守恒,-1 可以让 NumPy 帮你算;flatten() 给副本,ravel() 给视图。

下一节预告 下一节用 5 道练习把上面这些练熟——包括「把一维数组掰成二维」「用 linspace 生成一整年的月份刻度」这种实战里天天干的活。

阅读 --