warnings --- 警告控制

原始碼:Lib/warnings.py


警告訊息通常會在需要提醒使用者程式中的某些狀況時發出,而該狀況(通常)不至於需要引發例外並終止程式。舉例來說,當程式使用一個過時的模組時,就可能會需要發出警告。

Python 程式設計師可以透過呼叫此模組中定義的 warn() 函式來發出警告。(C 語言的程式設計師則使用 PyErr_WarnEx();詳情請參閱 例外處理)。

警告訊息通常會被寫入 sys.stderr,但它們的處理方式可以被彈性地更改,從忽略所有警告到將它們轉為例外都可以。警告的處理方式會根據 警告類別、警告訊息的文本,以及發出警告的原始碼位置而有所不同。在同一個原始碼位置重複出現的特定警告通常會被抑制。

警告控制有兩個階段:首先,每當發出一個警告時,會先決定是否該發出訊息;接著,如果要發出訊息,它會使用一個可由使用者設定的掛鉤 (hook) 來格式化並印出。

是否要發出警告訊息是由 警告過濾器 所控制,它是一連串的匹配規則和動作。可以透過呼叫 filterwarnings() 來新增規則到過濾器中,並透過呼叫 resetwarnings() 將其重設為預設狀態。

警告訊息的印出是透過呼叫 showwarning() 來完成,而它可以被覆寫;此函式的預設實作會透過呼叫 formatwarning() 來格式化訊息,它也可以被自訂的實作使用。

也參考

logging.captureWarnings() 允許你使用標準的 logging 基礎設施來處理所有警告。

警告類別

有許多內建的例外代表警告類別。這種分類方式對於能夠過濾掉特定群組的警告很有用。

雖然這些在技術上是 內建例外,但它們被記錄在這裡,因為從概念上來說,它們屬於警告機制的一部分。

使用者程式碼可以透過繼承其中一個標準警告類別來定義額外的警告類別。一個警告類別必須永遠是 Warning 類別的子類別。

目前定義了以下警告類別:

類別

描述

Warning

這是所有警告類別的基底類別。它是 Exception 的子類別。

UserWarning

warn() 的預設類別。

DeprecationWarning

關於已棄用功能的警告的基底類別,當這些警告是針對其他 Python 開發者時(預設為忽略,除非由 __main__ 中的程式碼觸發)。

SyntaxWarning

關於可疑語法功能的警告的基底類別。

RuntimeWarning

關於可疑 runtime 功能的警告的基底類別。

FutureWarning

關於已棄用功能的警告的基底類別,當這些警告是針對以 Python 編寫的應用程式的終端使用者時。

PendingDeprecationWarning

關於未來將被棄用的功能的警告的基底類別(預設為忽略)。

ImportWarning

在引入模組過程中觸發的警告的基底類別(預設為忽略)。

UnicodeWarning

與 Unicode 相關的警告的基底類別。

BytesWarning

與 bytes 和 bytearray 相關的警告的基底類別。

ResourceWarning

與資源使用相關的警告的基底類別(預設為忽略)。

在 3.7 版的變更: 在過去,DeprecationWarning 和 FutureWarning 是根據一個功能是被完全移除還是改變其行為來區分的。它們現在是根據其目標受眾以及預設警告過濾器處理它們的方式來區分。

警告過濾器

警告過濾器控制警告是被忽略、顯示,還是轉為錯誤(引發一個例外)。

