NEP 23 — 向後相容性與棄用政策#

作者:

Ralf Gommers <ralf.gommers@gmail.com>

狀態:

活躍

類型:

流程

建立日期:

2018-07-14

決議:

https://mail.python.org/pipermail/numpy-discussion/2021-January/081423.html

摘要#

在本 NEP 中,我們描述 NumPy 對於向後相容性的處理方式、其棄用與移除政策,以及在考量打破向後相容性的個別案例中,權衡取捨與決策流程。

動機與範疇#

NumPy 擁有非常龐大的使用者群。這些使用者仰賴 NumPy 的穩定性,以及他們撰寫的使用 NumPy 功能的程式碼能夠持續運作。NumPy 也積極地維護與改進 – 有時改進需要或藉由打破向後相容性來簡化。最後,現有使用者的穩定性與避免錯誤或為新使用者提供更佳使用者體驗之間存在權衡。這些相互競爭的需求經常引發長時間的辯論,並延遲接受或拒絕貢獻。本 NEP 嘗試透過提供政策,以及何時打破向後相容性是好主意或不是好主意的範例與理由來解決此問題。

此外,本 NEP 可以作為關於 NumPy 專案如何處理向後相容性,以及他們預期變更速度的文件,供使用者參考。

本 NEP 的範疇包括

  • NumPy 處理向後相容性的原則。

  • 如何棄用功能,以及何時移除已棄用的功能。

  • 棄用與移除的決策制定流程。

  • 如何確保使用者充分了解任何變更。

超出範疇包括

  • 針對特定功能的棄用做出具體決策。

  • NumPy 的版本控制方案。

一般原則#

在考量提議的向後不相容變更時,NumPy 開發人員在做出決策時使用的主要原則是

  1. 變更需要為使用者帶來的好處大於壞處。

  2. NumPy 廣泛使用,因此預設應假設破壞性變更是有害的。

  3. 決策應基於變更如何影響使用者與下游套件,並應盡可能基於使用資料。此使用是否與文件或最佳實務相悖並不重要。

  4. 不正確的結果比錯誤甚至崩潰更糟。

在評估提議變更的成本時,請記住大多數使用者不會閱讀郵件列表,不會注意到棄用警告,有時會等待一年或兩年以上才從舊版本升級。且 NumPy 擁有數百萬使用者,因此「沒有人會執行或使用此功能」可能是錯誤的。

提議變更的好處可能包括改進的功能、可用性與效能,以及更低的維護成本和改進的未來可擴展性。

明確錯誤的修復不受此向後相容性政策的約束。然而,如果對使用者造成嚴重影響,即使是錯誤修復也可能必須延遲一個或多個版本發布。例如,如果下游函式庫將不再建置或會產生不正確的結果。

實作棄用與移除#

在所有最終將移除功能的情況下,棄用警告都是必要的。如果無意移除功能,則不應棄用。應改為在文件中使用「請勿將此用於新程式碼」或其他類型的警告,並且可以組織文件,使首選替代方案更突出地顯示。

棄用

  • 應包含功能被棄用的發布版本號碼。

  • 應包含關於棄用功能替代方案的資訊,或在沒有明確替代方案時說明棄用原因。請注意,發行說明可以包含更長的訊息(如果需要)。

  • 預設應使用 DeprecationWarning,對於已棄用後需要再次注意或因某些原因需要額外注意的變更,則使用 VisibleDeprecation

  • 應在首次出現棄用的發行版本的發行說明中列出。

  • 不應在微(錯誤修復)版本中引入。

  • 應設定 stacklevel,使警告顯示來自正確的位置。

  • 應在功能的說明文件中提及。可以使用 .. deprecated:: 指令來執行此操作。

良好棄用警告的範例(另請注意警告上方註解的標準形式,有助於使用 grep 時)

# NumPy 1.15.0, 2018-09-02
warnings.warn('np.asscalar(a) is deprecated since NumPy 1.16.0, use '
              'a.item() instead', DeprecationWarning, stacklevel=3)

# NumPy 1.15.0, 2018-02-10
warnings.warn("Importing from numpy.testing.utils is deprecated "
              "since 1.15.0, import from numpy.testing instead.",
              DeprecationWarning, stacklevel=2)

# NumPy 1.14.0, 2017-07-14
warnings.warn(
    "Reading unicode strings without specifying the encoding "
    "argument is deprecated since NumPy 1.14.0. Set the encoding, "
    "use None for the system default.",
    np.VisibleDeprecationWarning, stacklevel=2)
/* DEPRECATED 2020-05-13, NumPy 1.20 */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
        matrix_deprecation_msg, ufunc->name, "first") < 0) {
    return NULL;
}

移除已棄用的功能

  • 假設目前為 6 個月發行週期,則應在至少 2 個版本後執行;如果週期變更,則棄用與移除之間應至少間隔 1 年。

  • 應在發生移除的發行版本的發行說明中列出。

  • 可以在任何次要版本(但非錯誤修復版本)中執行。

對於非「棄用與移除」但程式碼將開始以不同方式運作的向後不相容變更,應使用 FutureWarning。發行說明、提及版本號碼和使用 stacklevel 的方式應與棄用警告相同。在行為變更後,應在說明文件中使用 .. versionchanged:: 指令來指示行為變更的時間

