numpy.eye(shape, …) 传参
本文最后更新于4 天前,其中的信息可能已经过时,如有错误请发送邮件到184874483@qq.com
import numpy
shape = tuple(map(int, input().split()))
print (numpy.eye(shape, k = 1))   

这段代码原本想做什么

目标其实很明确:读取一行输入里的 N M,然后生成一个 N x M 的数组,主对角线是 1,其他位置是 0。想到用 numpy.eye() 来做,这个思路本身是对的,因为 eye 就是专门生成“单位矩阵/对角线矩阵”这一类数组的。


问题定位

import numpy
shape = tuple(map(int, input().split()))
print(numpy.eye(shape, k = 1))

这里主要有两个问题。

第一个主要问题:numpy.eye(shape, ...) 传参方式错了

numpy.eye() 需要的是:

numpy.eye(N, M=None, k=0)

也就是说,它希望拿到的是两个整数:

  • N:行数
  • M:列数

但你传进去的是一个元组 shape,比如:

shape = (3, 3)

于是实际相当于写成了:

numpy.eye((3, 3), k=1)

这就不对了。eye() 不是要一个“形状元组”,而是要两个单独的数字。


第二个问题:k=1 不是主对角线

题目要求的是:

  • 主对角线为 1

主对角线对应的是:

k = 0

而你写的:

k = 1

表示的是“主对角线上方那一条对角线”。

比如 3 x 3 时:

numpy.eye(3, 3, k=1)

会得到:

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

这显然不是题目要的结果。


为什么会错

1. shape 是元组,但 eye() 这里要的是整数参数

前面这句:

shape = tuple(map(int, input().split()))

如果输入是:

3 3

那么 shape 的值就是:

(3, 3)

这是一个元组。

numpy.eye() 的参数设计不是:

numpy.eye(shape)

而是:

numpy.eye(N, M)

也就是它想看到的是:

numpy.eye(3, 3)

而不是:

numpy.eye((3, 3))

这是很多初学者特别容易混淆的地方:

  • 有些函数要“shape 元组”,比如 numpy.zeros(shape)
  • 有些函数要“拆开的行列参数”,比如 numpy.eye(N, M)

你这里就是把这两种写法混在一起了。


2. k 控制的是“第几条对角线”

numpy.eye() 里的 k 不是随便写的,它有明确含义:

  • k=0:主对角线
  • k=1:主对角线上一条
  • k=-1:主对角线下一条

题目写的是 main diagonal,也就是主对角线,所以这里应该用默认值 0,甚至可以不写 k


最小改动修复版本

如果想尽量保留现在的写法,只做最小修改,那么可以这样改:

import numpy
numpy.set_printoptions(legacy='1.13')

shape = tuple(map(int, input().split()))
print(numpy.eye(*shape, k=0))

修改说明

这里我只改了两点。

改动 1:shape 前面加了 *

numpy.eye(*shape, k=0)

这个 *shape 的意思是“拆包”。

如果:

shape = (3, 3)

那么:

numpy.eye(*shape, k=0)

就等价于:

numpy.eye(3, 3, k=0)

这就刚好符合 eye() 要的参数形式了。


改动 2:把 k=1 改成 k=0

因为题目要的是主对角线,所以必须用 k=0

其实这里还可以直接省略:

print(numpy.eye(*shape))

因为 k 默认就是 0


更规范版本

更规范、更清楚的写法,一般不写 shape,而是直接把行列拆开:

import numpy
numpy.set_printoptions(legacy='1.13')

N, M = map(int, input().split())
print(numpy.eye(N, M))

这个版本更适合初学者复习,因为一眼就能看懂:

  • N 是行数
  • M 是列数

不会把“元组 shape”和“两个独立参数”混在一起。


补充说明:为什么这题里还要加 numpy.set_printoptions(legacy='1.13')

题目备注里专门说了要加这一句:

numpy.set_printoptions(legacy='1.13')

这是因为平台希望你的输出格式和它的旧版 NumPy 显示格式一致。

比如它想看到的是:

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

有时候不同版本的 NumPy 在小数显示、对齐方式上会略有不同。为了避免“明明数组对了,但显示格式不一致”,题目才要求你加这一句。

所以这题最好完整写成:

import numpy
numpy.set_printoptions(legacy='1.13')

