warnings —— 警告信息的控制?

源代碼: Lib/warnings.py


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

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

警告信息通常會寫入 sys.stderr ,但可以靈活改變,從忽略所有警告到變成異常都可以。警告的處理方式可以依據(jù) 警告類型 、警告信息的文本和發(fā)出警告的源位置而進行變化。同一源位置重復出現(xiàn)的警告通常會被抑制。

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

警告過濾器 控制著是否發(fā)出警告信息,也即一系列的匹配規(guī)則和動作。調(diào)用 filterwarnings() 可將規(guī)則加入過濾器,調(diào)用 resetwarnings() 則可重置為默認狀態(tài)。

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

參見

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

警告類別?

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

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

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

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

描述

Warning

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

UserWarning

The default category for warn().

DeprecationWarning

已廢棄特性警告的基類,這些警告是為其他 Python 開發(fā)者準備的(默認會忽略,除非在 __main__ 中用代碼觸發(fā))。

SyntaxWarning

用于警告可疑語法的基類。

RuntimeWarning

用于警告可疑運行時特性的基類。

FutureWarning

用于警告已廢棄特性的基類,這些警告是為 Python 應用程序的最終用戶準備的。

PendingDeprecationWarning

用于警告即將廢棄功能的基類(默認忽略)。

ImportWarning

導入模塊時觸發(fā)的警告的基類(默認忽略)。

UnicodeWarning

用于 Unicode 相關警告的基類。

BytesWarning

bytesbytearray 相關警告的基類。

ResourceWarning

Base category for warnings related to resource usage (ignored by default).

在 3.7 版更改: 以前 DeprecationWarningFutureWarning 是根據(jù)某個功能是否完全刪除或改變其行為來區(qū)分的。現(xiàn)在是根據(jù)受眾和默認警告過濾器的處理方式來區(qū)分的。

警告過濾器?

警告過濾器控制著警告是否被忽略、顯示或轉(zhuǎn)為錯誤(觸發(fā)異常)。

從概念上講,警告過濾器維護著一個經(jīng)過排序的過濾器類別列表;任何具體的警告都會依次與列表中的每種過濾器進行匹配,直到找到一個匹配項;過濾器決定了匹配項的處理方式。每個列表項均為 ( action , message , category , module , lineno ) 格式的元組,其中:

  • action 是以下字符串之一:

    處置

    "default"

    為發(fā)出警告的每個位置(模塊+行號)打印第一個匹配警告

    "error"

    將匹配警告轉(zhuǎn)換為異常

    "ignore"

    從不打印匹配的警告

    "always"

    總是打印匹配的警告

    "module"

    為發(fā)出警告的每個模塊打印第一次匹配警告(無論行號如何)

    "once"

    無論位置如何,僅打印第一次出現(xiàn)的匹配警告

  • message 是包含正則表達式的字符串,警告信息的開頭必須與之匹配。該表達式編譯時不區(qū)分大小寫。

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

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

  • lineno 是個整數(shù),發(fā)生警告的行號必須與之匹配,或為 0 表示與所有行號匹配。

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

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

警告過濾器的介紹?

警告過濾器由傳給 Python 解釋器的命令行 -W 選項和 PYTHONWARNINGS 環(huán)境變量初始化。解釋器在 sys.warningoptions 中保存了所有給出的參數(shù),但不作解釋;warnings 模塊在第一次導入時會解析這些參數(shù)(無效的選項被忽略,并會先向 sys.stderr 打印一條信息)。

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

action:message:category:module:line

這些字段的含義在 警告過濾器 中描述。當一行中列出多個過濾器時(如 PYTHONWARNINGS),過濾器間用逗號隔開,后面的優(yōu)先于前面的(因為是從左到右應用的,最近應用的過濾器優(yōu)先于前面的)。

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

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 命令行參數(shù)、 PYTHONWARNINGS 環(huán)境變量及調(diào)用 filterwarnings() 進行覆蓋。

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

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

調(diào)試版本 中,默認警告過濾器的列表是空的。

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

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

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

重寫默認的過濾器?

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

import sys

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

建議 Python 代碼測試的開發(fā)者使用如下代碼,以確保被測代碼默認顯示 所有 警告:

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__ 以外的命名空間運行用戶代碼的交互式開發(fā)者,請確保 DeprecationWarning 在默認情況下是可見的,可采用如下代碼(這里 user_ns 是用于執(zhí)行交互式輸入代碼的模塊):

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

暫時禁止警告?