def argsort(self, axis=np._NoValue, ...):
    """
    Parameters
    ----------
    axis : int, optional
        Axis along which to sort. If None, the default, the flattened array
        is used.

        ..  versionchanged:: 1.13.0
            Previously, the default was documented to be -1, but that was
            in error. At some future date, the default will change to -1, as
            originally intended.
            Until then, the axis should be given explicitly when
            ``arr.ndim > 1``, to avoid a FutureWarning.
    """
    ...
    warnings.warn(
        "In the future the default for argsort will be axis=-1, not the "
        "current None, to match its documentation and np.argsort. "
        "Explicitly pass -1 or None to silence this warning.",
        MaskedArrayFutureWarning, stacklevel=3)

決策制定#

在需要應用此政策的具體案例中,決策根據 NumPy 治理模型制定。

所有棄用都必須在郵件列表上提出,以便讓所有對 NumPy 開發感興趣的人都有機會評論。移除已棄用的功能不需要在郵件列表上討論。

具有更嚴格棄用政策的功能#

  • numpy.random 有其自身的向後相容性政策,除了本 NEP 中的要求外,還有額外要求,請參閱 NEP 19 — 隨機數產生器政策

  • .npy.npz 檔案的檔案格式嚴格地獨立於 NumPy 版本進行版本控制;即使引入較新的格式版本,現有的格式版本也必須保持向後相容。

範例案例#

我們現在討論 NumPy 歷史上的一些具體範例,以說明典型問題和權衡取捨。

變更函數的行為

np.histogram 可能是最臭名昭著的範例。首先,引入了一個新的關鍵字 new=False,然後在一個版本後切換為 None,最後又再次移除。此外,它還有一個 normed 關鍵字,其行為可以被認為是次優或損壞(取決於對統計的看法)。引入了一個新的關鍵字 density 來取代它;normed 僅在 v.1.15.0 中才開始發出 DeprecationWarninghistogram 的演進

def histogram(a, bins=10, range=None, normed=False):  # v1.0.0

def histogram(a, bins=10, range=None, normed=False, weights=None, new=False):  #v1.1.0

def histogram(a, bins=10, range=None, normed=False, weights=None, new=None):  #v1.2.0

def histogram(a, bins=10, range=None, normed=False, weights=None):  #v1.5.0

def histogram(a, bins=10, range=None, normed=False, weights=None, density=None):  #v1.6.0

def histogram(a, bins=10, range=None, normed=None, weights=None, density=None):  #v1.15.0
    # v1.15.0 was the first release where `normed` started emitting
    # DeprecationWarnings

new 關鍵字從一開始就計畫是暫時性的。這種計畫迫使使用者多次變更其程式碼,這幾乎永遠不是正確的做法。相反,這裡更好的方法是棄用 histogram 並在其位置引入一個新的函數 hist

不允許使用浮點數進行索引

使用浮點數索引陣列會要求一些模稜兩可的東西,並且可能是使用者程式碼中錯誤的徵兆。經過一些討論後,認為棄用使用浮點數進行索引是個好主意。這首先在 v1.8.0 版本中嘗試,然而在預先發布測試中,顯然這將破壞許多依賴 NumPy 的函式庫。因此,在發布前還原了它,以便讓這些函式庫有時間先修復其程式碼。它最終在 v1.11.0 中引入,並在 v1.12.0 中變成硬性錯誤。

此變更具有破壞性,但它確實捕捉到真實的錯誤,例如在 SciPy 和 scikit-learn 中。總體而言,此變更值得付出代價,並且首先在主要分支中引入以允許測試,然後在發布前再次移除,這是一個有用的策略。

類似的棄用也看起來像是清理/改進的良好範例

  • 移除已棄用的布林索引(在 2016 年,請參閱 gh-8312

  • 棄用對空陣列的真值測試(在 2017 年,請參閱 gh-9718

移除金融函數

金融函數(例如 np.pmt)具有簡短且非描述性的名稱,存在於主要的 NumPy 命名空間中,並且實際上不太適合 NumPy 的範疇。它們是在 2008 年在郵件列表上進行 討論 後新增的,當時意見分歧(但大多數贊成)。金融函數沒有造成大量額外負擔,但每年仍然有多個與它們相關的問題和 PR,需要維護人員花時間處理。並且它們使 numpy 命名空間變得混亂。關於移除它們的討論在 2013 年(gh-2880,已拒絕)和 2019 年(NEP 32 — 從 NumPy 移除金融函數,已接受且沒有重大抱怨)中進行了討論。

鑑於它們顯然超出 NumPy 的範疇,將它們移至獨立的 numpy-financial 套件並在棄用期後從 NumPy 中移除它們是有道理的。這也為使用者提供了一種透過執行 pip install numpy-financial 輕鬆更新其程式碼的方法。

替代方案#

更積極地進行棄用。

更積極的目標是讓 NumPy 能夠更快地向前發展。這將避免其他人發明自己的解決方案(通常在多個地方),並且對沒有舊版程式碼庫的使用者也是一種好處。我們拒絕此替代方案,因為 NumPy 在科學 Python 生態系統中的地位 - 為了不將下游函式庫和終端使用者的額外維護增加到無法接受的程度,必須保持相當保守。

討論#

參考文獻與腳註#