N, M = map(int, input().split())
print(numpy.eye(N, M))

这道题里最容易混淆的两个点

numpy.zeros(shape)numpy.eye(N, M) 的参数风格不同

这是一个非常典型的易错点。

zeros 常这样写:

numpy.zeros((3, 3))

因为它要的是一个形状元组。

eye 常这样写:

numpy.eye(3, 3)

因为它要的是行数和列数两个参数。

虽然它们都和“生成数组”有关,但参数格式不一样,不能机械照搬。


k=1 并不是“第一个对角线”,而是“上移一条对角线”

很多初学者会误以为:

  • 主对角线是不是 k=1

其实不是。

主对角线永远是:

k=0

你可以把它记成:

“主对角线不偏移,所以是 0;往上偏一条是 1;往下偏一条是 -1。”


如何避免下次再错

你可以记住这 3 条:

  1. numpy.eye() 要的是 N, M,不是一个整体的 shape 元组。
  2. 题目说 main diagonal,就是 k=0
  3. 遇到元组参数时,先想清楚:这个函数是要“整个元组”,还是要“拆开的多个参数”。

本题可直接提交版本

import numpy
numpy.set_printoptions(legacy='1.13')

N, M = map(int, input().split())
print(numpy.eye(N, M))

小练习

自己试着做一个很小的变形题:

题目:输入 N M,输出一个 N x M 的数组,其中“主对角线下一条对角线”为 1,其余位置为 0

提示:

  • 还是用 numpy.eye()
  • 想一想这次 k 应该写多少

补充说明:为什么 numpy.zeros((N, M)) 要写成元组,而 numpy.eye(N, M) 却不用元组?

这个问题非常值得单独讲,因为它看起来像是“NumPy 写法不统一”,但本质上不是你记错了,而是这两个函数的设计目标本来就不一样。

理解了这一点,以后你就不会在 zerosonesreshapeeye 这些函数之间反复混淆。


先说结论

最核心的一句话是:

numpy.zeros() 是“按形状创建数组”,所以它要的是 shape
numpy.eye() 是“按行列创建对角线矩阵”,所以它要的是 NM

也就是说,它们关心的重点不一样,所以参数形式就不一样。


一、numpy.zeros() 关心的是“数组的形状 shape”

numpy.zeros() 的典型写法是:

numpy.zeros(shape)

它的核心参数叫 shape

这里的 shape 是什么?

就是“这个数组每一维多长”。

比如:

numpy.zeros((3, 4))

意思就是:

  • 第 1 维长度是 3
  • 第 2 维长度是 4

也就是创建一个 3 行 4 列 的二维数组。

如果是三维数组:

numpy.zeros((2, 3, 4))

意思就是创建一个形状为 (2, 3, 4) 的三维数组。

所以你会发现,zeros() 面向的是更一般的“n 维数组创建”,它不知道你到底是二维、三维、四维,所以最自然的方式就是:

把所有维度统一打包成一个 shape 传进去。


二、为什么二维时要写成 ((N, M)) 这种样子

很多初学者第一次看到:

numpy.zeros((N, M))

会觉得“为什么有两层括号?”

其实不是“两层参数”,而是:

  • 外层括号:函数调用括号
  • 内层括号:元组

比如:

numpy.zeros((3, 4))

真正的结构是:

  • zeros(...):调用函数
  • (3, 4):把 shape 作为一个元组传进去

你可以把它拆开来看:

shape = (3, 4)
numpy.zeros(shape)

这样就更容易看懂了。

所以不是 zeros 特别奇怪,而是它只收一个主要参数:shape
而二维的 shape 恰好要写成元组 (N, M)


三、numpy.eye() 关心的是“行数和列数”

再看 numpy.eye()

它的典型写法是:

numpy.eye(N, M=None, k=0)

它的设计重点不是“任意 n 维数组”,而是专门生成二维的“对角线矩阵”。

既然它只处理二维,那么它就没必要再让你传一个“统一的 shape 元组”,而是可以直接把:

  • N:行数
  • M:列数

拆成两个独立参数。

比如:

numpy.eye(3, 4)

表示:

创建一个 3 行 4 列 的二维数组,并在主对角线位置放 1

这时它更强调的是“行”和“列”这两个概念,而不是一般意义上的 shape。