從概念上講,警告過濾器維護一個有序的過濾器規格串列;任何特定的警告都會依次與串列中的每個過濾器規格進行比對,直到找到匹配項;過濾器決定了匹配項的處理方式。每個條目都是一個 (action, message, category, module, lineno) 形式的元組,其中:

  • action 是以下字串之一:

    值

    處理方式

    "default"

    為發出警告的每個位置(模組 + 行號)印出第一次出現的匹配警告

    "error"

    將匹配的警告轉為例外

    "ignore"

    永不印出匹配的警告

    "always"

    總是印出匹配的警告

    "module"

    為發出警告的每個模組印出第一次出現的匹配警告(不論行號)

    "once"

    只印出第一次出現的匹配警告,不論位置

  • message 是一個包含正規表示式的字串,警告訊息的開頭必須與其匹配(不區分大小寫)。在 -W 和 PYTHONWARNINGS 中,message 是一個字面字串,警告訊息的開頭必須包含該字串(不區分大小寫),並忽略 message 開頭或結尾的任何空白字元。

  • category 是一個類別(Warning 的子類別),警告類別必須是它的子類別才能匹配。

  • module 是一個包含正規表示式的字串,完整限定模組名稱的開頭必須與其匹配(區分大小寫)。在 -W 和 PYTHONWARNINGS 中,module 是一個字面字串,完整限定模組名稱必須與其相等(區分大小寫),並忽略 module 開頭或結尾的任何空白字元。

  • lineno 是一個整數,發出警告的行號必須與其匹配,或者為 0 以匹配所有行號。

由於 Warning 類別衍生自內建的 Exception 類別,要將警告轉為錯誤,我們只需引發 category(message)。

如果一個警告被回報且不匹配任何已註冊的過濾器,則會套用 "default" 動作(因此得名)。

重複警告的抑制標準

抑制重複警告的過濾器會套用以下標準來判斷一個警告是否被視為重複:

  • "default":只有當 (message, category, module, lineno) 都相同時,警告才被視為重複。

  • "module":如果 (message, category, module) 相同,則警告被視為重複,忽略行號。

  • "once":如果 (message, category) 相同,則警告被視為重複,忽略模組和行號。

描述警告過濾器

警告過濾器由傳遞給 Python 直譯器命令列的 -W 選項和 PYTHONWARNINGS 環境變數初始化。直譯器會將所有提供條目的引數未經直譯地儲存在 sys.warnoptions 中;warnings 模組在首次引入時會剖析這些引數(無效選項會被忽略,並在向 sys.stderr 印出一條訊息後)。

個別的警告過濾器被指定為一系列由冒號分隔的欄位:

action:message:category:module:line

這些欄位中每一個的含義都如 警告過濾器 中所述。當在單一行中列出多個過濾器時(例如 PYTHONWARNINGS),個別的過濾器會以逗號分隔,且後面列出的過濾器優先於前面列出的過濾器(因為它們是從左到右應用的,而最近應用的過濾器優先於較早的過濾器)。

常用的警告過濾器適用於所有警告、特定類別的警告,或由特定模組或套件引發的警告。一些範例如下:

default                      # 顯示所有警告(即使是預設忽略的警告)
ignore                       # 忽略所有警告
error                        # 將所有警告轉換為錯誤
error::ResourceWarning       # 將 ResourceWarning 訊息視為錯誤
default::DeprecationWarning  # 顯示 DeprecationWarning 訊息
ignore,default:::mymodule    # 只回報由 "mymodule" 觸發的警告
error:::mymodule             # 將 "mymodule" 中的警告轉換為錯誤

預設警告過濾器

預設情況下,Python 會安裝數個警告過濾器,這些過濾器可以被 -W 命令列選項、PYTHONWARNINGS 環境變數以及對 filterwarnings() 的呼叫所覆寫。

在常規的發行建置中,預設的警告過濾器有以下條目(按優先順序排列):

default::DeprecationWarning:__main__
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
ignore::ImportWarning
ignore::ResourceWarning

在 偵錯建置 中,預設警告過濾器串列是空的。

在 3.2 版的變更: 除了 PendingDeprecationWarning 之外,DeprecationWarning 現在也預設被忽略。

在 3.7 版的變更: 當直接由 __main__ 中的程式碼觸發時,DeprecationWarning 會再次預設顯示。

