warnings —— 警告信息的控制?

源代碼: Lib/warnings.py


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

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

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

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

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

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

參見(jiàn)

利用 logging.captureWarnings() 可以采用標(biāo)準(zhǔn)的日志架構(gòu)處理所有警告。

警告類別?

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

雖然在技術(shù)上警告類別屬于 內(nèi)置異常,但也只是在此記錄一下而已,因?yàn)樵诟拍钌纤麄儗儆诰鏅C(jī)制的一部分。

通過(guò)對(duì)某個(gè)標(biāo)準(zhǔn)的警告類別進(jìn)行派生,用戶代碼可以定義其他的警告類別。 警告類別必須是 Warning 類的子類。

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

描述

Warning

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

UserWarning

The default category for warn().

DeprecationWarning

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

SyntaxWarning

用于警告可疑語(yǔ)法的基類。

RuntimeWarning

用于警告可疑運(yùn)行時(shí)特性的基類。

FutureWarning

用于警告已廢棄特性的基類,這些警告是為 Python 應(yīng)用程序的最終用戶準(zhǔn)備的。

PendingDeprecationWarning

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

ImportWarning

導(dǎo)入模塊時(shí)觸發(fā)的警告的基類(默認(rèn)忽略)。

UnicodeWarning

用于 Unicode 相關(guān)警告的基類。

BytesWarning

bytesbytearray 相關(guān)警告的基類。

ResourceWarning

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

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

警告過(guò)濾器?

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

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

  • action 是以下字符串之一:

    處置

    "default"

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

    "error"

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

    "ignore"

    從不打印匹配的警告

    "always"

    總是打印匹配的警告

    "module"

    為發(fā)出警告的每個(gè)模塊打印第一次匹配警告(無(wú)論行號(hào)如何)

    "once"

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

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

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

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

  • lineno 是個(gè)整數(shù),發(fā)生警告的行號(hào)必須與之匹配,或?yàn)?0 表示與所有行號(hào)匹配。

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

如果警告不匹配所有已注冊(cè)的過(guò)濾器,那就會(huì)應(yīng)用 “default” 動(dòng)作(正如其名)。

警告過(guò)濾器的介紹?

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

每個(gè)警告過(guò)濾器的設(shè)定格式為冒號(hào)分隔的字段序列:

action:message:category:module:line

這些字段的含義在 警告過(guò)濾器 中描述。當(dāng)一行中列出多個(gè)過(guò)濾器時(shí)(如 PYTHONWARNINGS),過(guò)濾器間用逗號(hào)隔開(kāi),后面的優(yōu)先于前面的(因?yàn)槭菑淖蟮接覒?yīng)用的,最近應(yīng)用的過(guò)濾器優(yōu)先于前面的)。

常用的警告過(guò)濾器適用于所有的警告、特定類別的警告、由特定模塊和包引發(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"

默認(rèn)警告過(guò)濾器?

Python 默認(rèn)安裝了幾個(gè)警告過(guò)濾器,可以通過(guò) -W 命令行參數(shù)、 PYTHONWARNINGS 環(huán)境變量及調(diào)用 filterwarnings() 進(jìn)行覆蓋。

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

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

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

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

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

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

重寫默認(rèn)的過(guò)濾器?

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

import sys

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

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

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

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

暫時(shí)禁止警告?

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

import warnings

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

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

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

測(cè)試警告?

要測(cè)試由代碼引發(fā)的警告,請(qǐng)采用 catch_warnings 上下文管理器。有了它,就可以臨時(shí)改變警告過(guò)濾器以方便測(cè)試。例如,以下代碼可捕獲所有的警告以便查看:

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)因?yàn)?once / default 規(guī)則而被引發(fā),那么無(wú)論設(shè)置什么過(guò)濾器,該條警告都不會(huì)再出現(xiàn),除非該警告有關(guān)的注冊(cè)數(shù)據(jù)被清除。

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

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

為新版本的依賴關(guān)系更新代碼?

在默認(rèn)情況下,主要針對(duì) Python 開(kāi)發(fā)者(而不是 Python 應(yīng)用程序的最終用戶)的警告類別,會(huì)被忽略。

值得注意的是,這個(gè)“默認(rèn)忽略”的列表包含 DeprecationWarning (適用于每個(gè)模塊,除了 __main__),這意味著開(kāi)發(fā)人員應(yīng)該確保在測(cè)試代碼時(shí)應(yīng)將通常忽略的警告顯示出來(lái),以便未來(lái)破壞性 API 變化時(shí)及時(shí)收到通知(無(wú)論是在標(biāo)準(zhǔn)庫(kù)還是第三方包)。

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

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

可用的函數(shù)?

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

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

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

這會(huì)讓警告指向 deprecation() 的調(diào)用者,而不是 deprecation() 本身的來(lái)源(因?yàn)楹笳邥?huì)破壞引發(fā)警告的目的)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

警告過(guò)濾器種類 列表中插入一條簡(jiǎn)單數(shù)據(jù)項(xiàng)。函數(shù)參數(shù)的含義與 filterwarnings() 相同,但不需要正則表達(dá)式,因?yàn)椴迦氲倪^(guò)濾器總是匹配任何模塊中的任何信息,只要類別和行號(hào)匹配即可。

warnings.resetwarnings()?

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

可用的上下文管理器?

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

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

module 參數(shù)代表一個(gè)模塊,當(dāng)導(dǎo)入 warnings 時(shí),將被用于代替返回的模塊,其過(guò)濾器將被保護(hù)。該參數(shù)主要是為了測(cè)試 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 管理器的工作方式,是替換并隨后恢復(fù)模塊的 showwarning() 函數(shù)和內(nèi)部的過(guò)濾器種類列表。這意味著上下文管理器將會(huì)修改全局狀態(tài),因此不是線程安全的。

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