四、两者最根本的区别:一个通用,一个专用

可以把它们这样理解:

函数用途参数思路
numpy.zeros()通用创建全 0 数组给我整个形状 shape
numpy.eye()专门创建二维对角线矩阵直接告诉我行数 N、列数 M

所以并不是谁对谁错,而是:

  • zeros() 更通用,支持 1 维、2 维、3 维……
  • eye() 更专门,只做二维矩阵

正因为 zeros() 更通用,所以它必须使用 shape 这种统一表达方式。
正因为 eye() 只处理二维,所以它直接写 N, M 更清楚。


五、为什么 zeros() 不直接写成 numpy.zeros(N, M)

这个问题你可能也会顺着想到。

原因就在于:zeros() 不只是二维。

如果真的设计成:

numpy.zeros(N, M)

那三维怎么办?

numpy.zeros(A, B, C)

四维呢?

numpy.zeros(A, B, C, D)

这样参数个数就不固定了,不利于函数设计。

所以 NumPy 干脆统一规定:

不管几维,都把维度信息打包成一个 shape

于是就有:

numpy.zeros(5)          # 一维
numpy.zeros((3, 4))     # 二维
numpy.zeros((2, 3, 4))  # 三维

这套规则就非常统一。


六、为什么 eye() 不直接也写成 numpy.eye((N, M))

因为 eye() 的重点不是“任意维数组形状”,而是“二维矩阵中的对角线”。

如果写成:

numpy.eye((3, 4))

那就会把“二维矩阵”这个信息弱化掉,反而不如:

numpy.eye(3, 4)

来得直观。

eye() 的参数设计,本身就在强调:

“这是一个二维矩阵,先告诉我多少行,再告诉我多少列。”

而且它还有 k 参数,表示偏移哪条对角线:

numpy.eye(N, M, k=0)

整个接口都很明显是在围绕“二维矩阵”来设计的。


七、一个很重要的补充:zeros 不是一定要元组

这里要特别提醒一下,不然以后又容易形成新的误解。

不是说 numpy.zeros() 永远都必须写元组。

当你创建的是一维数组时,可以直接写整数:

numpy.zeros(5)

这会得到:

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

因为一维的 shape 就只有一个数字 5

但是当你要创建二维或更高维数组时,就必须把多个维度放在元组里:

numpy.zeros((3, 4))
numpy.zeros((2, 3, 4))

所以更准确地说法是:

  • 一维时,shape 可以直接是一个整数
  • 多维时,shape 必须是元组

八、那我已经有一个元组 shape = (N, M),能不能给 eye() 用?

可以,但不能直接写:

numpy.eye(shape)

因为 eye() 要的是两个独立参数,不是一个元组。

这时候要用“拆包”:

shape = (3, 4)
numpy.eye(*shape)

这等价于:

numpy.eye(3, 4)

所以你之前那道题里,下面这句才是对的:

print(numpy.eye(*shape))

而不是:

print(numpy.eye(shape))

九、把这两个函数放在一起对比,就更容易记住了

zeros 的思维方式

“我要创建一个什么形状的数组?”

numpy.zeros((3, 4))

重点在 shape。


eye 的思维方式

“我要创建一个多少行多少列的对角线矩阵?”

numpy.eye(3, 4)

重点在行和列。


十、你可以这样记忆,最省事

以后做题时,可以直接用这套记忆法:

看到这些函数,优先想到“shape”

  • numpy.zeros(shape)
  • numpy.ones(shape)
  • numpy.empty(shape)
  • numpy.reshape(a, shape)

它们都和“形状”关系很强。


看到 eye(),优先想到“行列”

  • numpy.eye(N, M, k=0)

它不是让你交一个 shape,而是让你分别给行和列。


本节小结

这两个函数看起来只是写法不同,其实背后反映的是两种不同的设计思路。

numpy.zeros() 是一个更通用的数组创建函数,它面对的是任意维度数组,所以统一使用 shape。二维时之所以写成 numpy.zeros((N, M)),本质上是在传一个形状元组。

numpy.eye() 则是一个专门生成二维对角线矩阵的函数,它只处理二维,所以直接把 NM 拆成两个参数。这样更符合“矩阵有多少行、多少列”的直觉,也更方便再配合 k 表示对角线偏移。

