Version 9 (modified by sasha, 7 years ago)

Zero-Rank Arrays and Array Scalars

Zero-Rank Arrays

Zero-rank arrays are arrays with shape=(). For example:

>>> x = array(1)
>>> x.shape
()

Zero-Rank Arrays and Array Scalars

Array scalars are similar to zero-rank arrays in many aspects

>>> int_(1).shape
()

They even print the same:

>>> print int_(1)
1
>>> print array(1)
1

However there are some important differences:

  • Array scalars are immutable
  • Array scalars have different python type for different data types

Indexing of Zero-Rank Arrays

As of NumPy release 0.9.3, zero-rank arrays do not support any indexing

>>> x[...]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
IndexError: 0-d arrays can't be indexed.

On the other hand there are several cases that make sense for rank-zero arrays.

Ellipsis and empty tuple

Sasha started a discussion on scipy-dev with the folowing proposal:

... it may be reasonable to allow a[...].  This way
ellipsis can be interpereted as any number of  ":"s including zero. 
Another subscript operation that makes sense for scalars would be
a[...,newaxis] or even a[{newaxis, }* ..., {newaxis,}*], where 
{newaxis,}* stands for any number of comma-separated newaxis tokens. 
This will allow one to use ellipsis in generic code that would work on
any numpy type. 

The idea to allow increasing the rank of zero-rank arrays with [..., newaxis] was originally proposed by Sebastien.

Francesc Altet supported the idea of [...] on zero-rank arrays and suggested that [()] be supported as well.

Francesc's proposal was:

In [65]: type(numpy.array(0)[...])
Out[65]: <type 'numpy.ndarray'>

In [66]: type(numpy.array(0)[()])   # Indexing a la numarray
Out[66]: <type 'int32_arrtype'>

In [67]: type(numpy.array(0).item())  # already works
Out[67]: <type 'int'>

There is a consensus that for a zero-rank array x, both x[...] and x[()] should be valid, but the question remains on what should be the type of the result - zero rank ndarray or x.dtype?

(Sasha) First, whatever choice is made for x[...] and x[()] they should be the same because ... is just syntactic sugar for "as many : as necessary", which in the case of zero rank leads to ... = (:,)*0 = (). Second, rank zero arrays and numpy scalar types are interchangeable within numpy, but numpy scalars can be use in some python constructs where ndarrays can't. For example:

>>> (1,)[array(0)]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: tuple indices must be integers
>>> (1,)[int32(0)]
1

Since most if not all numpy function automatically convert zero-rank arrays to scalars on return, there is no reason for [...] and [()] operations to be different.

See changeset:1864 for implementation of x[...] and x[()] returning numpy scalars.

See changeset:1866 for implementation of x[...] = v and x[()] = v.

Increasing rank with newaxis

Everyone who commented liked this feature, so as of changeset:1871 any number of ellipses and newaxis tokens can be placed as a subscript argument for a zero-rank array. For example:

>>> x = array(1)
>>> x[newaxis,...,newaxis,...]
array([[1]])

It is not clear why more than one ellipsis should be allowed, but this is the behavior of higher rank arrays that we are trying to preserve.

Refactoring

Currently all indexing on zero-rank arrays is implemented in a special if (nd == 0) branch of code that used to always raise an index error. This ensures that the changes do not affect any existing usage (except, the usage that relies on exceptions). On the other hand part of motivation for these changes was to make behavior of ndarrays more uniform and this should allow to eliminate if (nd == 0) checks alltogether.