29.5. 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() 可以采用标准的日志部件处理所有警告。

29.5.1. 警告类别

There are a number of built-in exceptions that represent warning categories. This categorization is useful to be able to filter out groups of warnings. The following warnings category classes are currently defined:

描述

Warning

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

UserWarning

The default category for warn().

DeprecationWarning

Base category for warnings about deprecated features (ignored by default).

SyntaxWarning

用于警告可疑语法的基类。

RuntimeWarning

用于警告可疑运行时特性的基类

FutureWarning

Base category for warnings about constructs that will change semantically in the future.

PendingDeprecationWarning

用于警告即将废弃功能的基类(默认忽略)。

ImportWarning

导入模块时触发的警告的基类(默认忽略)。

UnicodeWarning

用于 Unicode 相关警告的基类。

BytesWarning

bytesbytearray 相关警告的基类

ResourceWarning

资源利用相关警告的基类。

While these are technically built-in exceptions, they are documented here, because conceptually they belong to the warnings mechanism.

通过对某个标准的警告类别进行派生,用户代码可以定义其他的警告类别。 警告类别必须是 Warning 类的子类。

29.5.2. 警告过滤器

警告过滤器控制着警告是否被忽略、显示或转为错误(触发异常)。

Conceptually, the warnings filter maintains an ordered list of filter specifications; any specific warning is matched against each filter specification in the list in turn until a match is found; the match determines the disposition of the match. Each entry is a tuple of the form (action, message, category, module, lineno), where:

  • action 是以下字符串之一:

    处置

    "error"

    将匹配警告转换为异常

    "ignore"

    从不打印匹配的警告

    "always"

    总是打印匹配的警告

    "default"

    print the first occurrence of matching warnings for each location where the warning is issued

    "module"

    print the first occurrence of matching warnings for each module where the warning is issued

    "once"

    无论位置如何,仅打印第一次出现的匹配警告

  • message 是包含正则表达式的字符串,警告信息的开头必须与之匹配。该表达式编译时不区分大小写。

  • category 是警告类别的类(Warning 的子类),警告类别必须是其子类,才能匹配。

  • module 是个字符串,包含了模块名称必须匹配的正则表达式。该表达式编译时大小写敏感。

  • lineno 是个整数,发生警告的行号必须与之匹配,或与所有行号匹配。

由于 Warning 类是由内置类 Exception 派生出来的,要把某个警告变成错误,只要触发``category(message)`` 即可。

The warnings filter is initialized by -W options passed to the Python interpreter command line. The interpreter saves the arguments for all -W options 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).

29.5.2.1. Default Warning Filters

By default, Python installs several warning filters, which can be overridden by the command-line options passed to -W and calls to filterwarnings().

在 3.2 版更改: 除了 PendingDeprecationWarning 之外,DeprecationWarning 现在默认会被忽略。

29.5.3. 暂时禁止警告

If you are using code that you know will raise a warning, such as a deprecated function, but do not want to see the warning, then it is possible to suppress the warning using the catch_warnings context manager:

import warnings

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

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

在上下文管理器中,所有的警告将被简单地忽略。这样就能使用已知的过时代码而又不必看到警告,同时也不会限制警告其他可能不知过时的代码。注意:只能保证在单线程应用程序中生效。如果两个以上的线程同时使用 catch_warnings 上下文管理器,行为不可预知。

29.5.4. 测试警告

要测试由代码引发的警告,请采用 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 上下文管理器,行为未定义。

当测试多项操作会引发同类警告时,重点是要确保每次操作都会触发新的警告(比如,将警告设置为异常并检查操作是否触发异常,检查每次操作后警告列表的长度是否有增加,否则就在每次新操作前将以前的警告列表项删除)。

29.5.5. Updating Code For New Versions of Python

Warnings that are only of interest to the developer are ignored by default. As such you should make sure to test your code with typically ignored warnings made visible. You can do this from the command-line by passing -Wd to the interpreter (this is shorthand for -W default). This enables default handling for all warnings, including those that are ignored by default. To change what action is taken for encountered warnings you simply change what argument is passed to -W, e.g. -W error. See the -W flag for more details on what is possible.

To programmatically do the same as -Wd, use:

warnings.simplefilter('default')

Make sure to execute this code as soon as possible. This prevents the registering of what warnings have been raised from unexpectedly influencing how future warnings are treated.

Having certain warnings ignored by default is done to prevent a user from seeing warnings that are only of interest to the developer. As you do not necessarily have control over what interpreter a user uses to run their code, it is possible that a new version of Python will be released between your release cycles. The new interpreter release could trigger new warnings in your code that were not there in an older interpreter, e.g. DeprecationWarning for a module that you are using. While you as a developer want to be notified that your code is using a deprecated module, to a user this information is essentially noise and provides no benefit to them.

The unittest module has been also updated to use the 'default' filter while running tests.

29.5.6. 可用的函数

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() 的调用效果。

29.5.7. 可用的上下文管理器

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

该上下文管理器会复制警告过滤器和 showwarning() 函数,并在退出时恢复。 如果 record 参数是 False (默认),则在进入时会返回 None。 如果 recordTrue,则返回一个列表,列表由自定义 showwarning() 函数所用对象逐步填充(该函数还会抑制 sys.stdout 的输出)。 列表中每个对象的属性与 showwarning() 的参数名称相同。

module 参数代表一个模块,当导入 warnings 时,将被用于代替返回的模块,其过滤器将被保护。该参数主要是为了测试 warnings 模块自身。

注解

catch_warnings 管理器的工作方式,是替换并随后恢复模块的 showwarning() 函数和内部的过滤器种类列表。这意味着上下文管理器将会修改全局状态,因此不是线程安全的。