所以你以后不要把它记成“一个要元组,一个不要元组”这种死规则,而要记成:

  • zeros 是按 shape 创建
  • eye 是按行列创建

这样才不容易混。


小练习

你可以自己先想一想下面三句分别对不对,为什么:

numpy.zeros((2, 3))
numpy.eye((2, 3))
numpy.eye(*(2, 3))

提示:

  • 第一句看的是 zeros 要不要 shape
  • 第二句看的是 eye 能不能直接收元组
  • 第三句看的是 * 拆包之后会发生什么

一次性讲清:shapereshapeeyezeros 到底怎么区分

这几个词之所以特别容易混,不是因为你记性不好,而是因为它们都和“数组长什么样”有关,表面上看起来很像,但它们其实分属不同层面。

如果只靠死记硬背,做题时很容易出现下面这种混乱:

  • shape 当函数用
  • reshape 当“创建数组”的函数用
  • zeros((N, M))eye(N, M) 写混
  • 明明知道都和“形状”有关,但一上手还是不知道该传什么参数

所以这一节不只是分别解释,而是要把这四个东西放在同一个框架里,一口气理清楚。


先给你一个总表

先看最核心的区分表。

名称它是什么作用常见写法
shape数组的“形状信息”表示每一维的长度arr.shape
reshape改变形状的方法/函数把已有数据重新排成新形状numpy.reshape(arr, (3, 3))
zeros创建数组的函数创建一个全是 0 的新数组numpy.zeros((3, 3))
eye创建特殊数组的函数创建对角线为 1 的二维数组numpy.eye(3, 3)

你可以先抓住这四句话:

  • shape:这是“形状本身”
  • reshape:这是“改形状”
  • zeros:这是“按形状创建全 0 数组”
  • eye:这是“按行列创建对角线数组”

如果你能先把这四个定位分开,后面很多题就不容易乱。


一、shape:它不是创建数组,而是在描述数组

shape 的本质是什么

shape 说白了就是:

一个数组每一维有多长。

比如:

import numpy

arr = numpy.array([[1, 2, 3],
                   [4, 5, 6]])
print(arr.shape)

输出:

(2, 3)

这表示:

  • 第 1 维长度是 2
  • 第 2 维长度是 3

也就是:这个数组是 2 行 3 列


shape 最容易混淆的点

很多初学者会把 shape 误以为是一个“做事的函数”,其实不是。

它更像是数组的一个“属性”或者“信息”。

比如:

print(arr.shape)

是在问:

“这个数组现在长什么样?”

而不是:

“帮我创建一个数组”
或者
“帮我改一下数组”。

所以你可以把 shape 记成:

它是“状态描述”,不是“动作”。


二、reshape:它不是新建内容,而是重排已有内容

reshape 是干什么的

reshape 的字面意思就是:

重新塑形,重新调整形状。

比如:

import numpy

arr = numpy.array([1, 2, 3, 4, 5, 6])
new_arr = numpy.reshape(arr, (2, 3))
print(new_arr)

输出:

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

这里发生的事情不是“生成新数字”,而是:

把原来的一维数据,按新的形状重新排布。


reshape 最关键的一点

reshape 不负责“创造数据”,它只负责“重新安排已有数据”。

也就是说:

  • 原来有几个元素,改完后还是几个元素
  • 元素总数必须一致

比如原数组有 6 个元素:

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

那你可以改成:

(2, 3)
(3, 2)
(1, 6)
(6, 1)

因为这些形状对应的总元素数都是 6。

但你不能改成:

(4, 2)

因为 4 * 2 = 8,元素个数对不上。


reshapeshape 的区别

这是最容易一起混的地方。

你可以这样理解:

  • shape 是“现在的形状是什么”
  • reshape 是“我要把它变成什么形状”

比如:

arr = numpy.array([1, 2, 3, 4, 5, 6])
print(arr.shape)

输出的是:

(6,)

这表示“它现在是一维、长度 6”。

然后你再写:

arr2 = numpy.reshape(arr, (2, 3))

这表示“我要把它改成 2 行 3 列”。

所以:

  • shape 是结果信息
  • reshape 是改变动作

三、zeros:按照给定形状,创建一个全 0 数组

zeros 是创建,不是修改

