warnings — 警告控制

源代码: Lib/warnings.py


通常在需要提醒用户程序中某些条件的情况下发出警告消息,而该条件(通常)不保证引发异常并终止程序。例如,当程序使用过时的模块时,可能需要发出警告。

Python 程序员通过调用此模块中定义的 warn() 函数来发出警告。(C 程序员使用 PyErr_WarnEx();有关详细信息,请参阅 异常处理)。

警告消息通常会写入 sys.stderr,但可以灵活地更改其处理方式,从忽略所有警告到将它们转换成异常。警告的处理方式可能因 警告类别、警告消息的文本以及发出警告的源位置而异。通常会禁止对同一源位置重复发出特定警告。

警告控制分为两个阶段:首先,每次发出警告时,都会确定是否应该发出消息;其次,如果要发出消息,则使用用户可设置的挂钩对其进行格式化和打印。

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

通过调用showwarning()打印警告消息,该函数可以被覆盖;此函数的默认实现通过调用formatwarning()格式化消息,自定义实现也可以使用此函数。

另请参见

logging.captureWarnings()允许你使用标准日志记录基础设施处理所有警告。

警告类别

有一些内置异常表示警告类别。此分类对于能够过滤出警告组非常有用。

虽然这些在技术上是内置异常,但它们在此处记录,因为在概念上它们属于警告机制。

用户代码可以通过子类化一个标准警告类别来定义其他警告类别。警告类别必须始终是Warning类的子类。

当前定义了以下警告类别类

说明

Warning

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

UserWarning

warn()的默认类别。

DeprecationWarning

当这些警告针对其他 Python 开发人员时,有关已弃用功能的警告的基本类别(默认情况下忽略,除非由__main__中的代码触发)。

SyntaxWarning

有关可疑语法功能的警告的基本类别。

RuntimeWarning

有关可疑运行时功能的警告的基本类别。

FutureWarning

当这些警告针对以 Python 编写的应用程序的最终用户时,有关已弃用功能的警告的基本类别。

PendingDeprecationWarning

有关将来将被弃用的功能的警告的基本类别(默认情况下忽略)。

ImportWarning

在导入模块的过程中触发的警告的基本类别(默认情况下忽略)。

UnicodeWarning

与 Unicode 相关的警告的基本类别。

BytesWarning

bytesbytearray 相关的警告的基本类别。

ResourceWarning

与资源使用相关的警告的基本类别(默认情况下忽略)。

在 3.7 版本中更改: 以前,DeprecationWarningFutureWarning 的区别在于某个功能是完全移除还是更改其行为。现在,它们的区别在于其目标受众以及默认警告过滤器处理它们的方式。

警告过滤器

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

从概念上讲,警告过滤器维护一个有序的过滤器规范列表;任何特定警告都依次与列表中的每个过滤器规范进行匹配,直到找到匹配项;过滤器确定匹配项的处理方式。每个条目都是一个元组,格式为 (操作, 消息, 类别, 模块, 行号),其中

  • 操作是以下字符串之一

    处理方式

    "default"

    打印每个位置(模块 + 行号)的匹配警告的首次出现,其中发出警告

    "error"

    将匹配警告转换为异常

    "ignore"

    从不打印匹配警告

    "always"

    始终打印匹配警告

    "module"

    打印每个模块的匹配警告的首次出现,其中发出警告(无论行号如何)

    "once"

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

  • 消息是一个包含正则表达式的字符串,警告消息的开头必须与之匹配,不区分大小写。在 -WPYTHONWARNINGS 中,消息是一个文字字符串,警告消息的开头必须包含它(不区分大小写),忽略 消息 开头或结尾处的任何空格。

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

  • module 是一个包含正则表达式的字符串,完全限定模块名称的开头必须与之匹配,区分大小写。在 -WPYTHONWARNINGS 中,module 是一个文字字符串,完全限定模块名称必须等于它(区分大小写),忽略 module 开头或结尾的任何空格。

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

由于 Warning 类派生自内置 Exception 类,因此要将警告转换为错误,我们只需引发 category(message)

如果报告了警告并且与任何已注册的过滤器不匹配,则应用“默认”操作(因此得名)。

描述警告过滤器

