warnings — 警告控制

原始碼: Lib/warnings.py


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

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

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

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

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

警告訊息的列印是透過呼叫 showwarning() 來完成的,該函式可能會被覆蓋;此函式的預設實現透過呼叫 formatwarning() 來格式化訊息,該函式也可用於自定義實現。

另請參閱

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

警告類別

有許多內建異常表示警告類別。這種分類有助於過濾出警告組。

雖然這些在技術上是內建異常,但它們在此處記錄,因為從概念上講,它們屬於警告機制。

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

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

描述

Warning

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

UserWarning

warn() 的預設類別。

DeprecationWarning

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

SyntaxWarning

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

RuntimeWarning

關於可疑執行時功能的警告的基本類別。

FutureWarning

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

PendingDeprecationWarning

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

ImportWarning

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

UnicodeWarning

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

BytesWarning

bytesbytearray 相關的警告的基本類別。

ResourceWarning

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

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

警告過濾器

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

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

  • action 是以下字串之一

    處理方式

    "default"

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

    "error"

    將匹配的警告轉換為異常

    "ignore"

    從不列印匹配的警告

    "always"

    始終列印匹配的警告

    "module"

    列印每個發出警告的模組的第一次匹配警告(不考慮行號)

    "once"

    只打印第一次匹配的警告,不考慮位置

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

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

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

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

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

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

重複警告抑制標準

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

  • "default":只有當(messagecategorymodulelineno)都相同時,才將警告視為重複。

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

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

描述警告過濾器

警告過濾器由傳遞給 Python 直譯器命令列的 -W 選項和 PYTHONWARNINGS 環境變數初始化。直譯器將所有提供的條目的引數儲存到 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 版本中更改:除了 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") # 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 規則而已經引發了警告,那麼無論設定什麼過濾器,都不會再次看到該警告,除非與該警告相關的警告登錄檔已清除。

一旦上下文管理器退出,警告過濾器會恢復到進入上下文時的狀態。這可以防止測試在測試之間以意外的方式更改警告過濾器,從而導致不確定的測試結果。模組中的 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)。如果發出的特定警告透過 警告過濾器 被更改為錯誤,則此函式會引發異常。Python 中編寫的包裝函式可以使用 stacklevel 引數,如下所示

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

這使得警告引用 deprecated_api 的呼叫者,而不是 deprecated_api 本身的來源(因為後者會破壞警告訊息的目的)。

skip_file_prefixes 關鍵字引數可用於指示在計算堆疊級別時忽略哪些堆疊幀。當您希望警告始終出現在包外部的呼叫站點時,這很有用,而常量 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()package.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 是要包含在警告訊息中的原始碼行;如果未提供 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為真,則插入到列表的末尾。此函式檢查引數的型別,編譯 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__ 屬性中。如果應用於過載,則裝飾器必須在 @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 管理器的工作方式是替換,然後在稍後恢復模組的 showwarning() 函式和內部的過濾器規範列表。這意味著上下文管理器正在修改全域性狀態,因此不是執行緒安全的。

在 3.11 版本中更改: 添加了 actioncategorylinenoappend 引數。