zeros 的作用是:

直接创建一个新数组,并且里面所有元素都是 0。

比如:

import numpy
print(numpy.zeros((2, 3)))

输出:

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

这里不是“把某个已有数组改成 0”,而是:

从头新建了一个 2 x 3 的数组。


为什么 zeros 的参数是 shape

zeros 的重点在于:

“我要创建一个什么形状的数组?”

所以它要的参数就是 shape

比如:

numpy.zeros((3, 4))

这里的 (3, 4) 表示形状。

如果是三维:

numpy.zeros((2, 3, 4))

也照样可以。

这说明 zeros 是一个比较通用的函数,它支持任意维度,所以要统一使用 shape


zerosreshape 的本质区别

这两个也很容易混。

很多人脑子里都是“反正最后都能得到一个 2 x 3 的数组”,于是就糊了。

但实际上:

项目zerosreshape
本质新建数组改已有数组形状
数据来源直接生成 0使用原数组已有数据
是否依赖旧数组不依赖必须依赖

举例:

numpy.zeros((2, 3))

这是直接新建一个全 0 数组。

而:

arr = numpy.array([1, 2, 3, 4, 5, 6])
numpy.reshape(arr, (2, 3))

这是把现有数据重新摆放。

所以它们虽然都能得到“二维数组”,但出发点完全不同。


四、eye:创建对角线为 1 的二维数组

eye 是特殊用途的创建函数

eyezeros 一样,也是“创建数组”的函数。

但它不是创建全 0 数组,而是创建一种特殊的二维数组:

主对角线是 1,其他位置是 0。

比如:

import numpy
print(numpy.eye(3, 3))

输出:

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

为什么 eye 不写成 eye((N, M))

因为 eye 是专门做二维矩阵的,它强调的是:

  • 行数 N
  • 列数 M

所以它的接口设计成:

numpy.eye(N, M=None, k=0)

而不是统一写成 shape 元组。

这和 zeros 不同:

  • zeros 是面向任意维度,所以用 shape
  • eye 是专门处理二维矩阵,所以直接拆成 N, M

eye 还有一个非常容易错的参数:k

比如:

numpy.eye(3, 3, k=0)

表示主对角线是 1。

而:

numpy.eye(3, 3, k=1)

表示主对角线上方那条对角线是 1。

numpy.eye(3, 3, k=-1)

表示主对角线下方那条对角线是 1。

所以 eye 不只是“创建二维数组”,它还和“对角线位置”绑定在一起。


五、把四者放进一个统一思维框架里

现在你可以用一个非常实用的思路去判断。

第一步:我是在“看数组”,还是“做数组”?

如果你是在看数组本来长什么样,那通常想到的是:

arr.shape

这是读取信息。


第二步:如果我要“做数组”,我是“新建”还是“重排”?

如果你是从头创建一个新数组,那通常会想到:

  • numpy.zeros(...)
  • numpy.eye(...)

如果你是拿已有数组重新排形状,那想到的是:

  • numpy.reshape(...)

第三步:如果是“新建”,我要的是普通数组还是特殊数组?

如果只是要一个普通的全 0 数组:

numpy.zeros((N, M))

如果要一个对角线为 1 的特殊二维数组:

numpy.eye(N, M)

六、四者最容易混淆的真正原因

你之所以老觉得这几个东西缠在一起,根本原因是:

它们都和“数组形状”有关,但关注角度不一样。

名称它关注什么
shape现在的形状
reshape改成什么形状
zeros按什么形状新建
eye建一个多少行多少列的对角线矩阵

你会发现,它们都在围绕“形状”打转,但角色完全不同:

  • shape 是描述
  • reshape 是调整
  • zeros 是按形状创建
  • eye 是按行列创建特殊矩阵

一旦你从“角色”角度去区分,而不是从“它们都长得像 NumPy 函数”去区分,脑子就会清楚很多。


七、一个对比示例,放在一起看最清楚

看下面这段代码:

import numpy

arr = numpy.array([1, 2, 3, 4, 5, 6])

print(arr.shape)
print(numpy.reshape(arr, (2, 3)))
print(numpy.zeros((2, 3)))
print(numpy.eye(2, 3))

逐行理解:

第一行

print(arr.shape)

