NEP 27 — 零秩陣列#

作者:

Alexander Belopolsky (sasha),轉錄者 Matt Picus <matti.picus@gmail.com>

狀態:

最終

類型:

資訊性

建立日期:

2006-06-10

決議:

https://mail.python.org/pipermail/numpy-discussion/2018-October/078824.html

注意

NumPy 同時具有零秩陣列和純量值。這份設計文件改編自2006 年的 Wiki 條目,描述了什麼是零秩陣列以及它們存在的原因。它於 2018-10-13 被轉錄成 NEP,並且連結已更新。提取請求引發了關於 NumPy 中持續需要零秩陣列和純量值的熱烈討論

此處的一些資訊已過時,例如,0-D 陣列的索引現在已實作,並且不會產生錯誤。

零秩陣列#

零秩陣列是 shape=() 的陣列。例如

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

零秩陣列和陣列純量值#

陣列純量值在許多方面與零秩陣列相似

>>> int_(1).shape
()

它們甚至列印出來也相同

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

然而,它們之間存在一些重要的差異

  • 陣列純量值是不可變的

  • 陣列純量值對於不同的資料型別具有不同的 Python 型別

陣列純量值的動機#

NumPy 提供 0 維陣列和陣列純量值(除了原生 Python 型別之外)的設計決策,違反了 Python 的基本設計原則之一,即應該只有一種顯而易見的方法來做某事。在本節中,我們將嘗試解釋為什麼有必要使用三種不同的方式來表示數字。

有幾個 numpy-discussion 線程

有人多次建議 NumPy 在所有情況下都只使用 rank-0 陣列來表示純量。將 rank-0 陣列轉換為純量的優缺點總結如下

  • 優點

    • 在某些情況下,當 Python 期望整數時(最戲劇性的是在切片和索引序列時:ceval.c 中的 _PyEval_SliceIndex),它不會先嘗試將其轉換為整數,然後才引發錯誤。因此,擁有由陣列物件為您轉換的整數 0 維陣列很方便。

    • 使用者不會因為有兩個幾乎但不完全相同的型別而感到困惑,這兩個型別的獨立存在只能用 Python 和 NumPy 開發的歷史來解釋。

    • 對於執行顯式型別檢查的程式碼沒有問題 (isinstance(x, float)type(x) == types.FloatType)。雖然顯式型別檢查通常被認為是不好的做法,但有幾個合理的理由可以使用它們。

    • 不會在 pickle 檔案中建立對 Numeric 的依賴(儘管這也可以透過陣列的 pickling 程式碼中的特殊情況來完成)

  • 缺點

    • 很難編寫通用程式碼,因為純量值不具有與陣列相同的方法和屬性。(例如 .type.shape)。此外,Python 純量值也具有不同的數值行為。

    • 這導致了令人不快的特殊情況檢查。從根本上來說,它讓使用者相信,多維同質陣列在某種程度上類似於 Python 列表(除了 Object 陣列之外,它們並非如此)。

NumPy 實作了一個旨在擁有上述所有優點且沒有任何缺點的解決方案。

為所有 21 種型別建立 Python 純量型別,並從已存在的三種型別繼承。為這些 Python 純量型別定義等效的方法和屬性。

對零秩陣列的需求#

一旦使用零秩陣列表示純量值的想法被拒絕,很自然地會考慮是否可以完全消除零秩陣列。但是,在某些重要的用例中,零秩陣列無法被陣列純量值取代。另請參閱 2006 年 2 月的 A case for rank-0 arrays

  • 輸出引數

    >>> y = int_(5)
    >>> add(5,5,x)
    array(10)
    >>> x
    array(10)
    >>> add(5,5,y)
    Traceback (most recent call last):
         File "<stdin>", line 1, in ?
    TypeError: return arrays must be of ArrayType
    
  • 共享資料

    >>> x = array([1,2])
    >>> y = x[1:2]
    >>> y.shape = ()
    >>> y
    array(2)
    >>> x[1] = 20
    >>> y
    array(20)
    

零秩陣列的索引#

截至 NumPy 0.9.3 版本,零秩陣列不支援任何索引

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

另一方面,在某些情況下,零秩陣列是有意義的。

省略符號和空元組#

Alexander 在 scipy-dev 上發起了 2006 年 1 月的討論,並提出以下提案

… 允許 a[...] 可能是合理的。這樣,省略符號可以解釋為任意數量的 :,包括零。對於純量值有意義的另一個下標運算將是 a[...,newaxis] 甚至是 a[{newaxis, }* ..., {newaxis,}*],其中 {newaxis,}* 代表任意數量的逗號分隔的 newaxis 標記。這將允許在通用程式碼中使用省略符號,該程式碼適用於任何 numpy 型別。

Francesc Altet 支持零秩陣列上的 [...] 想法,並且 建議 也應該支援 [()]

Francesc 的提案是

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'>

人們一致認為,對於零秩陣列 xx[...]x[()] 都應該有效,但問題仍然是結果的型別應該是什麼 - 零秩 ndarray 還是 x.dtype

(Alexander)

首先,無論對 x[...]x[()] 做出什麼選擇,它們都應該是相同的,因為 ... 只是「根據需要盡可能多的 :」的語法糖,在零秩的情況下,這會導致 ... = (:,)*0 = ()。其次,在 numpy 中,零秩陣列和 numpy 純量型別是可互換的,但 numpy 純量值可以用在某些 ndarray 無法使用的 Python 結構中。例如

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

由於大多數(如果不是全部)numpy 函數在返回時會自動將零秩陣列轉換為純量值,因此 [...]x[()] 運算沒有理由不同。

請參閱 SVN changeset 1864(已成為 git commit 9024ff0),以了解 x[...]x[()] 返回 numpy 純量值的實作。

請參閱 SVN changeset 1866(已成為 git commit 743d922),以了解 x[...] = vx[()] = v 的實作

使用 newaxis 增加秩#

所有評論的人都喜歡此功能,因此從 SVN changeset 1871(已成為 git commit b32744e)開始,任意數量的省略符號和 newaxis 標記都可以作為零秩陣列的下標引數放置。例如

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

尚不清楚為什麼應該允許多個省略符號,但這是我們試圖保留的較高秩陣列的行為。

重構#

目前,零秩陣列上的所有索引都在程式碼的特殊 if (nd == 0) 分支中實作,該分支過去總是會引發索引錯誤。這確保了變更不會影響任何現有的用法(除了依賴異常的用法)。另一方面,這些變更的部分動機是使 ndarray 的行為更加統一,這應該可以完全消除 if (nd == 0) 檢查。