NEP 52 — NumPy 2.0 的 Python API 清理#
- 作者:
Ralf Gommers <ralf.gommers@gmail.com>
- 作者:
Stéfan van der Walt <stefanv@berkeley.edu>
- 作者:
Nathan Goldbaum <ngoldbaum@quansight.com>
- 作者:
Mateusz Sokół <msokol@quansight.com>
- 狀態:
最終
- 類型:
標準追蹤
- 建立時間:
2023-03-28
- 決議:
摘要#
我們提議為 NumPy 2.0 版本清理 NumPy 的 Python API。這包括更清楚地定義公開與私有內容的區隔,並透過移除別名和具有更好替代方案的函數來縮減主要命名空間的大小。此外,每個函數都應僅可從一個位置存取,因此所有重複項目也需要移除。
動機與範疇#
NumPy 具有龐大的 API 介面,多年來有機地發展
>>> objects_in_api = [s for s in dir(np) if not s.startswith('_')]
>>> len(objects_in_api)
562
>>> modules = [s for s in objects_in_api if inspect.ismodule(eval(f'np.{s}'))]
>>> modules
['char', 'compat', 'ctypeslib', 'emath', 'fft', 'lib', 'linalg', 'ma', 'math', 'polynomial', 'random', 'rec', 'testing', 'version']
>>> len(modules)
14
以上甚至不包括公開但已從 __dir__
中隱藏的項目。其中一個特別有問題的例子是 np.core
,它在技術上是私有的,但在實務中被大量使用。如需完整了解哪些被視為公開、私有或介於兩者之間,請參閱 numpy/numpy。
API 的大小以及缺乏邊界定義造成了顯著的成本
使用者發現難以釐清名稱相似的函數之間的差異。
在 IPython、筆記本或 IDE 中尋找具有 Tab 鍵完成功能的函數是一項挑戰。例如,輸入
np.<TAB>
並查看提供的前六個項目:兩個 ufuncs (abs
,add
)、一個別名 (absolute
) 和三個非供終端使用者使用的函數 (add_docstring
,add_newdoc
,add_newdoc_ufunc
)。因此,NumPy 的學習曲線比原本應有的更陡峭。模擬 NumPy API 的函式庫面臨重大的實作障礙。
對於 NumPy API 相容陣列函式庫(Dask、CuPy、JAX、PyTorch、TensorFlow、cuNumeric 等)以及編譯器/轉譯器(Numba、Pythran、Cython 等)的維護者來說,命名空間中的每個物件都有實作成本。實際上,沒有其他函式庫完全支援整個 NumPy API,部分原因是當面臨大量別名和舊版物件時,很難知道要包含哪些內容。
教導 NumPy 比原本應有的更複雜。
同樣地,較大的 API 對於學習者來說會造成混淆,他們不僅要找到函數,還必須選擇要使用哪些函數。
開發人員對於擴大 API 介面感到猶豫。
即使變更是有必要的,這種情況仍然會發生,因為他們意識到上述問題。
此 NEP 的範疇包括
棄用或移除對於 NumPy 來說太過小眾、設計不良、已被更好的替代方案取代、不必要的別名,或以其他方式成為移除候選者的功能。
透過使用底線清楚區隔公開與私有 NumPy API。
重組 NumPy 命名空間,使其更易於理解和導覽。
此 NEP 的範疇不包括
引入新功能或效能增強。
使用方式與影響#
此 API 重構的關鍵原則是確保當程式碼已適應變更並與 2.0 相容時,該程式碼也能與 NumPy 1.2x.x
搭配運作。這可降低使用者和下游函式庫維護者的負擔,無需攜帶根據 NumPy 主要版本號碼切換的重複程式碼。
向後相容性#
如上所述,雖然新的(或清理過的,NumPy 2.0)API 應向後相容,但不保證從 1.25.X 到 2.0 的向前相容性。程式碼必須更新,以考量已棄用、移動或移除的函數/類別,以及更嚴格執行的私有 API。
為了更容易採用此 NEP 中的變更,我們將
提供轉換指南,列出每個 API 變更及其替代方案。
明確標記所有過期的屬性,並提供有意義的
AttributeError
,指出新位置或建議替代方案。提供腳本以在可行範圍內自動化遷移。這將類似於
tools/replace_old_macros.sed
(適用於先前 C API 命名方案變更的程式碼)。這將是基於sed
(或同等工具)而非嘗試 AST 分析,因此它不會涵蓋所有內容。
詳細描述#
清理主要命名空間#
我們預期主要命名空間中的條目數量將大幅減少,約為 100 個。以下是一些具代表性的範例
np.inf
和np.nan
之間有 8 個別名,其中大多數可以移除。gh-12385 中列出的一系列隨機且未記錄的函數(例如,
byte_bounds
、disp
、safe_eval
、who
)可以棄用並移除。所有
*sctype
函數都可以棄用並移除,它們(請參閱 gh-17325、gh-12334 以及其他關於maximum_sctype
和相關函數的問題)。將移除在 Python 2 到 3 轉換期間使用的
np.compat
命名空間。將移除範圍狹窄、公用案例非常少的函數。這些函數必須透過手動和問題分類來識別。
為警告/例外狀況 (np.exceptions
) 和 dtype 相關功能 (np.dtypes
) 引入新的命名空間。NumPy 2.0 是從主要命名空間填充這些子模組的好機會。
廣泛使用但具有偏好替代方案的功能可能會被棄用(棄用訊息會指出要改用什麼),或者透過不將其包含在 __dir__
中來隱藏。在隱藏的情況下,可以使用 .. legacy::
目錄來標記文件中的此類功能。
將新增一項測試,以確保所有命名空間在未來受到限制的成長;也就是說,每個新條目都需要明確新增至允許清單。
清理子模組結構#
我們將清理 NumPy 子模組結構,使其更易於導覽。當之前討論過此問題時(請參閱 MAINT: Hide internals of np.lib to only show submodules),已經有大致共識 - 然而,在次要版本中很難實現。
我們將遵守的基本原則是「一個函數,一個位置」。在多個命名空間中公開的函數(例如,許多函數存在於 numpy
和 numpy.lib
中)需要找到單一的家。
我們將沿著主要和子模組命名空間重新組織 API 參考指南,並且僅在主要命名空間內使用目前的功能分組細分。也依「主流」和特殊用途命名空間劃分
# Regular/recommended user-facing namespaces for general use. Present these
# as the primary set of namespaces to the users.
numpy
numpy.exceptions
numpy.fft
numpy.linalg
numpy.polynomial
numpy.random
numpy.testing
numpy.typing
# Special-purpose namespaces. Keep these, but document them in a separate
# grouping in the reference guide and explain their purpose.
numpy.array_api
numpy.ctypeslib
numpy.emath
numpy.f2py # only a couple of public functions, like `compile` and `get_include`
numpy.lib.stride_tricks
numpy.lib.npyio
numpy.rec
numpy.dtypes
numpy.array_utils
# Legacy (prefer not to use, there are better alternatives and/or this code
# is deprecated or isn't reliable). This will be a third grouping in the
# reference guide; it's still there, but de-emphasized and the problems
# with it or better alternatives are explained in the docs.
numpy.char
numpy.distutils
numpy.ma
numpy.matlib
# To remove
numpy.compat
numpy.core # rename to _core
numpy.doc
numpy.math
numpy.version # rename to _version
numpy.matrixlib
# To clean out or somehow deal with: everything in `numpy.lib`
注意
待辦事項:我們是否會保留 np.lib
?它只有幾個獨特的函數/物件,例如 Arrayterator
(移除候選者)、NumPyVersion
以及 stride_tricks
、mixins
和 format
子子模組。numpy.lib
本身不是一個連貫的命名空間,甚至沒有參考指南頁面。
我們將使所有子模組以延遲方式提供,以便使用者不必輸入 import numpy.xxx
,但可以使用 import numpy as np; np.xxx.*
,同時不會對 import numpy
的額外負荷產生負面影響。這對於教導 scikit-image 和 SciPy 非常有幫助,並且它解決了 Spyder 使用者的潛在問題,因為 Spyder 已經使所有子模組都可用 - 因此使用上述匯入模式的程式碼可以在 Spyder 中運作,但在外部則不行。
減少選擇 dtype 的方式數量#
許多 dtype 類別、實例、別名和選擇它們的方式是 NumPy API 中較大的可用性問題之一。例如
>>> # np.intp is different, but compares equal too
>>> np.int64 == np.int_ == np.dtype('i8') == np.sctypeDict['i8']
True
>>> np.float64 == np.double == np.float_ == np.dtype('f8') == np.sctypeDict['f8']
True
### Really?
>>> np.clongdouble == np.clongfloat == np.longcomplex == np.complex256
True
這些別名可以移除:https://numpy.dev.org.tw/devdocs/reference/arrays.scalars.html#other-aliases
所有單字元型別代碼字串和相關常式(如 mintypecode
)都將標記為舊版。
待討論
將所有 dtype 相關類別移至
np.dtypes
?比較/選擇 dtype 的標準方式:
np.isdtype
(新的,xref array API NEP),保留np.issubdtype
用於 numpy 的 dtype 類別階層的更小眾用途,並隱藏大多數其他內容。可能移除
float96
/float128
?它們是不一定存在的別名,而且太容易搬石頭砸自己的腳。
清理 numpy.ndarray
上的小眾方法#
ndarray
物件有很多屬性和方法,其中一些太過小眾而不應如此突出,所有這些只會分散一般使用者的注意力。例如
.itemset
(已不建議使用).newbyteorder
(太過小眾).ptp
(小眾,改用np.ptp
函數)
已考量和拒絕的 API 變更#
對於某些函數和子模組,結果證明移除它們會造成過多的中斷,或需要與實際收益不成比例的工作量。我們針對以下項目得出此結論
移除營業日函數:
np.busday_count
、np.busday_offset
、np.busdaycalendar
。移除
np.nan*
函數並在相關基本函數中引入新的nan_mode
引數。將直方圖函數隱藏在
np.histograms
子模組中。將
c_
、r_
和s_
隱藏在np.lib.index_tricks
子模組中。看起來小眾但在 Array API 中存在的函數(例如
np.can_cast
)。從
ndarray
物件中移除.repeat
和.ctypes
。
實作方式#
實作已分散在許多不同的 PR 中,每個 PR 都涉及單個 API 或一組相關的 API。以下是一些最具影響力的 PR 範例
可以透過搜尋專用標籤找到 2.0 版本中完成的完整清理工作列表
一些 PR 已經合併並與 1.25.0 版本一起發布。例如,棄用非偏好別名
隱藏或移除意外公開或甚至根本不是 NumPy 物件的物件
建立新的命名空間,使其更易於導覽模組結構
替代方案#
討論#
參考文獻與註腳#
著作權#
本文檔已置於公共領域。