在 3.7 版的變更: BytesWarning 不再出現在預設過濾器串列中,而是在指定 -b 兩次時,透過 sys.warnoptions 進行設定。

覆寫預設過濾器

以 Python 編寫的應用程式的開發者可能希望預設對其使用者隱藏 所有 Python 層級的警告,並且只在執行測試或以其他方式處理應用程式時才顯示它們。用於將過濾器設定傳遞給直譯器的 sys.warnoptions 屬性可以用作一個標記,以指示是否應停用警告:

import sys

if not sys.warnoptions:
    import warnings
    warnings.simplefilter("ignore")

建議 Python 程式碼的測試執行器開發者,應確保在預設情況下,為受測程式碼顯示 所有 警告,可使用如下程式碼:

import sys

if not sys.warnoptions:
    import os, warnings
    warnings.simplefilter("default") # 在此行程中更改過濾器
    os.environ["PYTHONWARNINGS"] = "default" # 也會影響子行程

最後,建議在 __main__ 以外的命名空間中執行使用者程式碼的互動式 shell 開發者,應確保 DeprecationWarning 訊息預設為可見,可使用如下程式碼(其中 user_ns 是用於執行互動式輸入程式碼的模組):

import warnings
warnings.filterwarnings("default", category=DeprecationWarning,
                                   module=user_ns.get("__name__"))

暫時抑制警告

如果你正在使用的程式碼,你知道它會引發一個警告(例如一個已棄用的函式),但你不想看到這個警告(即使警告已透過命令列明確設定),那麼可以使用 catch_warnings 情境管理器來抑制該警告:

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    fxn()

在情境管理器中,所有警告都將被直接忽略。這允許你在使用已知的已棄用程式碼時不必看到警告,同時又不會抑制其他可能不知道自己正在使用已棄用程式碼的程式碼所發出的警告。注意:這只能在單一執行緒的應用程式中得到保證。如果兩個或多個執行緒同時使用 catch_warnings 情境管理器,其行為是未定義的。

測試警告

要測試程式碼引發的警告,請使用 catch_warnings 情境管理器。透過它,你可以暫時改變警告過濾器以方便你的測試。例如,執行以下操作來捕獲所有引發的警告以進行檢查:

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # 讓所有警告總是會被觸發。
    warnings.simplefilter("always")
    # 觸發一個警告。
    fxn()
    # 驗證一些事情
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

也可以使用 error 而非 always 來讓所有警告都變成例外。需要注意的一點是,如果一個警告因為 once/default 規則已經被引發過,那麼無論設定什麼過濾器,這個警告都不會再出現,除非與該警告相關的警告註冊表已被清除。

一旦情境管理器退出,警告過濾器就會恢復到進入情境時的狀態。這可以防止測試在不同測試之間以意想不到的方式更改警告過濾器,從而導致不確定的測試結果。模組中的 showwarning() 函式也會恢復到其原始值。注意:這只能在單一執行緒的應用程式中得到保證。如果兩個或多個執行緒同時使用 catch_warnings 情境管理器,其行為是未定義的。

在測試多個會引發同類警告的操作時,重要的是要以一種能確認每個操作都在引發新警告的方式來進行測試(例如,將警告設定為引發例外,並檢查操作是否引發例外;檢查警告串列的長度在每次操作後是否持續增加;或者在每次新操作前從警告串列中刪除先前的條目)。

為新版相依套件更新程式碼

主要針對 Python 開發者(而非以 Python 編寫的應用程式的終端使用者)的警告類別預設會被忽略。

值得注意的是,這個「預設忽略」串列包括 DeprecationWarning(除了 __main__ 以外的所有模組),這意味著開發者應該確保在測試他們的程式碼時,讓通常被忽略的警告可見,以便及時收到未來 API 破壞性變更的通知(無論是在標準函式庫還是第三方套件中)。

在理想情況下,程式碼會有一個合適的測試套件,而測試執行器會在執行測試時負責隱式地啟用所有警告(unittest 模組提供的測試執行器就是這樣做的)。

