warnings --- 警告控制¶
原始碼:Lib/warnings.py
警告訊息通常會在需要提醒使用者程式中的某些狀況時發出,而該狀況(通常)不至於需要引發例外並終止程式。舉例來說,當程式使用一個過時的模組時,就可能會需要發出警告。
Python 程式設計師可以透過呼叫此模組中定義的 warn() 函式來發出警告。(C 語言的程式設計師則使用 PyErr_WarnEx();詳情請參閱 例外處理)。
警告訊息通常會被寫入 sys.stderr,但它們的處理方式可以被彈性地更改,從忽略所有警告到將它們轉為例外都可以。警告的處理方式會根據 警告類別、警告訊息的文本,以及發出警告的原始碼位置而有所不同。在同一個原始碼位置重複出現的特定警告通常會被抑制。
警告控制有兩個階段:首先,每當發出一個警告時,會先決定是否該發出訊息;接著,如果要發出訊息,它會使用一個可由使用者設定的掛鉤 (hook) 來格式化並印出。
是否要發出警告訊息是由 警告過濾器 所控制,它是一連串的匹配規則和動作。可以透過呼叫 filterwarnings() 來新增規則到過濾器中,並透過呼叫 resetwarnings() 將其重設為預設狀態。
警告訊息的印出是透過呼叫 showwarning() 來完成,而它可以被覆寫;此函式的預設實作會透過呼叫 formatwarning() 來格式化訊息,它也可以被自訂的實作使用。
也參考
logging.captureWarnings() 允許你使用標準的 logging 基礎設施來處理所有警告。
警告類別¶
有許多內建的例外代表警告類別。這種分類方式對於能夠過濾掉特定群組的警告很有用。
雖然這些在技術上是 內建例外,但它們被記錄在這裡,因為從概念上來說,它們屬於警告機制的一部分。
使用者程式碼可以透過繼承其中一個標準警告類別來定義額外的警告類別。一個警告類別必須永遠是 Warning 類別的子類別。
目前定義了以下警告類別:
類別 |
描述 |
|---|---|
這是所有警告類別的基底類別。它是 |
|
|
|
關於已棄用功能的警告的基底類別,當這些警告是針對其他 Python 開發者時(預設為忽略,除非由 |
|
Base category for warnings about dubious syntactic features (typically emitted when compiling Python source code, and hence may not be suppressed by runtime filters) |
|
關於可疑 runtime 功能的警告的基底類別。 |
|
關於已棄用功能的警告的基底類別,當這些警告是針對以 Python 編寫的應用程式的終端使用者時。 |
|
關於未來將被棄用的功能的警告的基底類別(預設為忽略)。 |
|
在引入模組過程中觸發的警告的基底類別(預設為忽略)。 |
|
與 Unicode 相關的警告的基底類別。 |
|
與資源使用相關的警告的基底類別(預設為忽略)。 |
在 3.7 版的變更: 在過去,DeprecationWarning 和 FutureWarning 是根據一個功能是被完全移除還是改變其行為來區分的。它們現在是根據其目標受眾以及預設警告過濾器處理它們的方式來區分。
警告過濾器¶
警告過濾器控制警告是被忽略、顯示,還是轉為錯誤(引發一個例外)。
從概念上講,警告過濾器維護一個有序的過濾器規格串列;任何特定的警告都會依次與串列中的每個過濾器規格進行比對,直到找到匹配項;過濾器決定了匹配項的處理方式。每個條目都是一個 (action, message, category, module, lineno) 形式的元組,其中:
action 是以下字串之一:
值
處理方式
"default"為發出警告的每個位置(模組 + 行號)印出第一次出現的匹配警告
"error"將匹配的警告轉為例外
"ignore"永不印出匹配的警告
"always"總是印出匹配的警告
"all""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()
在情境管理器中,所有警告都將被直接忽略。這允許你在使用已知的已棄用程式碼時不必看到警告,同時又不會抑制其他可能不知道自己正在使用已棄用程式碼的程式碼所發出的警告。
備註
See 情境管理器的並行安全性 for details on the concurrency-safety of the
catch_warningscontext manager when used in programs using multiple threads or async functions.
測試警告¶
要測試程式碼引發的警告,請使用 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 規則已經被引發過,那麼無論設定什麼過濾器,這個警告都不會再出現,除非與該警告相關的警告註冊表已被清除。
一旦情境管理器退出,警告過濾器就會恢復到進入情境時的狀態。這可以防止測試在不同測試之間以意想不到的方式更改警告過濾器,從而導致不確定的測試結果。
備註
See 情境管理器的並行安全性 for details on the concurrency-safety of the
catch_warningscontext manager when used in programs using multiple threads or async functions.
在測試多個會引發同類警告的操作時,重要的是要以一種能確認每個操作都在引發新警告的方式來進行測試(例如,將警告設定為引發例外,並檢查操作是否引發例外;檢查警告串列的長度在每次操作後是否持續增加;或者在每次新操作前從警告串列中刪除先前的條目)。
為新版相依套件更新程式碼¶
主要針對 Python 開發者(而非以 Python 編寫的應用程式的終端使用者)的警告類別預設會被忽略。
值得注意的是,這個「預設忽略」串列包括 DeprecationWarning(除了 __main__ 以外的所有模組),這意味著開發者應該確保在測試他們的程式碼時,讓通常被忽略的警告可見,以便及時收到未來 API 破壞性變更的通知(無論是在標準函式庫還是第三方套件中)。
在理想情況下,程式碼會有一個合適的測試套件,而測試執行器會在執行測試時負責隱式地啟用所有警告(unittest 模組提供的測試執行器就是這樣做的)。
在較不理想的情況下,可以透過將 -Wd 傳遞給 Python 直譯器(這是 -W default 的簡寫)或在環境中設定 PYTHONWARNINGS=default 來檢查應用程式是否使用了已棄用的介面。這會為所有警告啟用預設處理,包括那些預設被忽略的警告。要更改對遇到的警告所採取的動作,你可以更改傳遞給 -W 的引數(例如 -W error)。有關更多可能性的詳細資訊,請參閱