NEP 30 — NumPy 陣列的鴨子型別 - 實作#
- 作者:
Peter Andreas Entschev <pentschev@nvidia.com>
- 作者:
Stephan Hoyer <shoyer@google.com>
- 狀態:
已取代
- 取代者:
- 類型:
標準追蹤
- 建立日期:
2019-07-31
- 更新日期:
2019-07-31
- 決議:
摘要#
我們提議 __duckarray__
協定,遵循 NEP 22 中描述的高階概述,允許下游函式庫傳回其定義型別的陣列,而不是 np.asarray
,後者會將這些 array_like
物件強制轉換為 NumPy 陣列。
詳細描述#
NumPy 的 API,包含陣列定義,已在無數其他專案中實作和模仿。根據定義,許多這些陣列在操作方式上與 NumPy 標準非常相似。__array_function__
的引入允許直接透過 NumPy 的 API 調度由這些專案中的幾個實作的函式。這引入了一個新的需求,即傳回類似 NumPy 的陣列本身,而不是強制轉換為純 NumPy 陣列。
為了上述目的,NEP 22 將鴨子型別的概念引入 NumPy 陣列。NEP 中建議的解決方案允許函式庫避免在必要時將類似 NumPy 的陣列強制轉換為純 NumPy 陣列,同時仍然允許不希望實作該協定的類似 NumPy 陣列函式庫透過 np.asarray
將陣列強制轉換為純 NumPy 陣列。
使用指南#
使用 np.duckarray
的程式碼旨在支援其他「遵循 NumPy API」的類似 ndarray 的物件。目前這是一個定義不明確的概念 – 每個已知的函式庫僅部分實作 NumPy API,並且許多函式庫至少在某些細微方面有意偏離。這不容易補救,因此對於 np.duckarray
的使用者,我們建議以下策略:檢查在您使用 np.duckarray
後的程式碼所使用的 NumPy 功能是否存在於 Dask、CuPy 和 Sparse 中。如果是,則合理預期任何鴨子陣列都可以在此處運作。如果不是,我們建議您在您的文件字串中指出接受哪些種類的鴨子陣列,或者它們需要具備哪些屬性。
為了舉例說明鴨子陣列的用法,假設有人想要取得類似陣列的物件 arr
的 mean()
。使用 NumPy 達成此目的,可以寫成 np.asarray(arr).mean()
以達成預期的結果。如果 arr
不是 NumPy 陣列,這將建立一個實際的 NumPy 陣列以便呼叫 .mean()
。但是,如果陣列是符合 NumPy API 的物件(全部或部分符合),例如 CuPy、Sparse 或 Dask 陣列,則該複製將是不必要的。另一方面,如果有人要使用新的 __duckarray__
協定:np.duckarray(arr).mean()
,並且 arr
是符合 NumPy API 的物件,它將只會被傳回,而不是被強制轉換為純 NumPy 陣列,從而避免不必要的複製和潛在的效能損失。
實作方式#
實作想法相當簡單,需要在 NumPy 中引入一個新的函式 duckarray
,以及在類似 NumPy 的陣列類別中引入一個新的方法 __duckarray__
。新的 __duckarray__
方法應傳回下游類似陣列的物件本身,例如 self
物件,而 __array__
方法則引發 TypeError
。或者,__array__
方法可以建立一個實際的 NumPy 陣列並傳回它。
新的 NumPy duckarray
函式可以如下實作
def duckarray(array_like):
if hasattr(array_like, '__duckarray__'):
return array_like.__duckarray__()
return np.asarray(array_like)
實作類似 NumPy 陣列的專案範例#
現在考慮一個函式庫,它實作了一個名為 NumPyLikeArray
的 NumPy 相容陣列類別,這個類別應實作上述方法,完整的實作方式如下所示
class NumPyLikeArray:
def __duckarray__(self):
return self
def __array__(self):
raise TypeError("NumPyLikeArray can not be converted to a NumPy "
"array. You may want to use np.duckarray() instead.")
上面的實作範例是最簡單的情況,但總體思路是函式庫將實作一個 __duckarray__
方法,該方法傳回原始物件,以及一個 __array__
方法,該方法可以建立並傳回適當的 NumPy 陣列,或引發 ``TypeError`` 以防止在 NumPy 陣列中意外用作物件(如果在不實作 __array__
的任意物件上呼叫 np.asarray
,它將建立一個 NumPy 陣列純量)。
對於尚未實作 __array__
但想要使用鴨子陣列型別的現有函式庫,建議他們同時引入 __array__
和 ``__duckarray__`` 方法。
用法#
以下範例展示了如何使用 __duckarray__
協定來編寫基於 concatenate
的 stack
函式,以及其產生的結果。此處選擇此範例不僅是為了示範 duckarray
函式的用法,也是為了示範它對 NumPy API 的依賴性,透過檢查陣列的 shape
屬性來示範。請注意,此範例僅是 NumPy 實際實作在第一個軸上運作的 stack
的簡化版本,並且假設 Dask 已實作 __duckarray__
方法。
def duckarray_stack(arrays):
arrays = [np.duckarray(arr) for arr in arrays]
shapes = {arr.shape for arr in arrays}
if len(shapes) != 1:
raise ValueError('all input arrays must have the same shape')
expanded_arrays = [arr[np.newaxis, ...] for arr in arrays]
return np.concatenate(expanded_arrays, axis=0)
dask_arr = dask.array.arange(10)
np_arr = np.arange(10)
np_like = list(range(10))
duckarray_stack((dask_arr, dask_arr)) # Returns dask.array
duckarray_stack((dask_arr, np_arr)) # Returns dask.array
duckarray_stack((dask_arr, np_like)) # Returns dask.array
相反地,僅使用 np.asarray
(在本 NEP 撰寫時,這是函式庫開發人員通常採用的方法,以確保陣列類似 NumPy)會產生不同的結果
def asarray_stack(arrays):
arrays = [np.asanyarray(arr) for arr in arrays]
# The remaining implementation is the same as that of
# ``duckarray_stack`` above
asarray_stack((dask_arr, dask_arr)) # Returns np.ndarray
asarray_stack((dask_arr, np_arr)) # Returns np.ndarray
asarray_stack((dask_arr, np_like)) # Returns np.ndarray
向後相容性#
此提案在 NumPy 內部不會引起任何向後相容性問題,因為它僅引入了一個新函式。但是,選擇引入 __duckarray__
協定的下游函式庫可能會選擇移除透過 np.array
或 np.asarray
函式將陣列強制轉換回 NumPy 陣列的能力,從而防止此類陣列強制轉換回純 NumPy 陣列的意外影響(正如某些函式庫已經做的那樣,例如 CuPy 和 Sparse),但仍然讓未實作該協定的函式庫可以選擇使用 np.duckarray
來將 array_like
物件提升為純 NumPy 陣列。
先前的提案和討論#
此處提出的鴨子型別協定在 NEP 22 中以高階方式描述。
此外,關於協定和相關提案的更長討論發生在 numpy/numpy #13831
著作權#
本文檔已被置於公共領域。