警告过滤器由 -W 选项初始化,该选项传递给 Python 解释器命令行和 PYTHONWARNINGS 环境变量。解释器将所有提供的条目的参数保存在 sys.warnoptions 中,不进行解释;warnings 模块在首次导入时解析这些内容(忽略无效选项,在 sys.stderr 中打印一条消息后)。

各个警告过滤器指定为一个由冒号分隔的字段序列

action:message:category:module:line

这些字段的每个含义如 警告过滤器 中所述。在单行上列出多个过滤器(如 PYTHONWARNINGS)时,各个过滤器由逗号分隔,并且后面列出的过滤器优先于前面列出的过滤器(因为它们从左到右应用,并且最近应用的过滤器优先于较早的过滤器)。

常用的警告过滤器适用于所有警告、特定类别中的警告或由特定模块或包引发的警告。一些示例

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"

默认警告过滤器

默认情况下,Python 会安装几个警告过滤器,这些过滤器可以通过 -W 命令行选项、PYTHONWARNINGS 环境变量以及对 filterwarnings() 的调用来覆盖。

在常规发行版本中,默认警告过滤器的条目如下(按优先级顺序排列)

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

调试版本 中,默认警告过滤器列表为空。

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

在 3.7 版中更改:DeprecationWarning 在由 __main__ 中的代码直接触发时,现在再次默认显示。

在 3.7 版中更改:BytesWarning 不再出现在默认过滤器列表中,而是当两次指定 -b 时,通过 sys.warnoptions 进行配置。

覆盖默认过滤器

用 Python 编写的应用程序的开发者可能希望默认情况下向其用户隐藏所有 Python 级警告,并且仅在运行测试或以其他方式处理应用程序时显示它们。用于将过滤器配置传递给解释器的 sys.warnoptions 属性可用作指示是否应禁用警告的标记

import sys

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

建议 Python 代码的测试运行器的开发者确保默认情况下对要测试的代码显示所有警告,使用类似以下的代码

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__ 之外的命名空间中运行用户代码的交互式 shell 的开发者确保默认情况下使 DeprecationWarning 消息可见,使用类似以下的代码(其中 user_ns 是用于执行交互式输入代码的模块)

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

暂时禁止警告

如果您使用的是您知道会引发警告的代码,例如已弃用的函数,但不想看到警告(即使已通过命令行明确配置警告),那么可以使用 catch_warnings 上下文管理器来禁止警告

import warnings

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

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

在上下文管理器中,所有警告都将被忽略。这允许您使用已知的已弃用代码,而无需看到警告,同时不会禁止其他可能不知道其使用已弃用代码的代码的警告。注意:这只能在单线程应用程序中得到保证。如果两个或更多线程同时使用 catch_warnings 上下文管理器,则行为未定义。

测试警告

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

在测试引发相同类型警告的多个操作时,重要的是以一种方式对其进行测试,以确认每个操作都引发了一个新警告(例如,将警告设置为异常并检查操作引发异常,检查警告列表的长度在每次操作后继续增加,或者在每次新操作之前从警告列表中删除以前的条目)。

更新依赖项的新版本代码

主要对 Python 开发人员(而不是用 Python 编写的应用程序的最终用户)感兴趣的警告类别在默认情况下会被忽略。

值得注意的是,此“默认情况下忽略”列表包括 DeprecationWarning(对于 __main__ 以外的每个模块),这意味着开发人员应确保使用通常被忽略的警告对他们的代码进行测试,以便及时收到未来破坏性 API 更改的通知(无论是在标准库还是第三方包中)。

在理想情况下,代码将拥有一个合适的测试套件,并且测试运行器将负责在运行测试时隐式启用所有警告(unittest 模块提供的测试运行器会执行此操作)。

在不太理想的情况下,可以通过将 -Wd 传递给 Python 解释器(这是 -W default 的简写)或在环境中设置 PYTHONWARNINGS=default 来检查应用程序是否使用了已弃用的接口。这将启用对所有警告(包括默认情况下被忽略的警告)的默认处理。要更改对遇到的警告执行的操作,可以更改传递给 -W 的参数(例如 -W error)。有关可执行操作的更多详细信息,请参阅 -W 标志。

可用函数

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

