Python中的warnings模块详细阐述

  • 异常和警告
  • warnings — 非致命提醒
  • 警告类别
  • 警告过滤器
  • 默认警告过滤器
  • 可用函数
  • 生成警告
  • 使用模式过滤
  • 重复警告
  • 其他消息传递函数



由于在读

Python3 cookbook时遇到了

warnings,所以索性总结一番。并且经过数据分析实践时才发现在导入大量库包时使用warnings模块多有用,完全没有警告信息(本人有点强迫症,不喜欢红色的警告),事实上这些警告也不会有太大影响。

异常和警告

通常我们会在程序的编程的过程中就可以预期到一些会导致程序错误退出的事件,这种预期中的异常事件被称作异常(Exception).

而有些时候我们判断需要通过一些途径向用户发送消息以提醒用户在程序中的某些条件下不能保证引发异常并终止程序但又有一定的威胁.例如,当程序使用过时的模块时,我们就应该使用警告(warnings).在python中警告也是一种异常.

异常和警告是python语言中框架/服务开发重要工具.它为框架/服务提供了业务流的提醒和退出机制.
异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。 一般情况下,在Python无法正常处理程序时就会发生一个异常。 异常是Python对象,表示一个错误。 当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。 python的标准异常可以在官网文档中查看.
但是本文的重点不在异常的处理,本文主要阐述对于warnings的一些理解。

warnings — 非致命提醒

目的:向用户提供有关运行程序时遇到的问题的非致命警报

warnings 模块由 PEP 230 引入,作为一种警告程序员关于语言或库功能的变化的方法,以预测 Python 3.0 带来的向后不兼容的变化。它还可用于报告从丢失的库中恢复的配置错误或功能退化。不过,最好是通过 logging 模块提供面向用户的消息,因为发送到控制台的警告可能丢失。

由于警告不是致命的,因此程序在运行过程中可能会多次遇到相同的警告情况。warnings 模块抑制来自同一来源的重复消息,以减少一遍又一遍地看到相同警告的烦恼。输出可以根据具体情况进行控制,使用解释器的命令行选项或调用 warnings 中的函数。
Python 通过调用 warnings 模块中定义的 warn() 函数来发出警告。警告消息通常用于提示用户一些错误或者过时的用法,当这些情况发生时我们不希望抛出异常或者直接退出程序。警告消息通常写入 sys.stderr,对警告的处理方式可以灵活的更改,例如忽略或者转变为为异常。警告的处理可以根据警告类别,警告消息的文本和发出警告消息的源位置而变化。对相同源位置的特定警告的重复通常被抑制。

警告控制分为两个阶段:

  • 首先,警告被触发时,确定是否应该发出消息;
  • 接下来,如果要发出消息,则使用用户可设置的钩子来格式化和打印消息。

警告过滤器可以用来控制是否发出警告消息,警告过滤器是一些匹配规则和动作的序列。可以通过调用 filterwarnings() 将规则添加到过滤器,并通过调用 resetwarnings() 将其重置为默认状态。

警告消息的输出是通过调用 showwarning() 函数来完成的,其可以被覆盖;该函数的默认实现通过调用 formatwarning() 格式化消息,这也可以由自定义实现使用。

警告类别

內建警告类型:


描述

Warning

所有警告类别类的基类,它是 Exception 的子类

UserWarning

函数 warn() 的默认类别

DeprecationWarning

用于已弃用功能的警告(默认被忽略)

SyntaxWarning

用于可疑语法的警告

RuntimeWarning

用于有关可疑运行时功能的警告

FutureWarning

对于未来特性更改的警告

PendingDeprecationWarning

对于未来会被弃用的功能的警告(默认将被忽略)

ImportWarning

导入模块过程中触发的警告(默认被忽略)

UnicodeWarning

与 Unicode 相关的警告

BytesWarning

与 bytes 和 bytearray 相关的警告 (Python3)

ResourceWarning

与资源使用相关的警告(Python3)

可以通过继承內建警告类型来实现自定义的警告类型,警告类型必须始终是 Warning 类的子类。

警告过滤器

警告过滤器用于控制警告的行为,如忽略,显示或转换为错误(引发异常)。警告过滤器维护着一个有序的过滤规则列表,匹配规则用于确定如何处理警告,任何特定警告都将依次与列表中的每个过滤规则匹配,直到找到匹配为止。过滤规则类型为一个元组 (action,message,category,module,lineno),其中:

  • action 为以下值:
值	        处理方式
"error"	    将匹配警告转换为异常
"ignore"	忽略匹配的警告
"always"	始终输出匹配的警告
"default"	对于同样的警告只输出第一次出现的警告
"module"	在一个模块中只输出第一次出现的警告
"once"	    输出第一次出现的警告,而不考虑它们的位置
  • message 是包含正则表达式的字符串,警告消息的开始必须匹配,不区分大小写
  • category 是一个警告类型(必须是 Warning 的子类)
  • module 是包含模块名称的正则表达式字符串,区分大小写
  • lineno 是一个整数,警告发生的行号,为 0 则匹配所有行号

默认警告过滤器

默认情况下,Python 设置了几个警告过滤器,可以通过 -W 命令行选项和调用 filterwarnings() 函数来覆盖它们。

DeprecationWarningPendingDeprecationWarningImportWarning 被默认忽略。
除非 -b 选项给出一次或两次,否则忽略 BytesWarning;在这种情况下,此警告或者被输出(-b)或者变成异常(-bb)。
除非 Python 是在调试模式下构建的,否则将忽略 ResourceWarning
在 3.2 版中的调整: 除 PendingDeprecationWarning 之外,默认情况下将忽略 DeprecationWarning

可用函数

1. warn

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

触发异常。category 参数默认为 UserWarningmessage 参数为警告消息,可以是 Warning 实例,在这种情况下,将忽略 category 并使用 message.__class__,消息文本则为 str(message)

2.warn_explicit

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

这是 warn() 函数的低级接口,明确传递消息,类别,文件名和行号,以及可选的模块名称和注册表(应该是模块的 __warningregistry__ 字典)

3.showwarning

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

写入警告到文件。默认调用 formatwarning(message, category, filename, lineno, line) 并将结果字符串写入 file,默认为 sys.stderrline 是包含在警告消息中的一行源代码;如果未提供则尝试读取由 filenamelineno 指定的行。

4.formatwarning

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

格式化警告,返回一个字符串。可能包含嵌入的换行符,并以换行符结束。 line 是包含在警告消息中的一行源代码;如果不提供则尝试读取由 filename 和 lineno 指定的行。

5.filterwarnings

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

过滤警告,在 警告过滤器规则 列表中插入一个条目。默认情况下,条目插入在前面;如果 append 为真,则在末尾插入。它检查参数的类型,编译 message 和 module 的正则表达式,并将它们作为警告过滤器列表中的元组插入。如果多个地方都匹配特定的警告,那么更靠近列表前面的条目会覆盖列表中后面的条目,省略的参数默认为匹配一切的值。

6.simplefilter

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

简单易用的过滤器,类似 filterwarnings() 函数,但是不需要正则表达式。

7.resetwarnings

warnings.resetwarnings()

重置警告过滤器。这会丢弃所有以前对 filterwarnings() 调用的影响,包括 -W 命令行选项和对 simplefilter() 的调用的影响。

生成警告

发出警告的最简单方法是使用消息作为参数调用 warn()

import warnings

print('Before the warning')
warnings.warn('This is a warning message')
print('After the warning')

然后,当程序运行时,将打印该消息。

$ python3 -u warnings_warn.py

Before the warning
warnings_warn.py:13: UserWarning: This is a warning message
  warnings.warn('This is a warning message')
After the warning

即使打印了警告,默认行为是继续经过该点并运行程序的其余部分。 可以使用过滤器更改该行为。

warnings_warn_raise.py

import warnings

warnings.simplefilter('error', UserWarning)

print('Before the warning')
warnings.warn('This is a warning message')
print('After the warning')

在这个例子中, simplefilter() 函数在内部过滤器列表中添加一个条目,告诉 warnings 模块在发出 UserWarning 警告时引发异常。

$ python3 -u warnings_warn_raise.py

Before the warning
Traceback (most recent call last):
  File "warnings_warn_raise.py", line 15, in <module>
    warnings.warn('This is a warning message')
UserWarning: This is a warning message

也可以通过使用解释器的 -W 选项从命令行控制过滤器行为。 将过滤器属性指定为一个字符串,其中五个部分(操作、消息、类别、模块和行号)以冒号( : )分隔。 例如,如果运行 warnings_warn.py 并将过滤器设置为在 UserWarning 上引发错误,则会产生异常。