在較不理想的情況下,可以透過將 -Wd 傳遞給 Python 直譯器(這是 -W default 的簡寫)或在環境中設定 PYTHONWARNINGS=default 來檢查應用程式是否使用了已棄用的介面。這會為所有警告啟用預設處理,包括那些預設被忽略的警告。要更改對遇到的警告所採取的動作,你可以更改傳遞給 -W 的引數(例如 -W error)。有關更多可能性的詳細資訊,請參閱 -W 旗標。

可用的函式

warnings.warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=())

發出一個警告,或者可能忽略它或引發一個例外。category 引數(如果提供)必須是一個 警告類別;它預設為 UserWarning。或者,message 可以是一個 Warning 實例,在這種情況下 category 將被忽略,並使用 message.__class__。在這種情況下,訊息文本將是 str(message)。如果發出的特定警告被 警告過濾器 轉為錯誤,此函式會引發一個例外。stacklevel 引數可以被以 Python 編寫的包裝函式使用,如下所示:

def deprecated_api(message):
    warnings.warn(message, DeprecationWarning, stacklevel=2)

這會讓警告指向 deprecated_api 的呼叫者,而不是 deprecated_api 本身的原始碼(因為後者會違背警告訊息的目的)。

skip_file_prefixes 關鍵字引數可用於指示在計算堆疊層級時應忽略哪些堆疊幀。當你希望警告總是在套件外部的呼叫點出現,而固定的 stacklevel 不適用於所有呼叫路徑或難以維護時,這會很有用。如果提供,它必須是一個字串的元組。當提供前綴時,stacklevel 會被隱式地覆寫為 max(2, stacklevel)。要讓警告歸因於目前套件外部的呼叫者,你可以這樣寫:

# example/lower.py
_warn_skips = (os.path.dirname(__file__),)

def one_way(r_luxury_yacht=None, t_wobbler_mangrove=None):
    if r_luxury_yacht:
        warnings.warn("Please migrate to t_wobbler_mangrove=.",
                      skip_file_prefixes=_warn_skips)

# example/higher.py
from . import lower

def another_way(**kw):
    lower.one_way(**kw)

這使得警告只會從存在於 example 套件之外的呼叫程式碼中,指向 example.lower.one_way() 和 example.higher.another_way() 的呼叫點。

source(如果提供)是發出 ResourceWarning 的已銷毀物件。

在 3.6 版的變更: 新增 source 參數。

在 3.12 版的變更: 新增 skip_file_prefixes。

warnings.warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)

這是一個 warn() 功能的低階介面,明確傳入訊息、類別、檔名和行號,以及可選的模組名稱和註冊表(應為模組的 __warningregistry__ 字典)。模組名稱預設為去掉 .py 的檔名;如果沒有傳入註冊表,警告將永遠不會被抑制。message 必須是字串,category 必須是 Warning 的子類別,或者 message 可以是 Warning 的實例,在這種情況下 category 將被忽略。

module_globals(如果提供)應該是發出警告的程式碼所使用的全域命名空間。(此引數用於支援顯示在 zip 檔案或其他非檔案系統引入來源中找到的模組的原始碼)。

source(如果提供)是發出 ResourceWarning 的已銷毀物件。

在 3.6 版的變更: 新增 source 參數。

warnings.showwarning(message, category, filename, lineno, file=None, line=None)

將警告寫入一個檔案。預設實作會呼叫 formatwarning(message, category, filename, lineno, line) 並將結果字串寫入 file,預設為 sys.stderr。你可以透過賦值給 warnings.showwarning 來用任何可呼叫物件取代此函式。line 是要包含在警告訊息中的一行原始碼;如果未提供 line,showwarning() 將嘗試讀取由 filename 和 lineno 指定的行。

warnings.formatwarning(message, category, filename, lineno, line=None)