问的是:
这个数组现在的形状是什么?


第二行

print(numpy.reshape(arr, (2, 3)))

意思是:
把现有数据重新排成 2 x 3


第三行

print(numpy.zeros((2, 3)))

意思是:
新建一个 2 x 3 的全 0 数组。


第四行

print(numpy.eye(2, 3))

意思是:
新建一个 2 x 3 的二维数组,并在主对角线位置放 1


八、最实用的记忆法

你以后可以直接套下面这组口诀。

1. 看到 shape

先想:
“这是数组长什么样。”


2. 看到 reshape

先想:
“这是把已有数组重新排形状。”


3. 看到 zeros

先想:
“这是按 shape 新建一个全 0 数组。”


4. 看到 eye

先想:
“这是按行列新建一个对角线矩阵。”


九、再补一个最常见的错法辨析

下面这几句里,哪些对,哪些错,你最好能做到一眼判断。

numpy.zeros((3, 3))
numpy.eye(3, 3)
numpy.reshape([1, 2, 3, 4], (2, 2))
arr.shape

这四句都对。

再看下面:

numpy.zeros(3, 3)
numpy.eye((3, 3))
numpy.shape(arr, (2, 2))

这三句都不对,或者说不是你现在该这么写的形式。

原因分别是:

numpy.zeros(3, 3)

因为 zeros 要的是一个 shape 参数。二维时要写成:

numpy.zeros((3, 3))

numpy.eye((3, 3))

因为 eye 要的是两个独立参数:

numpy.eye(3, 3)

如果你已经有元组 (3, 3),就要拆包:

shape = (3, 3)
numpy.eye(*shape)

numpy.shape(arr, (2, 2))

因为 shape 不是“把数组变形”的动作。
要改形状应该用:

numpy.reshape(arr, (2, 2))

本节小结

这四个 NumPy 名词之所以总让初学者混乱,是因为它们都在处理“数组长什么样”这个问题,但分工并不一样。

shape 只是描述数组当前的形状,本质上是一个结果信息。
reshape 是对已有数组做重新排布,它不创造新数据,只改变排列方式。
zeros 是创建函数,按给定 shape 生成一个全 0 数组。
eye 也是创建函数,但它是专门用来生成二维对角线矩阵的,所以参数写成 N, M,而不是 shape 元组。

如果以后你再遇到类似题目,先不要急着记语法,而是先问自己三个问题:

第一,我现在是在“查看形状”,还是“改变形状”,还是“新建数组”?
第二,如果是新建数组,我要的是普通数组还是特殊的对角线数组?
第三,这个函数要的是 shape,还是要拆开的 N, M

你只要能把这三步在脑子里过一遍,基本就不会再把这几个概念搅在一起了。


小练习

试着判断下面每一句代码分别是在做什么,不要急着运行,先口头说出它的作用:

a = numpy.array([1, 2, 3, 4])
print(a.shape)
print(numpy.reshape(a, (2, 2)))
print(numpy.zeros((2, 2)))
print(numpy.eye(2, 2))

提示:

  • 哪一句是在“读信息”
  • 哪一句是在“重排旧数据”
  • 哪两句是在“新建数组”
  • 哪一句创建的是特殊矩阵

补充说明:为什么 reshape 要求元素总数必须一致?这一点到底该怎么快速判断

这是 NumPy 里一个特别基础、但又特别容易让初学者“知道结论却没真正理解”的点。

很多人记住了:

reshape 前后元素总数必须一致。”

但是一到做题时,还是会下意识地觉得:

“我只是想把它改个形状,为什么不行?”
“明明都是数组,为什么 6 个元素不能直接变成 2 x 4?”
“这个报错到底是在说什么?”

所以这一节我们不只是记规则,而是把背后的原因、判断方法、常见误区一次讲透。


一、先说结论

reshape 不是在“创造新数据”,而是在“重新排列已有数据”。

既然它只是重新排布,那么原来有多少个元素,改完以后就还必须有多少个元素,不能多,也不能少。

这就是为什么 reshape 要求:

新形状的元素总数 = 原数组的元素总数


二、为什么必须一致:因为 reshape 只是“重排”,不是“增删”

先看一个最简单的例子。

import numpy

arr = numpy.array([1, 2, 3, 4, 5, 6])
print(arr)