$ python3 -u -W "error::UserWarning::0" warnings_warn.py

Before the warning
Traceback (most recent call last):
  File "warnings_warn.py", line 13, in <module>
    warnings.warn('This is a warning message')
UserWarning: This is a warning message

由于 message 和 module 的字段留空,因此它们被解释为匹配任何内容。

使用模式过滤

要以编程方式过滤更复杂的规则,请使用 filterwarnings() 。 例如,要根据消息文本的内容进行过滤,请将正则表达式模式作为 message 参数。

warnings_filterwarnings_message.py

import warnings

warnings.filterwarnings('ignore', '.*do not.*',)

warnings.warn('Show this message')
warnings.warn('Do not show this message')

该模式包含「 do not 」,但实际消息使用「 Do not 」。 模式匹配,因为始终编译正则表达式以查找不区分大小写的匹配项。

$ python3 warnings_filterwarnings_message.py

warnings_filterwarnings_message.py:14: UserWarning: Show this
message
  warnings.warn('Show this message')

下面的示例程序会生成两个警告。

import warnings

warnings.warn('Show this message')
warnings.warn('Do not show this message')

可以使用命令行上的 filter 参数忽略其中一个警告。

$ python3 -W "ignore:do not:UserWarning::0" warnings_filter.py

warnings_filter.py:12: UserWarning: Show this message
  warnings.warn('Show this message')

相同的模式匹配规则适用于包含生成警告的调用的源模块的名称。 通过将模块名称作为模式传递给 module 参数来抑制来自 warnings_filter 模块的所有消息。

warnings_filterwarnings_module.py

import warnings

warnings.filterwarnings(
    'ignore',
    '.*',
    UserWarning,
    'warnings_filter',
)

import warnings_filter

由于过滤器已就位,因此在导入 warnings_filter 时不会发出警告。

$ python3 warnings_filterwarnings_module.py

要仅抑制 warnings_filter 第 13 行的消息,请将行号包含为 filterwarnings() 的最后一个参数。 使用源文件中的实际行号来限制过滤器,或使用 0 使过滤器适用于所有出现的消息。

warnings_filterwarnings_lineno.py

import warnings

warnings.filterwarnings(
    'ignore',
    '.*',
    UserWarning,
    'warnings_filter',
    13,
)

import warnings_filter

模式匹配任何消息,因此重要的参数是模块名称和行号。

$ python3 warnings_filterwarnings_lineno.py

.../warnings_filter.py:12: UserWarning: Show this message
  warnings.warn('Show this message')

重复警告

默认情况下,大多数类型的警告仅在第一次出现在给定位置时打印,其中「 location 」由生成警告的模块和行号组合定义。

import warnings

def function_with_warning():
    warnings.warn('This is a warning!')

function_with_warning()
function_with_warning()
function_with_warning()

此示例多次调用相同的函数,但会生成一个警告。

$ python3 warnings_repeated.py

warnings_repeated.py:14: UserWarning: This is a warning!
  warnings.warn('This is a warning!')

「 once 」动作可用于抑制来自不同位置的同一消息的实例。

warnings_once.py

import warnings

warnings.simplefilter('once', UserWarning)

warnings.warn('This is a warning!')
warnings.warn('This is a warning!')
warnings.warn('This is a warning!')

将保存所有警告的消息文本,并且仅打印唯一消息。

$ python3 warnings_once.py

warnings_once.py:14: UserWarning: This is a warning!
  warnings.warn('This is a warning!')

类似地,「 module 」将禁止来自同一模块的重复消息,无论哪个行号。

其他消息传递函数

通常警告会打印到 sys.stderr 。通过替换 warnings 模块中的 showwarning() 函数来改变这种行为。 例如,要将警告发送到日志文件而不是标准错误流,请将 showwarning() 替换为记录警告的函数。

import warnings
import logging

def send_warnings_to_log(message, category, filename, lineno,
                         file=None, line=None):
    logging.warning(
        '%s:%s: %s:%s',
        filename, lineno,
        category.__name__, message,
    )

logging.basicConfig(level=logging.INFO)

old_showwarning = warnings.showwarning
warnings.showwarning = send_warnings_to_log

warnings.warn('message')

当调用 warn() 时,警告将与其余的日志消息一起发出。

$ python3 warnings_showwarning.py

WARNING:root:warnings_showwarning.py:28: UserWarning:message