warnings — 警告控制

原始碼: Lib/warnings.py


警告訊息通常在程式中需要提醒使用者某種情況時發出,這種情況下(通常)不需要引發異常並終止程式。例如,當程式使用已過時的模組時,可能會發出警告。

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

警告訊息通常寫入 sys.stderr,但其處理方式可以靈活更改,從忽略所有警告到將其轉換為異常。警告的處理方式可以根據 警告類別、警告訊息的文字以及發出警告的源位置而異。同一源位置的特定警告重複通常會被抑制。

警告控制分為兩個階段:首先,每次發出警告時,會確定是否應該發出訊息;其次,如果需要發出訊息,則使用使用者可設定的鉤子對其進行格式化和列印。

是否發出警告訊息的決定由 警告過濾器 控制,它是一系列匹配規則和操作。可以透過呼叫 filterwarnings() 向過濾器新增規則,並透過呼叫 resetwarnings() 將其重置為預設狀態。

警告訊息的列印透過呼叫 showwarning() 完成,該函式可以被重寫;此函式的預設實現透過呼叫 formatwarning() 來格式化訊息,該函式也可供自定義實現使用。

參見

logging.captureWarnings() 允許您使用標準日誌記錄基礎設施處理所有警告。

警告類別

有許多內建異常代表警告類別。這種分類有助於篩選出特定組的警告。

雖然這些在技術上是 內建異常,但此處對其進行文件說明,因為它們在概念上屬於警告機制。

使用者程式碼可以透過繼承標準警告類別之一來定義額外的警告類別。警告類別必須始終是 Warning 類的子類。

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

描述

警告

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

UserWarning

warn() 的預設類別。

DeprecationWarning

用於警告已棄用功能的基類別,當這些警告旨在供其他 Python 開發者使用時(預設情況下忽略,除非由 __main__ 中的程式碼觸發)。

SyntaxWarning

用於警告可疑語法特徵的基類別(通常在編譯 Python 原始碼時發出,因此可能無法透過執行時過濾器抑制)

RuntimeWarning

用於警告可疑執行時特徵的基類別。

FutureWarning

用於警告已棄用功能的基類別,當這些警告旨在供用 Python 編寫的應用程式的終端使用者使用時。

PendingDeprecationWarning

用於警告將來會棄用功能的基類別(預設情況下忽略)。

ImportWarning

用於在匯入模組過程中觸發的警告的基類別(預設情況下忽略)。

UnicodeWarning

用於與 Unicode 相關的警告的基類別。

BytesWarning

用於與 bytesbytearray 相關的警告的基類別。

ResourceWarning

用於與資源使用相關的警告的基類別(預設情況下忽略)。

版本 3.7 中的變更: 以前 DeprecationWarningFutureWarning 是根據功能是完全移除還是改變其行為來區分的。現在它們是根據其目標受眾和預設警告過濾器處理它們的方式來區分的。

警告過濾器

警告過濾器控制警告是被忽略、顯示還是轉換為錯誤(引發異常)。

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

  • action 是以下字串之一

    處理方式

    "default"

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

    "error"

    將匹配的警告轉換為異常

    "ignore"

    從不列印匹配的警告

    "always"

    始終列印匹配的警告

    "all"

    “always” 的別名

    "module"

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

    "once"

    只打印匹配警告的第一次出現,無論位置如何

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

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

  • module 是一個字串,包含正則表示式,完全限定模組名的開頭必須與該正則表示式匹配,區分大小寫。在 -WPYTHONWARNINGS 中,module 是一個文字字串,完全限定模組名必須等於該字串(區分大小寫),忽略 module 開頭或結尾的任何空白字元。

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

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

如果報告的警告不匹配任何已註冊的過濾器,則應用“default”操作(因此得名)。

重複警告抑制標準

抑制重複警告的過濾器應用以下標準來確定警告是否被視為重複

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

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

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

描述警告過濾器

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

單獨的警告過濾器被指定為由冒號分隔的欄位序列

action:message:category:module:line

這些欄位的含義如 警告過濾器 中所述。當在一行中列出多個過濾器時(例如對於 PYTHONWARNINGS),各個過濾器由逗號分隔,後面列出的過濾器優先於前面列出的過濾器(因為它們從左到右應用,並且最新應用的過濾器優先於早期應用)。

常用的警告過濾器適用於所有警告、特定類別的警告或特定模組或包引發的警告。一些示例

default                      # Show all warnings (even those ignored by default)
ignore                       # Ignore all warnings
error                        # Convert all warnings to errors
error::ResourceWarning       # Treat ResourceWarning messages as errors
default::DeprecationWarning  # Show DeprecationWarning messages
ignore,default:::mymodule    # Only report warnings triggered by "mymodule"
error:::mymodule             # Convert warnings to errors in "mymodule"

預設警告過濾器

預設情況下,Python 安裝了幾個警告過濾器,這些過濾器可以透過命令列選項 -W、環境變數 PYTHONWARNINGS 和對 filterwarnings() 的呼叫來覆蓋。

在常規發行版構建中,預設警告過濾器具有以下條目(按優先順序順序)

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

除錯構建 中,預設警告過濾器列表為空。

版本 3.2 中的變更: DeprecationWarning 現在預設情況下被忽略,除了 PendingDeprecationWarning

版本 3.7 中的變更: __main__ 中的程式碼直接觸發 DeprecationWarning 時,預設情況下再次顯示。

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