这个数组里有 6 个元素。

如果你写:

numpy.reshape(arr, (2, 3))

那么 NumPy 做的事情其实只是把这 6 个元素按 2 行 3 列 重新排一下:

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

这里没有产生新元素,也没有丢掉元素,只是换了摆放方式。


那为什么不能变成 (2, 4)

因为 (2, 4) 需要的位置数是:

2 * 4 = 8

也就是说,这个新形状一共需要 8 个格子。

可你原来只有 6 个元素:

1 2 3 4 5 6

那还差 2 个,NumPy 不知道该补什么。

如果它自己随便补 0,那就不是“重排”了,而是“改内容”了。


那为什么也不能变成 (5,)

因为 (5,) 只需要 5 个位置。

但你原来有 6 个元素。

那就会多出来 1 个元素没地方放。

NumPy 也不能自己帮你随便删掉一个,因为这同样不再是“重排”,而是“改内容”。


三、最本质的一句话:位置数必须和元素数一一对应

你可以把 reshape 想成:

把一堆现成的东西,重新装进一个新盒子。

那么:

  • 原来有几个东西
  • 新盒子里有几个位置

这两个数必须完全一样。

只有这样才能做到:

  • 每个元素都有位置放
  • 每个位置都刚好放一个元素

如果位置太多,就会“元素不够”;
如果位置太少,就会“位置不够放”。

所以 reshape 的核心不是“改形状”,而是:

“用一个新布局去容纳同样数量的旧元素。”


四、怎么快速判断能不能 reshape

判断方法其实非常简单:

第一步:先数原数组一共有多少个元素

比如:

arr = numpy.array([1, 2, 3, 4, 5, 6])

这里元素总数是 6。


第二步:算新形状一共需要多少个位置

比如想改成:

(2, 3)

那就算:

2 * 3 = 6

说明新形状也需要 6 个位置,可以改。

如果想改成:

(3, 3)

那就算:

3 * 3 = 9

说明新形状需要 9 个位置,不可以改。


五、最实用的快速判断公式

你可以直接记成一句规则:

一维数组

如果原数组有 n 个元素,那么新形状各维度相乘也必须等于 n


二维数组

比如要改成 (a, b),就看:

a * b

是不是等于原元素总数。


三维数组

比如要改成 (a, b, c),就看:

a * b * c

是不是等于原元素总数。


更一般的说法

新形状里所有维度连乘的结果,必须等于原元素总数。


六、几个例子,帮你彻底形成感觉

例 1:可以改

arr = numpy.array([1, 2, 3, 4, 5, 6])
numpy.reshape(arr, (2, 3))

判断:

2 * 3 = 6

原来也是 6 个元素,所以可以。


例 2:也可以改

numpy.reshape(arr, (3, 2))

判断:

3 * 2 = 6

也等于 6,所以也可以。

这说明:

同样 6 个元素,可以有很多种不同排法。


例 3:不可以改

numpy.reshape(arr, (4, 2))

判断:

4 * 2 = 8

但原来只有 6 个元素,所以不可以。


例 4:一维变二维

arr = numpy.array([10, 20, 30, 40])
numpy.reshape(arr, (2, 2))

判断:

2 * 2 = 4

原来也是 4 个元素,所以可以。


例 5:二维再变一维

arr = numpy.array([[1, 2, 3],
                   [4, 5, 6]])
numpy.reshape(arr, (6,))

原来这个二维数组虽然看起来是“2 行 3 列”,但元素总数还是:

2 * 3 = 6

所以改成 (6,) 也是可以的。

这也说明一个很重要的点:

reshape 关心的是元素总数,不关心你原来是几维。


七、为什么很多初学者会在这里犯错

错误 1:只看“行列”,不看“总数”

很多人一看到要改成二维数组,就开始盯着“几行几列”,但忘了最关键的是:

行 * 列 = 元素总数

比如原数组有 8 个元素,你想当然写成 (3, 3),觉得“3 行 3 列很整齐”,但:

3 * 3 = 9

根本对不上。

所以不是“好不好看”,而是“乘积对不对”。


错误 2:误以为 reshape 会自动补 0

有些初学者会想:

“少几个位置,NumPy 不能自动补吗?”

不能。

因为 reshape 的职责不是“补全数组”,而是“重新排列已有元素”。