发出警告,或者忽略它或引发异常。如果提供了类别参数,它必须是 警告类别类;它默认为 UserWarning。或者,消息可以是 Warning 实例,在这种情况下,将忽略类别,并将使用 message.__class__。在这种情况下,消息文本将是 str(message)。如果 警告过滤器 将发出的特定警告更改为错误,则此函数将引发异常。stacklevel 参数可由用 Python 编写的包装函数使用,如下所示

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

这使得警告引用 deprecated_api 的调用者,而不是 deprecated_api 本身(因为后者会破坏警告消息的目的)。

skip_file_prefixes 关键字参数可用于指示在计算堆栈级别时忽略哪些堆栈帧。当您希望警告始终出现在包外部的调用站点时,这可能很有用,而常量 stacklevel 并不适合所有调用路径或在其他方面难以维护。如果提供,它必须是一个字符串元组。当提供前缀时,stacklevel 会隐式覆盖为 max(2, stacklevel)。要使警告归因于当前包外部的调用者,您可以编写

# example/lower.py
_warn_skips = (os.path.dirname(__file__),)

def one_way(r_luxury_yacht=None, t_wobbler_mangrove=None):
    if r_luxury_yacht:
        warnings.warn("Please migrate to t_wobbler_mangrove=.",
                      skip_file_prefixes=_warn_skips)

# example/higher.py
from . import lower

def another_way(**kw):
    lower.one_way(**kw)

这使得警告仅从 example 包外部的调用代码引用 example.lower.one_way()package.higher.another_way() 调用站点。

如果提供了source,则是发出 ResourceWarning 的已销毁对象。

在 3.6 版中更改:添加了 source 参数。

在 3.12 版中更改:添加了 skip_file_prefixes

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)

将警告写入文件。默认实现调用 formatwarning(message, category, filename, lineno, line),并将结果字符串写入 file,其默认为 sys.stderr。您可以通过分配给 warnings.showwarning 来用任何可调用对象替换此函数。line 是要包含在警告消息中的源代码行;如果未提供 lineshowwarning() 将尝试读取由 filenamelineno 指定的行。

警告。格式化警告(消息, 类别, 文件名, 行号, =)

以标准方式格式化警告。这会返回一个可能包含嵌入换行符并以换行符结尾的字符串。是包含在警告消息中的源代码行;如果未提供formatwarning() 将尝试读取由文件名行号指定的行。

警告。筛选警告(操作, 消息='', 类别=警告, 模块='', 行号=0, 追加=)

将一个条目插入到警告筛选规范列表中。默认情况下,该条目被插入到开头;如果追加为真,则将其插入到结尾。这会检查参数的类型,编译消息模块正则表达式,并将它们作为元组插入到警告筛选列表中。如果两个条目都匹配特定警告,则列表中靠前的条目将覆盖列表中靠后的条目。省略的参数默认为与所有内容匹配的值。

警告。简单筛选(操作, 类别=警告, 行号=0, 追加=)

警告过滤器规范 列表中插入一个简单条目。函数参数的含义与 filterwarnings() 相同,但不需要正则表达式,因为插入的过滤器只要类别和行号匹配,就始终匹配任何模块中的任何消息。

warnings.resetwarnings()

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

可用的上下文管理器

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

一个上下文管理器,它会复制警告过滤器和 showwarning() 函数,并在退出时还原它们。如果 record 参数为 False(默认值),则上下文管理器在进入时返回 None。如果 recordTrue,则返回一个列表,该列表会随着自定义 showwarning() 函数(它还会禁止输出到 sys.stdout)所看到的对象而逐步填充。列表中的每个对象都具有与 showwarning() 参数同名的属性。

module 参数采用一个模块,该模块将替代导入 warnings 时返回的模块,其过滤器将受到保护。此参数主要用于测试 warnings 模块本身。

如果 action 参数不是 None,则剩余的参数将作为 simplefilter() 的参数传递,就好像在进入上下文时立即调用它一样。

注意

catch_warnings 管理器通过替换并随后还原模块的 showwarning() 函数和内部过滤器规范列表来工作。这意味着上下文管理器正在修改全局状态,因此不是线程安全的。

3.11 版本中的更改: 添加了 actioncategorylinenoappend 参数。