覆蓋預設過濾器

用 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") # Change the filter in this process
    os.environ["PYTHONWARNINGS"] = "default" # Also affect subprocesses

最後,建議在非 __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:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

也可以透過使用 error 而不是 always 來使所有警告成為異常。需要注意的一點是,如果警告已經由於 once/default 規則而引發,那麼無論設定了什麼過濾器,除非與警告相關的警告登錄檔已被清除,否則警告將不會再次出現。

一旦上下文管理器退出,警告過濾器將恢復到進入上下文時的狀態。這可以防止測試在測試之間以意想不到的方式更改警告過濾器,並導致不確定的測試結果。

備註

有關在多執行緒或非同步函式程式中使用 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(如果提供)應該是發出警告的程式碼正在使用的全域性名稱空間。(此引數用於支援顯示 zipfile 或其他非檔案系統匯入源中找到的模組的原始碼)。

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

版本 3.6 中的變更: 添加了 source 引數。

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

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

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

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

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

警告過濾器規範 列表中插入一個條目。預設情況下,條目插入到前面;如果 append 為 true,則插入到末尾。此函式檢查引數型別,編譯 messagemodule 正則表示式,並將它們作為元組插入到警告過濾器列表中。列表中靠前的條目會覆蓋列表中靠後的條目,如果兩者都匹配特定警告。省略的引數預設為匹配所有內容的某個值。

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

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

warnings.resetwarnings()

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

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

裝飾器,用於指示類、函式或過載已被棄用。

當此裝飾器應用於物件時,在執行時使用該物件時可能會發出棄用警告。靜態型別檢查器 也將在使用已棄用物件時生成診斷資訊。

用法

from warnings import deprecated
from typing import overload

@deprecated("Use B instead")
class A:
    pass

@deprecated("Use g instead")
def f():
    pass

@overload
@deprecated("int support is deprecated")
def g(x: int) -> int: ...
@overload
def g(x: str) -> int: ...

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

傳遞給裝飾器的棄用訊息儲存在被裝飾物件的 __deprecated__ 屬性中。如果應用於過載,裝飾器必須在 @~typing.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。如果 recordTrue,則返回一個列表,該列表將由自定義 showwarning() 函式(該函式也抑制輸出到 sys.stdout)所見的警告物件逐步填充。列表中每個物件都具有與 showwarning() 引數相同的屬性名稱。

module 引數接受一個模組,該模組將代替匯入 warnings 時返回的模組,其過濾器將受到保護。此引數主要用於測試 warnings 模組本身。

如果 action 引數不為 None,則剩餘的引數將傳遞給 simplefilter(),如同在進入上下文時立即呼叫它一樣。

有關 categorylineno 引數的含義,請參閱 警告過濾器

備註

有關在多執行緒或非同步函式程式中使用 catch_warnings 上下文管理器時的併發安全性,請參閱 上下文管理器的併發安全性

版本 3.11 中的變更: 添加了 actioncategorylinenoappend 引數。

上下文管理器的併發安全性

catch_warnings 上下文管理器的行為取決於 sys.flags.context_aware_warnings 標誌。如果該標誌為 true,則上下文管理器以併發安全的方式執行,否則不安全。併發安全意味著它既是執行緒安全的,又可以在 asyncio 協程 和任務中使用。執行緒安全意味著在多執行緒程式中行為是可預測的。對於自由執行緒構建,該標誌預設為 true,否則為 false。

如果 context_aware_warnings 標誌為 false,則 catch_warnings 將修改 warnings 模組的全域性屬性。如果在併發程式(使用多個執行緒或使用 asyncio 協程)中使用,這是不安全的。例如,如果兩個或更多執行緒同時使用 catch_warnings 類,則行為是未定義的。

如果該標誌為 true,catch_warnings 不會修改全域性屬性,而是使用 ContextVar 來儲存新建立的警告過濾狀態。上下文變數提供執行緒區域性儲存,並使 catch_warnings 執行緒安全。

上下文處理器的 record 引數的行為也因標誌的值而異。當 record 為 true 且標誌為 false 時,上下文管理器透過替換並稍後恢復模組的 showwarning() 函式來工作。這不是併發安全的。

record 為 true 且標誌為 true 時,showwarning() 函式不會被替換。相反,記錄狀態由上下文變數中的內部屬性指示。在這種情況下,showwarning() 函式在退出上下文處理器時不會被恢復。

context_aware_warnings 標誌可以透過 -X context_aware_warnings 命令列選項或透過 PYTHON_CONTEXT_AWARE_WARNINGS 環境變數進行設定。

備註

大多數希望警告模組具有執行緒安全行為的程式可能也希望將 thread_inherit_context 標誌設定為 true。該標誌導致由 threading.Thread 建立的執行緒以從中啟動它的執行緒的上下文變數副本開始。當為 true 時,由 catch_warnings 在一個執行緒中建立的上下文也將適用於它啟動的新執行緒。如果為 false,新執行緒將以空的警告上下文變數開始,這意味著由 catch_warnings 上下文管理器建立的任何過濾都將不再活動。

版本 3.14 中的變更: 添加了 sys.flags.context_aware_warnings 標誌,並在該標誌為 true 時,為 catch_warnings 使用上下文變數。Python 的早期版本表現得好像該標誌始終設定為 false。