如果你真的想补 0,那就应该先自己创建或拼接数据,而不是指望 reshape 帮你补。


错误 3:误以为 reshape 会自动截断多余元素

同理,有人也会想:

“多出来的元素不能自动扔掉吗?”

也不能。

NumPy 不会替你决定哪些元素该保留、哪些该删掉。

这是程序员自己的逻辑决定,不是 reshape 的职责。


八、做题时怎么一眼判断

这里给你一个特别实用的做题流程。

方法一:直接乘

比如原数组有 12 个元素,你看到几个备选形状:

  • (3, 4)
  • (2, 5)
  • (2, 2, 3)

你直接口算:

3 * 4 = 12   ✅
2 * 5 = 10   ❌
2 * 2 * 3 = 12   ✅

只要乘积等于 12,就可以。


方法二:先分解元素总数

比如原数组有 12 个元素。

你可以先想:

12 = 1*12 = 2*6 = 3*4 = 2*2*3

那能改成的常见形状就会很快想到:

  • (1, 12)
  • (12, 1)
  • (2, 6)
  • (6, 2)
  • (3, 4)
  • (4, 3)
  • (2, 2, 3) 等等

这个方法对选择题特别有用。


九、再补一个非常实用的知识:-1 自动推导

NumPy 允许你在 reshape 里写一个 -1,表示:

“这一维你帮我自动算出来。”

比如:

arr = numpy.array([1, 2, 3, 4, 5, 6])
print(numpy.reshape(arr, (2, -1)))

输出:

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

因为原来总共有 6 个元素,已经知道第一维是 2,那么第二维就必须是:

6 / 2 = 3

所以 -1 会自动变成 3。


为什么这也还是遵守“总数一致”?

因为 -1 不是例外,它只是让 NumPy 帮你算缺的那个数字,但本质上还是在保证:

总元素数一致。

比如:

numpy.reshape(arr, (3, -1))

原来有 6 个元素,第一维是 3,那第二维就自动算成:

6 / 3 = 2

于是就是 (3, 2)

但如果你写:

numpy.reshape(arr, (4, -1))

原来有 6 个元素,第一维是 4,那另一维就得是:

6 / 4 = 1.5

不是整数,说明根本没法整齐排布,所以就会报错。


十、你可以把 reshape 想成“换盒子装球”

这是最适合初学者的直觉模型。

假设你现在有 6 个球:

● ● ● ● ● ●

你想把它们装进一个新盒子。

盒子 1:2 行 3 列

一共 6 个格子:

□ □ □
□ □ □

刚好放下,可以。


盒子 2:3 行 3 列

一共 9 个格子:

□ □ □
□ □ □
□ □ □

你只有 6 个球,还差 3 个,不能。


盒子 3:1 行 5 列

一共 5 个格子:

□ □ □ □ □

你有 6 个球,多出 1 个没地方放,也不能。


所以本质上,reshape 就是:

换盒子,但不改变球的数量。


十一、本节小结

reshape 要求元素总数必须一致,根本原因在于:它不是在创建新数据,也不是在删除旧数据,而只是把同一批元素重新安排位置。

因此,新形状中一共有多少个位置,必须和原数组中一共有多少个元素完全一致。位置多了,元素不够放;位置少了,元素放不下。NumPy 不会替你自动补元素,也不会替你自动删元素,因为那已经不是“重排”了,而是在修改数据本身。

所以以后你判断一个 reshape 能不能做,不要靠感觉,也不要看“这个形状顺不顺眼”,只做一件事就够了:

把新形状的各维度乘起来,看它是否等于原数组的元素总数。

只要乘积相等,就可以;不相等,就不可以。


练习

试着先不要运行代码,直接判断下面哪些可以成功,哪些会报错,并说出理由。

import numpy

arr = numpy.array([1, 2, 3, 4, 5, 6, 7, 8])

print(numpy.reshape(arr, (2, 4)))
print(numpy.reshape(arr, (4, 2)))
print(numpy.reshape(arr, (3, 3)))
print(numpy.reshape(arr, (2, 2, 2)))

提示:

先数原数组有几个元素,再分别算:

  • 2 * 4
  • 4 * 2
  • 3 * 3
  • 2 * 2 * 2

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