warnings —— 警告信息的控制?

源代碼: Lib/warnings.py


通常以下情況會引發警告:提醒用戶注意程序中的某些情況,而這些情況(通常)還不值得觸發異常并終止程序。例如,當程序用到了某個過時的模塊時,就可能需要發出一條警告。

Python 程序員可調用本模塊中定義的 warn() 函數來發布警告。(C 語言程序員則用 PyErr_WarnEx() ; 詳見 異常處理 )。

Warning messages are normally written to sys.stderr, but their disposition can be changed flexibly, from ignoring all warnings to turning them into exceptions. The disposition of warnings can vary based on the warning category (see below), the text of the warning message, and the source location where it is issued. Repetitions of a particular warning for the same source location are typically suppressed.

控制警告信息有兩個階段:首先,每次引發警告時,決定信息是否要發出;然后,如果要發出信息,就用可由用戶設置的鉤子進行格式化并打印輸出。

The determination whether to issue a warning message is controlled by the warning filter, which is a sequence of matching rules and actions. Rules can be added to the filter by calling filterwarnings() and reset to its default state by calling resetwarnings().

警告信息的打印輸出是通過調用 showwarning() 完成的,該函數可被重寫;默認的實現代碼是調用 formatwarning() 進行格式化,自己編寫的代碼也可以調用此格式化函數。

參見

利用 logging.captureWarnings() 可以采用標準的日志部件處理所有警告。

警告類別?

警告的類別由一些內置的異常表示。這種分類有助于對警告信息進行分組過濾。

雖然在技術上警告類別屬于 內置異常,但也只是在此記錄一下而已,因為在概念上他們屬于警告機制的一部分。

通過對某個標準的警告類別進行派生,用戶代碼可以定義其他的警告類別。 警告類別必須是 Warning 類的子類。

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

描述

Warning

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

UserWarning

The default category for 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 是包含正則表達式的字符串,警告信息的開頭必須與之匹配。該表達式編譯時不區分大小寫。

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

  • module 是個字符串,包含了模塊名稱必須匹配的正則表達式。該表達式編譯時大小寫敏感。

  • lineno 是個整數,發生警告的行號必須與之匹配,或與所有行號匹配。

由于 Warning 類是由內置類 Exception 派生出來的,要把某個警告變成錯誤,只要觸發``category(message)`` 即可。

如果警告不匹配所有已注冊的過濾器,那就會應用 “default” 動作(正如其名)。

警告過濾器的介紹?

The warnings filter is initialized by -W options passed to the Python interpreter command line and the PYTHONWARNINGS environment variable. The interpreter saves the arguments for all supplied entries without interpretation in sys.warnoptions; the warnings module parses these when it is first imported (invalid options are ignored, after printing a message to sys.stderr).

每個警告過濾器的設定格式為冒號分隔的字段序列:

action:message:category:module:line

The meaning of each of these fields is as described in 警告過濾器. When listing multiple filters on a single line (as for PYTHONWARNINGS), the individual filters are separated by commas,and the filters listed later take precedence over those listed before them (as they're applied left-to-right, and the most recently applied filters take precedence over earlier ones).

常用的警告過濾器適用于所有的警告、特定類別的警告、由特定模塊和包引發的警告。下面是一些例子:

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"
                             # and any subpackages of "mymodule"

默認警告過濾器?

Python 默認安裝了幾個警告過濾器,可以通過 -W 命令行參數、 PYTHONWARNINGS 環境變量及調用 filterwarnings() 進行覆蓋。

在常規發布的版本中,默認的警告過濾器包括(按優先順序排列):

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

在調試版本中,默認警告過濾器的列表是空的。

在 3.2 版更改: 除了 PendingDeprecationWarning 之外,DeprecationWarning 現在默認會被忽略。

在 3.7 版更改: DeprecationWarning 在被 __main__ 中的代碼直接觸發時,默認會再次顯示。

在 3.7 版更改: 如果指定兩次 -b,則 BytesWarning 不再出現在默認的過濾器列表中,而是通過 sys.warningoptions 進行配置。

重寫默認的過濾器?

Python 應用程序的開發人員可能希望在默認情況下向用戶隱藏 所有 Python級別的警告,而只在運行測試或其他調試時顯示這些警告。用于向解釋器傳遞過濾器配置的 sys.warningoptions 屬性可以作為一個標記,表示是否應該禁用警告:

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__ 以外的命名空間運行用戶代碼的交互式開發者,請確保 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 模塊提供的測試運行程序就是如此)。

在不太理想的情況下,可以通過向 Python 解釋器傳入 -Wd (這是 -W default 的簡寫) 或設置環境變量 PYTHONWARNINGS=default 來檢查應用程序是否用到了已棄用的接口。 這樣可以啟用對所有警告的默認處理操作,包括那些默認忽略的警告。 要改變遇到警告后執行的動作,可以改變傳給 -W 的參數 (例如 -W error)。 請參閱 -W 旗標來了解更多的細節。

可用的函數?

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

引發警告,或者忽略或引發異常。 如果給出 category 參數,則必須是警告類別類(見上文);默認為 UserWarning。 或者 message 可為 Warning 的實例,這時 category 將被忽略,轉而采用 message.__class__。 在這種情況下,錯誤信息文本將是 str(message)。 如果某條警告被警告過濾器改成了錯誤,本函數將觸發一條異常。 參數 stacklevel 可供 Python 包裝函數使用,比如:

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

這會讓警告指向 deprecation() 的調用者,而不是 deprecation() 本身的來源(因為后者會破壞引發警告的目的)。

source 是發出 ResourceWarning 的被銷毀對象。

在 3.6 版更改: 加入 source? 參數。

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

這是 warn() 函數的底層接口,顯式傳入消息、類別、文件名和行號,以及可選的模塊名和注冊表(應為模塊的 __warningregistry__ 字典)。 模塊名稱默認為去除了 .py 的文件名;如果未傳遞注冊表,警告就不會被抑制。 message 必須是個字符串,categoryWarning 的子類;或者*message* 可為 Warning 的實例,且 category 將被忽略。

module_globals 應為發出警告的代碼所用的全局命名空間。(該參數用于從 zip 文件或其他非文件系統導入模塊時顯式源碼)。

source 是發出 ResourceWarning 的被銷毀對象。

在 3.6 版更改: 加入 source 參數。

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

Write a warning to a file. The default implementation calls formatwarning(message, category, filename, lineno, line) and writes the resulting string to file, which defaults to sys.stderr. You may replace this function with any callable by assigning to warnings.showwarning. line is a line of source code to be included in the warning message; if line is not supplied, showwarning() will try to read the line specified by filename and lineno.

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

以標準方式格式化一條警告信息。將返回一個字符串,可能包含內嵌的換行符,并以換行符結束。如果未提供 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() 的調用效果。

可用的上下文管理器?

class warnings.catch_warnings(*, record=False, module=None)?

該上下文管理器會復制警告過濾器和 showwarning() 函數,并在退出時恢復。 如果 record 參數是 False (默認),則在進入時會返回 None。 如果 recordTrue,則返回一個列表,列表由自定義 showwarning() 函數所用對象逐步填充(該函數還會抑制 sys.stdout 的輸出)。 列表中每個對象的屬性與 showwarning() 的參數名稱相同。

module 參數代表一個模塊,當導入 warnings 時,將被用于代替返回的模塊,其過濾器將被保護。該參數主要是為了測試 warnings 模塊自身。

注解

catch_warnings 管理器的工作方式,是替換并隨后恢復模塊的 showwarning() 函數和內部的過濾器種類列表。這意味著上下文管理器將會修改全局狀態,因此不是線程安全的。