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 的区别在于功能是完全删除还是更改其行为。现在,它们根据其目标受众以及默认警告过滤器处理它们的方式进行区分。

警告过滤器

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

从概念上讲,警告过滤器维护一个已排序的过滤器规范列表;任何特定警告都会依次与列表中的每个过滤器规范进行匹配,直到找到匹配项;过滤器决定匹配项的处理方式。每个条目都是一个元组,形式为 (action, message, category, module, lineno),其中

  • action 是以下字符串之一

    处理方式

    "default"

    打印每个发出警告的位置(模块 + 行号)的第一次匹配警告

    "error"

    将匹配的警告转换为异常

    "ignore"

    从不打印匹配的警告

    "always"

    始终打印匹配的警告

    "module"

    打印每个发出警告的模块的第一次匹配警告(不考虑行号)

    "once"

    只打印第一次匹配的警告,不考虑位置

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

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

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

  • lineno 是一个整数,表示发生警告的行号必须与之匹配,或者为 0 以匹配所有行号。

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

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

重复警告抑制标准

抑制重复警告的过滤器应用以下标准来确定警告是否被视为重复

  • "default":只有当(messagecategorymodulelineno)都相同时,才将警告视为重复。

  • "module":如果(messagecategorymodule)相同,则将警告视为重复,忽略行号。

  • "once":如果(messagecategory)相同,则将警告视为重复,忽略模块和行号。

描述警告过滤器

警告过滤器由传递给 Python 解释器命令行的 -W 选项和 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 版本中更改:除了 PendingDeprecationWarning 之外,现在默认情况下会忽略 DeprecationWarning

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

在 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=())

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

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

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

skip_file_prefixes 关键字参数可用于指示在计算堆栈级别时忽略哪些堆栈帧。当您希望警告始终出现在包外部的调用站点时,这很有用,而常量 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 必须是字符串,category 必须是 Warning 的子类,或者 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 指定的行。

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

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

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

警告过滤器规范列表中插入一个条目。默认情况下,该条目被插入到列表的前面;如果append为真,则插入到列表的末尾。此函数检查参数的类型,编译 messagemodule 正则表达式,并将它们作为元组插入到警告过滤器列表中。如果两者都匹配特定的警告,则列表中靠前的条目会覆盖列表中靠后的条目。省略的参数默认为匹配所有内容的值。

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

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

warnings.resetwarnings()

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

@warnings.deprecated(msg, *, category=DeprecationWarning, stacklevel=1)

用于指示类、函数或重载已弃用的装饰器。

当此装饰器应用于对象时,在运行时使用该对象时可能会发出弃用警告。静态类型检查器也会在使用已弃用对象时生成诊断信息。

用法

from warnings import deprecated
from typing import overload

@deprecated("Use B instead")
class A:
    pass

@deprecated("Use g instead")
def f():
    pass

@overload
@deprecated("int support is deprecated")
def g(x: int) -> int: ...
@overload
def g(x: str) -> int: ...

category 指定的警告将在运行时使用已弃用对象时发出。对于函数,这发生在调用时;对于类,发生在实例化和创建子类时。如果 categoryNone,则在运行时不发出警告。stacklevel 确定警告的发出位置。如果为 1(默认值),则警告在已弃用对象的直接调用者处发出;如果更高,则在堆栈中更高层级发出。静态类型检查器的行为不受 categorystacklevel 参数的影响。

传递给装饰器的弃用消息保存在已装饰对象的 __deprecated__ 属性中。如果应用于重载,则装饰器必须在 @overload 装饰器之后,以便该属性存在于 typing.get_overloads() 返回的重载中。

3.13 版本新增: 请参阅 PEP 702

可用的上下文管理器

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(),就像在进入上下文时立即调用它一样。

有关 categorylineno 参数的含义,请参阅警告过滤器

注意

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

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