如果明知正在使用會引起警告的代碼,比如某個廢棄函數(shù),但不想看到警告(即便警告已經(jīng)通過命令行作了顯式配置),那么可以使用 catch_warnings 上下文管理器來抑制警告。

import warnings

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

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

在上下文管理器中,所有的警告將被簡單地忽略。這樣就能使用已知的過時代碼而又不必看到警告,同時也不會限制警告其他可能不知過時的代碼。注意:只能保證在單線程應用程序中生效。如果兩個以上的線程同時使用 catch_warnings 上下文管理器,行為不可預知。

測試警告?

要測試由代碼引發(fā)的警告,請采用 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 ,讓所有的警告都成為異常。需要注意的是,如果某條警告已經(jīng)因為 once / default 規(guī)則而被引發(fā),那么無論設置什么過濾器,該條警告都不會再出現(xiàn),除非該警告有關的注冊數(shù)據(jù)被清除。

一旦上下文管理器退出,警告過濾器將恢復到剛進此上下文時的狀態(tài)。這樣在多次測試時可防止意外改變警告過濾器,從而導致不確定的測試結果。模塊中的 showwarning() 函數(shù)也被恢復到初始值。注意:這只能在單線程應用程序中得到保證。如果兩個以上的線程同時使用 catch_warnings 上下文管理器,行為未定義。

當測試多項操作會引發(fā)同類警告時,重點是要確保每次操作都會觸發(fā)新的警告(比如,將警告設置為異常并檢查操作是否觸發(fā)異常,檢查每次操作后警告列表的長度是否有增加,否則就在每次新操作前將以前的警告列表項刪除)。

為新版本的依賴關系更新代碼?

在默認情況下,主要針對 Python 開發(fā)者(而不是 Python 應用程序的最終用戶)的警告類別,會被忽略。

值得注意的是,這個“默認忽略”的列表包含 DeprecationWarning (適用于每個模塊,除了 __main__),這意味著開發(fā)人員應該確保在測試代碼時應將通常忽略的警告顯示出來,以便未來破壞性 API 變化時及時收到通知(無論是在標準庫還是第三方包)。

理想情況下,代碼會有一個合適的測試套件,在運行測試時會隱含地啟用所有警告(由 unittest 模塊提供的測試運行程序就是如此)。

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

可用的函數(shù)?

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

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

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

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

source 是發(fā)出 ResourceWarning 的被銷毀對象。

在 3.6 版更改: 加入 source  參數(shù)。

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

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

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

source 是發(fā)出 ResourceWarning 的被銷毀對象。

在 3.6 版更改: 加入 source 參數(shù)。

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

將警告信息寫入文件。默認的實現(xiàn)代碼是調(diào)用``formatwarning(message, category, filename, lineno, line)`` 并將結果字符串寫入 file ,默認文件為 sys.stderr。通過將任何可調(diào)用對象賦給 warnings.showwarning 可替換掉該函數(shù)。line 是要包含在警告信息中的一行源代碼;如果未提供 line,showwarning() 將嘗試讀取由*filename* 和 lineno 指定的行。

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

以標準方式格式化一條警告信息。將返回一個字符串,可能包含內(nèi)嵌的換行符,并以換行符結束。如果未提供 line,formatwarning() 將嘗試讀取由 filenamelineno 指定的行。

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

警告過濾器種類 列表中插入一條數(shù)據(jù)項。默認情況下,該數(shù)據(jù)項將被插到前面;如果 append 為 True,則會插到后面。這里會檢查參數(shù)的類型,編譯 messagemodule 正則表達式,并將他們作為一個元組插入警告過濾器的列表中。如果兩者都與某種警告匹配,那么靠近列表前面的數(shù)據(jù)項就會覆蓋后面的項。省略的參數(shù)默認匹配任意值。

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

警告過濾器種類 列表中插入一條簡單數(shù)據(jù)項。函數(shù)參數(shù)的含義與 filterwarnings() 相同,但不需要正則表達式,因為插入的過濾器總是匹配任何模塊中的任何信息,只要類別和行號匹配即可。

warnings.resetwarnings()?

重置警告過濾器。這將丟棄之前對 filterwarnings() 的所有調(diào)用,包括 -W 命令行選項和對 simplefilter() 的調(diào)用效果。

可用的上下文管理器?

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

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

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

If the action argument is not None, the remaining arguments are passed to simplefilter() as if it were called immediately on entering the context.

備注

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

在 3.11 版更改: Added the action, category, lineno, and append parameters.