为什么Numpy中的0维数组不被视为标量?

89 投票
2 回答
44617 浏览
提问于 2025-04-15 11:11

当然,一个0维数组是标量(也就是单一的数值),但是Numpy似乎并不这么认为……我是不是漏掉了什么,或者是我对这个概念理解错了?

>>> foo = numpy.array(1.11111111111, numpy.float64)
>>> numpy.ndim(foo)
0
>>> numpy.isscalar(foo)
False
>>> foo.item()
1.11111111111

2 个回答

6

你需要稍微不同的方式来创建标量数组:

>>> x = numpy.float64(1.111)
>>> x
1.111
>>> numpy.isscalar(x)
True
>>> numpy.ndim(x)
0

numpy中的标量 可能和你从纯数学角度理解的概念有点不同。我猜你是在想标量矩阵吧?

181

其实不用想得太复杂,这样对心理健康和长寿都更好。

Numpy的标量类型有点奇怪,因为没有一种优雅且一致的方法可以把1x1的矩阵降级为标量类型。虽然从数学上讲,它们是一样的,但在代码处理上却完全不同。

如果你做过一些科学计算,最终你会希望像max(a)这样的函数能在各种大小的矩阵上都能工作,包括标量。数学上,这个期望是合理的。然而对于程序员来说,这意味着在Numpy中,任何表示标量的东西都应该有.shape和.ndim属性,这样ufuncs就不需要对输入进行21种可能的标量类型的显式类型检查。

另一方面,它们也应该能和现有的Python库兼容,而这些库是会对标量类型进行显式类型检查的。这就成了一个两难的局面,因为Numpy的ndarray在降到标量时必须单独改变类型,而没有办法知道这种情况是否发生,除非对每次访问都进行检查。实际上,这样做可能会让处理标量类型变得非常慢。

Numpy开发者的解决方案是让自己的标量类型同时继承ndarray和Python的标量类型,这样所有标量也都有.shape、.ndim、.T等属性。1x1的矩阵仍然会存在,但如果你知道自己要处理的是标量,就不推荐使用它。理论上这应该没问题,但有时你还是会看到一些地方处理得不太好,内部的丑陋细节暴露出来:

>>> from numpy import *
>>> a = array(1)
>>> b = int_(1)
>>> a.ndim
0
>>> b.ndim
0
>>> a[...]
array(1)
>>> a[()]
1
>>> b[...]
array(1)
>>> b[()]
1

其实a[...]a[()]不应该返回不同的东西,但它们确实返回了不同的结果。虽然有提议要改变这一点,但看起来他们忘了为1x1数组完成这个工作。

还有一个可能更大、也许无法解决的问题是,Numpy的标量是不可变的。因此,把一个标量“喷涂”到ndarray中,数学上讲就是把一个数组压缩成标量的反操作,这个实现起来非常麻烦。你实际上不能扩展一个Numpy标量,它不能被定义为转换成ndarray,尽管newaxis在它身上神秘地有效:

>>> b[0,1,2,3] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'numpy.int32' object does not support item assignment
>>> b[newaxis]
array([1])

在Matlab中,扩展标量的大小是一个完全可以接受且简单的操作。而在Numpy中,你必须在每个你认为可能从标量开始并最终得到数组的地方都加上a = array(a)。我理解Numpy必须这样做以便与Python兼容,但这并不改变许多新手在这方面感到困惑的事实。有些人清楚地记得曾经为这种行为而挣扎并最终坚持下来,而另一些人则因为太过迷茫,通常会留下深深的无形心理创伤,时常在他们最无辜的梦中出现。这对大家来说都是个糟糕的情况。

撰写回答