以標準方式格式化警告。這會回傳一個可能包含嵌入換行符並以換行符結尾的字串。line 是要包含在警告訊息中的一行原始碼;如果未提供 line,formatwarning() 將嘗試讀取由 filename 和 lineno 指定的行。

warnings.filterwarnings(action, message='', category=Warning, module='', lineno=0, append=False)

在 警告過濾器規格 串列中插入一個條目。預設情況下,條目會插入到最前面;如果 append 為 true,則會插入到最後面。這會檢查引數的型別,編譯 message 和 module 的正規表示式,並將它們作為一個元組插入到警告過濾器串列中。如果兩個條目都匹配一個特定的警告,那麼串列中較靠前的條目會覆寫較後面的條目。省略的引數預設為一個能匹配所有內容的值。

warnings.simplefilter(action, category=Warning, lineno=0, append=False)

在 警告過濾器規格 串列中插入一個簡單的條目。函式參數的含義與 filterwarnings() 相同,但不需要正規表示式,因為只要類別和行號匹配,插入的過濾器總是會匹配任何模組中的任何訊息。

warnings.resetwarnings()

重設警告過濾器。這會捨棄所有先前對 filterwarnings() 的呼叫效果,包括 -W 命令列選項和對 simplefilter() 的呼叫。

@warnings.deprecated(msg, *, category=DeprecationWarning, stacklevel=1)

用來指示一個類別、函式或重載 (overload) 已被棄用的裝飾器。

當此裝飾器應用於一個物件時,在使用該物件時,可能會在 runtime 發出棄用警告。靜態型別檢查器 也會在使用已棄用物件時產生診斷訊息。

用法:

from warnings import deprecated
from typing import overload

@deprecated("改用 B")
class A:
    pass

@deprecated("改用 g")
def f():
    pass

@overload
@deprecated("對 int 的支援已棄用")
def g(x: int) -> int: ...
@overload
def g(x: str) -> int: ...

由 category 指定的警告將在使用已棄用物件時於 runtime 發出。對於函式,這發生在呼叫時;對於類別,發生在實例化和建立子類別時。如果 category 是 None,則在 runtime 不會發出警告。stacklevel 決定了警告發出的位置。如果它是 1(預設值),警告會在已棄用物件的直接呼叫者處發出;如果更高,它會在堆疊的更上層發出。靜態型別檢查器的行為不受 category 和 stacklevel 引數的影響。

傳遞給裝飾器的棄用訊息會儲存在被裝飾物件的 __deprecated__ 屬性中。如果應用於重載,裝飾器必須在 @overload 裝飾器之後,這樣屬性才會存在於 typing.get_overloads() 回傳的重載上。

在 3.13 版被加入: 參閱 PEP 702。

可用的情境管理器

class warnings.catch_warnings(*, record=False, module=None, action=None, category=Warning, lineno=0, append=False)

一個情境管理器,它會複製並在退出時恢復警告過濾器和 showwarning() 函式。如果 record 引數為 False(預設值),情境管理器在進入時回傳 None。如果 record 為 True,則會回傳一個串列,該串列會由自訂的 showwarning() 函式(該函式也會抑制對 sys.stdout 的輸出)所看到的物件逐步填充。串列中的每個物件都具有與 showwarning() 的引數同名的屬性。

module 引數接受一個模組,用以取代預設的 warnings 模組,而該(被傳入)模組的過濾器將會受到保護。此引數主要用於測試 warnings 模組本身。

如果 action 引數不是 None,則其餘引數會傳遞給 simplefilter(),就像在進入情境時立即呼叫它一樣。

有關 category 和 lineno 參數的含義,請參閱 警告過濾器。

備註

catch_warnings 管理器的工作原理是替換然後再恢復模組的 showwarning() 函式和內部過濾器規格串列。這意味著情境管理器正在修改全域狀態,因此不是執行緒安全的。

在 3.11 版的變更: 新增 action、category、lineno 和 append 參數。