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

88

一个0维数组显然是标量,但是Numpy似乎并不这么认为…… 我是漏了什么还是我只是误解了这个概念?

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

176

不要想得太复杂。这对个人的心理健康和寿命更有好处。

Numpy标量类型的奇怪情况源于没有优雅而一致的方法将1x1矩阵降级为标量类型。尽管数学上它们是相同的东西,但它们被非常不同的代码处理。

如果您已经编写了任何科学代码,最终您会希望像max(a)这样的东西在各种大小的矩阵上运行,甚至包括标量。从数学上讲,这是一个完全合理的期望。但是对于程序员来说,这意味着Numpy中呈现标量的任何内容都应该具有.shape和.ndim属性,以便ufuncs不必在Numpy的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兼容而必须这样做,但这并不能改变许多新用户对此深感困惑的事实。有些人明确记得曾经为此而苦苦挣扎并最终坚持下来,而其他人则已经太远而无法回头,通常留下一些深深的无形心理伤疤,经常困扰着他们最无辜的梦境。这对所有人来说都是一个丑陋的局面。

29
你有考虑过写作作为你的副业吗? - KobeJohn
8
很多看起来都是Matlab的想法 - “您实际上无法增加Numpy标量”,也无法增加np.array。明确大小可以减少意外的O(N^2)操作。 “它不能定义为ndarray” - 这就是np.asarray(scalar)的作用。“这个1x1矩阵…”- 将事物视为本质上是2D或矩阵,在这里并没有帮助。 - Eric
1
我猜 a[...]a[()] 有意不同,至少 Numpy 的文档在这里提到了它:x[()] 返回一个数组标量的副本, x[...] 返回一个0维的ndarray。 这是因为 "切片总是返回一个数组" 吗?但是确实,在最终 x [()] 也是切片... - Joe
Numpy数组API https://docs.scipy.org/doc/numpy/reference/c-api.array.html#array-scalars 建议在可能返回0维数组到Python时使用PyArray_Return(PyArrayObject* arr)来返回适当的数组标量。 - Joe'
我正在考虑向numpy提交一个PR,请求将np.array重命名为np.arrayUnlessConstructedWithANonSequenceInWhichCaseItsJustAValue - Johnus

6
您需要以稍微不同的方式创建标量数组:
>>> x = numpy.float64(1.111)
>>> x
1.111
>>> numpy.isscalar(x)
True
>>> numpy.ndim(x)
0

看起来在numpy中,标量的概念可能与纯数学角度下你所习惯的有些不同。我猜你是在考虑标量矩阵的概念?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接