URLhttps://learnscript.net/zh-hant/python/exceptions/warnings/
    複製連結移至說明  範例

    Python 警告,警告過濾器介紹,以及發出和過濾警告

    閱讀 29:30·字數 8854·發佈 
    Youtube 頻道
    訂閱 375

    先決條件

    閱讀本節的先決條件是已經了解 Python 例外狀況,你可以檢視Python 例外狀況介紹,以及擲回和擷取例外狀況一節來獲得相關內容。

    Python 警告

    Python 中的Warning(警告)類別及其衍生類別用於表示一些不同於例外狀況的情況,在這些情況下,程式可能並不需要被中斷。比如,你的應用允許使用者使用類似於123456一樣的密碼,但會通過Warning類別提示使用者密碼過於簡單。

    警告類別可以被當作例外狀況類別來使用

    由於Warning類別繼承自Exception類別,因此,Warning類別及其衍生類別可以被try…except陳述式擷取,或被raise陳述式擲回,只不過,此時的Warning是一個單純的例外狀況。

    在下面的例子中,我們將Warning類別作為普通的例外狀況來使用,他被raise陳述式擲回,並被try…except陳述式擷取。

    raise.py
    # 將 Warning 類別作為例外狀況來使用
    try:
    	raise Warning
    except Warning as err:
    	print(type(err))
    <class 'Warning'>

    發出 Python 警告

    在 Python 中,應使用warnings模組的warn函式來建立並發出一個警告,而不是通過raise陳述式。Python 警告並不像例外狀況一樣,需要try…except陳述式的處理,發出警告通常不會導致程式的中斷。

    warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=None)

    message 參數

    message參數是一個字串(將作為警告資訊的一部分),或者一個警告物件(Warning類別或其衍生類別的執行個體)。如果是一個警告物件,那麽運算式str(message)的傳回值將作為警告資訊的一部分。

    category 參數

    category參數是類別Warning或者該類別的衍生類別(預設為類別UserWarning),用於表示warn函式所建立的警告的型別。當message參數被指定為一個警告物件時,category參數將被忽略,此時,警告的型別被視為與message.__class__相同。

    stacklevel 參數

    stacklevel參數是堆疊的層級,預設值為1,這表示warn函式產生的警告將指向呼叫warn函式的程式碼。如果你在某個函式中呼叫了warn,那麽可以將stacklevel設定為2,警告將指向呼叫該函式的程式碼,而不是呼叫warn的程式碼。

    source 參數

    source參數應該是一個有效的 Python 物件(可充當導致警告的原因),他的回溯資訊將顯示在警告中,要實作這種效果,你可能需要在命令列中使用參數-X tracemalloc來執行相關的 Python 腳本,否則將會收到提示Warning: Enable tracemalloc to get the object allocation traceback

    skip_file_prefixes 參數

    skip_file_prefixes參數是一個表示檔案路徑的字串元組,如果 Python 腳本檔案的路徑的開始部分,與元組中的某一元素相符(區分大小寫),那麽warn函式產生的警告不會指向該檔案所包含的程式碼,這意味著警告可能被定位至其他腳本檔案。如果skip_file_prefixes參數被有效的設定,那麽stacklevel參數將被修改為運算式max(2,stacklevel)的計算結果,也就是說,警告不會指向直接呼叫warn函式的程式碼。

    skip_file_prefixes 參數不應包含 Python 腳本檔案的副檔名部分

    如果你希望在warn函式的skip_file_prefixes參數中包含 Python 腳本檔案的路徑,那麽需要去掉檔案的副檔名部分(比如,.py),否則可能不會產生任何效果。

    在下面的範例中,函式check用於檢查日期並呼叫warn函式發出警告。參數stacklevel被設定為2,因此,警告將指向 Python 腳本檔案的第 12 行,而不是呼叫warn函式的第 9 行,參數source被設定為today,因此,將顯示today的回溯資訊。

    issue.py
    import warnings
    from datetime import date
    
    # 檢查日期的函式 def check(today): # 如果是 11 月 11 日,則發出警告 if today.month == 11 and today.day == 11: # 警告將指向 check 的呼叫者,並顯示 today 的回溯資訊 warnings.warn('11 月 11 日?', Warning, 2, today)
    goodday = date(2024, 11, 11) check(goodday)

    將命令列跳躍至 Python 腳本檔案issue.py所在的目錄,然後輸入以下命令執行該腳本檔案,你將看到相關的輸出結果。

    Windows
    python -X tracemalloc issue.py
    \issue.py:12: Warning: 11 月 11 日?
      check(goodday)
    Object allocated at (most recent call last):
      File "\issue.py", lineno 11
        goodday = date(2024, 11, 11)
    UNIX/Linux/macOS
    python3 -X tracemalloc issue.py
    /issue.py:12: Warning: 11 月 11 日?
      check(goodday)
    Object allocated at (most recent call last):
      File "/issue.py", lineno 11
        goodday = date(2024, 11, 11)

    在下面的範例中,模組studentwarn函式用於發出警告。由於指定了參數skip_file_prefixes,並包含了student模組所在的目錄,因此,在模組school中呼叫studentwarn函式之後,警告並不會指向 Python 腳本檔案student.py,而是會指向父資料夾中的腳本檔案school.py

    skip/student.py
    import warnings
    import os
    
    def warn(): # 取得 student 模組所在的目錄 filepath = os.path.dirname(__file__) warnings.warn( f'警告不會指向符合 {filepath} 的檔案', skip_file_prefixes=(filepath,) )
    school.py
    from skip import student
    # 呼叫 student 模組的 warn 函式
    student.warn()
    # Windows 中的輸出結果
    \school.py:3: UserWarning: 警告不會指向符合 \skip 的檔案
      student.warn()

    自訂 Python 警告的顯示方式

    在一般情況下,通過warnings模組的warn函式發出的警告,其相關資訊將被傳遞至sys模組的stderr變數,他是一個用於處理錯誤資訊的標準檔案物件,該物件在接收到警告的相關資訊之後,會輸出這些資訊,比如,顯示在命令列中。以上這些工作,由warnings模組的showwarning函式完成,通過編寫一個新的函式或方法來代替他(將新的函式或方法指派給warnings.showwarning),即可自訂警告資訊的顯示方式。

    當然,你也可以直接呼叫warnings模組的showwarning函式來顯示警告資訊,但這種情況很少見。

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

    message 參數

    message參數是一個字串(將作為警告資訊的一部分),或者一個警告物件(Warning類別或其衍生類別的執行個體)。

    category 參數

    category參數是警告的型別(Warning類別或其衍生類別)。

    filename 參數

    filename參數表示與警告相關聯的腳本檔案的路徑。

    lineno 參數

    lineno參數表示與警告相關聯的程式碼的行號。

    file 參數

    file參數是用於輸出警告資訊的物件,如果未指定,則showwarning會嘗試使用sys.stderr

    line 參數

    line參數是與警告相關聯的程式碼,如果未指定,則showwarning會嘗試通過filenamelineno參數讀取相關程式碼。

    自訂的 showwarning 函式需要自行處理未指定的 file 和 line 參數

    warnings模組原有的showwarning函式,會處理參數fileline為空值的情況,如果你定義了自己的showwarning函式,那麽需要在函式中完成同樣的工作。

    在下面的範例中,我們首先呼叫函式showwarning直接顯示了一條警告,然後定義了函式mywarning以自訂警告的顯示方式。

    showwarning.py
    import warnings
    
    # 第 4 行程式碼將作為警告資訊的一部分 message = '直接呼叫 showwarning' warnings.showwarning( message, UserWarning, filename=__file__, lineno=4 )
    # 用於顯示警告的函式 mywarning def mywarning(message, category, filename, lineno, file=None, line=None):
    # 處理 file,line 參數為 None 的情況 if not file: import sys file = sys.stderr
    if not line: line = open(filename, encoding='utf8').readlines()[lineno - 1]

    file.writelines(f'自訂警告:{message} {category} {line}')
    # 用 mywarning 取代原有的 showwarning 函式 warnings.showwarning = mywarning warnings.warn('再次警告')
    # Windows 中的輸出結果
    \test.py:4: UserWarning: 直接呼叫 showwarning
      message = '直接呼叫 showwarning'
    自訂警告:再次警告 <class 'UserWarning'> warnings.warn('再次警告')

    如果你並不想修改整個警告的顯示方式,僅希望自訂警告資訊中的文字內容,那麽可以編寫一個新的函式或方法來替代warnings模組原有的formatwarning函式。formatwarning函式與showwarning的參數的含義一致,但由於沒有file參數,自訂的formatwarning函式僅需要處理line參數為空值的情況。

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

    formatwarning 函式由 warnings 模組原有的 showwarning 函式呼叫

    當然,直接呼叫formatwarning函式將得到包含警告資訊的字串,但這種情況很少見,因為formatwarning函式總是由warnings模組原有的showwarning函式來呼叫,原有的showwarning函式會將formatwarning函式的傳回值寫入到file參數表示的 Python 檔案物件中。

    在下面的範例中,我們使用函式myinfo來自訂警告的文字內容。

    formatwarning.py
    import warnings
    
    # 用於格式化警告資訊的函式 myinfo def myinfo(message, category, filename, lineno, line=None):
    # 處理 line 參數為 None 的情況 if not line: line = open(filename, encoding='utf8').readlines()[lineno - 1]
    return f'簡化的資訊:{line} {category} {message}'
    # 用 myinfo 取代原有的 formatwarning 函式 warnings.formatwarning = myinfo warnings.warn('一個警告')
    簡化的資訊:warnings.warn('一個警告')
     <class 'UserWarning'> 一個警告

    Python 警告過濾器

    事實上,warnings模組的warn函式會根據參數來決定下一步的動作,警告可能會被轉換為一個例外狀況,也可能會被忽略。所有的這一切均由警告過濾器來控製,該過濾器是一個 Python 串列,對應了warnings模組的filters變數,串列中包含了一系列的比對規則,每一個規則均對應一個 Python 元組,其格式為(action,message,category,module,lineno)(元組中的每一項的作用將在下面列出)。當warn函式的參數符合某個比對規則,即相符項目messagecategorymodulelineno時,則該比對規則的action項,將指示warn函式如何進行下一步的操作。

    action 項

    action是一個字串,用來指示warn函式如何處理目前的警告,他擁有以下的有效取值,'default'表示指向相同模組的相同程式碼的所有同類警告,僅第一個會被顯示,'error'表示會將警告作為例外狀況擲回,'ignore'表示不會顯示警告資訊(即忽略警告),'always'表示總是顯示警告的資訊,'module'表示指向相同模組的所有同類警告,僅第一個會被顯示,'once'表示所有相符的同類警告,僅第一個會被顯示(實際上,'once''module'所實作的效果幾乎沒有差別)。

    message 項

    message項是一個表示正規表達式的字串,用於比對warn函式的message參數,message參數對應的字串的開頭部分應該與message項相符(不區分大小寫)。如果未指定message項,那麽message參數將不需要進行比對。

    category 項

    category項是Warning類別或其衍生類別,這表示警告的型別應該是category表示的類別或其衍生類別。

    module 項

    module項是一個表示正規表達式的字串,用於比對警告對應的模組,模組的完整名稱的開頭部分應該與module項相符(區分大小寫)。如果未指定module項,則表示與任意模組均相符。

    lineno 項

    lineno項是一個表示行號的整數,警告對應的行號應該與lineno項相符。如果lineno項為0,則表示與任意行號均相符。

    當 warn 函式的參數未能與警告過濾器的任何規則相符時

    如果warnings模組的warn函式的參數,未能與警告過濾器的任何規則相符,那麽warn函式接下來進行的操作,會等同於將action項設定為'default'的情況。

    警告過濾器的比對規則可以與不同類的警告相符

    你可以將message參數和警告型別相同的警告視為同類警告,警告過濾器的同一個比對規則能夠與不同類的警告相符。

    Python 警告過濾器的預設比對規則

    除了偵錯版本,Python 的警告過濾器會包含一些預設的比對規則,你可以通過warnings模組的filters變數來檢視他們。當然,在不同的 Python 版本中,預設的比對規則可能會不同。

    default_rules.py
    import warnings
    
    # 顯示所有的預設比對規則 for rule in warnings.filters: print(rule)
    ('default', None, <class 'DeprecationWarning'>, '__main__', 0)
    ('ignore', None, <class 'DeprecationWarning'>, None, 0)
    ('ignore', None, <class 'PendingDeprecationWarning'>, None, 0)
    ('ignore', None, <class 'ImportWarning'>, None, 0)
    ('ignore', None, <class 'ResourceWarning'>, None, 0)

    為 Python 警告過濾器新增比對規則

    在某些情況下,你可能想為警告過濾器新增比對規則,以修改一些警告的處理方法,warnings模組的filterwarnings函式可以完成此目標,通過該函式新增的比對規則,將成為warnings模組的filters串列的第一個元素(可將append參數設定為True,以讓新規則被新增至filters串列的末尾),這表示新加入的規則比原有規則的優先順序更高。除了appendfilterwarnings函式的參數與之前描述的警告過濾器比對規則中的各項的含義相同。

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

    在下面的範例中,我們使用函式filterwarnings讓同類的警告只能在同一個模組中顯示一次,無論他們指向的程式碼位於哪一行。

    same_module.py
    import warnings
    
    # 指向同一模組的同類警告,僅顯示一次 warnings.filterwarnings('module')
    # 顯示警告 A def test(): warnings.warn('警告 A', UserWarning)
    # test 函式中的警告將被顯示 test() # 下面的警告不會被顯示,因為他與 test 中的警告屬於同一類 warnings.warn('警告 A', UserWarning)

    # 顯示警告 B # 警告 B 將被顯示,因為他與警告 A 不是同一類 warnings.warn('警告 B', UserWarning) from datetime import date today = date(2024, 11, 11) # 下面的警告不會被顯示,因為他與上一個警告 B 屬於同一類 warnings.warn('警告 B', UserWarning, source=today) # 下面的警告會被顯示,因為他指向了另一個模組 warnings.warn('警告 B', UserWarning, stacklevel=2) # 下面的警告不會被顯示,因為他與上一個警告 B 屬於同一類,並且指向了同一個模組 import os warnings.warn('警告 B', UserWarning, skip_file_prefixes=(os.path.dirname(__file__),))
    # Windows 中的輸出結果
    \same_module.py:7: UserWarning: 警告 A
      warnings.warn('警告 A', UserWarning)
    \same_module.py:15: UserWarning: 警告 B
      warnings.warn('警告 B', UserWarning)
    sys:1: UserWarning: 警告 B

    除了filterwarnings函式,warnings模組的simplefilter函式同樣可以用於為警告過濾器新增比對規則,只不過他缺少了messagemodule兩個參數,這表示simplefilter函式所新增的比對規則更為簡單。

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

    在下面的範例中,函式simplefilter被呼叫了三次,其中第三次新增的規則將替代第二次新增的規則。

    simple.py
    import warnings
    
    # 忽略行號為 5 的型別為 UserWarning 的警告 warnings.simplefilter('ignore', UserWarning, 5) warnings.warn('我被忽略了', UserWarning)
    # 忽略行號為 11 的警告 warnings.simplefilter('ignore', lineno=11) # 下面的規則將替代上面的規則生效 warnings.simplefilter('always', lineno=11) warnings.warn('顯示')
    # Windows 中的輸出結果
    \simple.py:11: UserWarning: 顯示
      warnings.warn('顯示')

    清空 Python 警告過濾器的所有比對規則

    如果你已經為警告器新增了足夠多的比對規則,現在想要刪除他們,那麽可以使用warnings模組的resetwarnings函式,該函式會清空警告器中的所有比對規則,包括預設的比對規則在內,這會讓warnings模組的filters變數成為一個空的 Python 串列。

    resetwarnings()

    在下面的範例中,我們使用resetwarnings清空了所有的比對規則,通過print函式可以發現,warnings.filters成為了一個空的串列。

    reset.py
    import warnings
    
    # 為警告過濾器新增比對規則 # 忽略 message 參數開頭符合正規表達式 Err_.+ 的警告 warnings.filterwarnings('ignore', 'Err_.+') # 下面的警告不會顯示,因為 message 的開頭符合 Err_.+(不區分大小寫) warnings.warn('eRR_A 糟糕!我又吃癟了?') # 下面的警告會顯示,因為 message 的開頭含有一個空格,與 Err_.+ 不相符 warnings.warn(' Err_A 拒絕吃癟')
    # 清空 Python 警告過濾器的規則 warnings.resetwarnings() print(warnings.filters)
    # 下面的警告會顯示,因為清空了所有規則 warnings.warn('Err_A 糟糕!我又吃癟了?')
    # Windows 中的輸出結果
    \reset.py:9: UserWarning:  Err_A 拒絕吃癟
      warnings.warn(' Err_A 拒絕吃癟')
    []
    \reset.py:16: UserWarning: Err_A 糟糕!我又吃癟了?
      warnings.warn('Err_A 糟糕!我又吃癟了?')

    臨時改變 Python 警告過濾器的比對規則

    無論是為警告器新增比對規則,還是通過resetwarnings清空比對規則,你都可能希望這是一種臨時的改變,尤其是通過新增比對規則來測試一些警告的時候。使用warnings模組的catch_warnings類別和with陳述式可以完成上述目標,在通過with陳述式進入與catch_warnings物件相關的上下文之後,對警告過濾器的比對規則以及showwarning函式的修改將是臨時性的。

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

    record 參數

    record參數用於指示在進入與catch_warnings物件相關的上下文之後,是否將發出的警告記錄到一個串列中(每個警告將對應一個包含警告相關內容的WarningMessage物件),預設值為False,不記錄。如果record參數被設定為True,那麽可通過with陳述式的as關鍵字將儲存警告的串列繫結至某個變數,這樣做的好處在於,with陳述式中發出的警告不會直接顯示,你可以更容易的得知catch_warnings所產生的效果。如果record參數被設定為False,那麽儲存警告的串列並不會存在。

    module 參數

    module參數用於表示進入與catch_warnings物件相關的上下文之前的warnings模組,catch_warnings物件將備份該模組的filters(即警告過濾器的比對規則)和showwarning等特性,以便在with陳述式結束時恢復他們。如果未指定module參數,那麽將使用sys.modules中的warnings模組。一般情況下,module參數僅用於測試warnings模組本身,你可以將自己改寫的warnings模組指派給該參數,以檢視其執行效果。

    action,category,lineno,append 參數

    參數actioncategorylinenoappend與函式simplefilter的參數的含義相同,如果action被指定了一個有效的值,那麽在進入與catch_warnings物件相關的上下文之後,以上四個參數將用於呼叫函式simplefilter,這相當於在with陳述式中為警告過濾器新增了一個比對規則。

    在警告器的預設比對規則中,型別為ImportWarning的警告一般不會顯示,我們通過with陳述式和catch_warnings類別來調整規則,以臨時的顯示所有警告。在with陳述式結束之後,檢視warnings.filters表示的比對規則,發現第一項並不是在with陳述式中新增的規則。

    show_all.py
    import warnings
    
    # ImportWarning 在預設規則中不會顯示 warnings.warn('預設不會顯示的警告', ImportWarning)
    # 臨時讓所有警告均被顯示 with warnings.catch_warnings(action='always'): warnings.warn('現在可以顯示 ImportWarning', ImportWarning)
    # 檢視比對規則的第一條 print(warnings.filters[0])
    # Windows 中的輸出結果
    \show_all.py:8: ImportWarning: 現在可以顯示 ImportWarning
      warnings.warn('現在可以顯示 ImportWarning', ImportWarning)
    ('default', None, <class 'DeprecationWarning'>, '__main__', 0)

    在下面的程式碼中,我們把catch_warningsrecord參數設定為Truewith陳述式中的警告將被儲存至串列log

    record.py
    import warnings
    
    # 將警告儲存在 log 串列中 with warnings.catch_warnings(record=True) as log: warnings.warn('第一個警告', UserWarning) # 下面的警告不會被儲存至 log,因為該警告本身會被忽略 warnings.warn('第二個警告', ImportWarning)
    # 顯示 with 陳述式中發出的警告 for warning in log: print(f'{warning.message} {warning.lineno}')
    第一個警告 5

    通過命令列參數或環境變數為 Python 警告過濾器新增比對規則

    在某些情況下,你可能無法或不想通過編寫程式碼來調整警告過濾器的比對規則,Python 提供了命令列參數-W和環境變數PYTHONWARNINGS,以間接的新增比對規則,這些比對規則應符合下面給出的格式,其各項的含義已經在警告過濾器一段中講述,只不過這裏的messagemodule僅為單純的字串,而非正規表達式,並且其開頭和末尾的空白字元將被忽略。

    action:message:category:module:lineno

    如果希望省略某個項,只需要將其留空即可。如果希望新增多個比對規則,則需要在環境變數PYTHONWARNINGS中使用,分隔他們(比如,ignore,error::UserWarning),或者書寫多個-W參數(比如,-W ignore -W error::UserWarning)。

    除了命令列參數-W,你還可以使用-Wd-Wdefault(等同於-W default),-We-Werror(等同於-W error),-Wa-Walways(等同於-W always),-Wm-Wmodule(等同於-W module),-Wo-Wonce(等同於-W once),-Wi-Wignore(等同於-W ignore)等選項。

    設定環境變數 PYTHONWARNINGS 後可能需要重新啟動才能生效

    如果希望通過環境變數PYTHONWARNINGS來新增比對規則,那麽在對其設定後,你可能需要重新啟動一些應用程式,以使對PYTHONWARNINGS的改動生效。

    檢查你的程式碼是否使用了已經取代的內容

    如果未作出任何調整,那麽關於取代內容的警告DeprecationWarning可能不會顯示,這是可以理解的,因為在程式被正常使用時,這些警告資訊不應該展示給使用者。不過,你可以通過新增命令列選項-Wd-Wdefault-W default::DeprecationWarning,來確認程式碼是否使用了已經取代的內容。

    當然,要完成以上目標,設定環境變數PYTHONWARNINGS或直接通過warnings模組修改比對規則也是可行的。

    設定環境變數

    關於如何設定環境變數,你可以檢視程式設計指南如何設定 Windows 環境變數如何設定 UNIX/Linux/macOS 環境變數兩節。

    在預設情況下,腳本檔案w.py中的第一個警告不會顯示,第二個警告不會被轉換為例外狀況,但我們通過參數-W改變這種狀況。

    w.py
    import warnings
    # 正常情況下,下面的警告不會顯示
    warnings.warn('警告', ImportWarning)
    # 正常情況下,下面的警告不會作為例外狀況被擲回
    warnings.warn('Err 擲回例外狀況')
    Windows
    python -W default::ImportWarning -W error:Err w.py
    \w.py:3: ImportWarning: 警告
      warnings.warn('警告', ImportWarning)
    Traceback (most recent call last):

    UserWarning: Err 擲回例外狀況
    UNIX/Linux/macOS
    python3 -W default::ImportWarning -W error:Err w.py
    /w.py:3: ImportWarning: 警告
      warnings.warn('警告', ImportWarning)
    Traceback (most recent call last):

    UserWarning: Err 擲回例外狀況

    原始碼

    src/zh-hant/exceptions/warnings·codebeatme/python·GitHub