warnings — 警告控制

源代码: Lib/warnings.py


警告消息通常在程序中需要提醒用户某种情况时发出,这种情况下(通常)不需要引发异常并终止程序。例如,当程序使用已过时的模块时,可能会发出警告。

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

警告消息通常写入 sys.stderr,但其处理方式可以灵活更改,从忽略所有警告到将其转换为异常。警告的处理方式可以根据 警告类别、警告消息的文本以及发出警告的源位置而异。同一源位置的特定警告重复通常会被抑制。

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

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

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

参见

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

警告类别

有许多内置异常代表警告类别。这种分类有助于筛选出特定组的警告。

虽然这些在技术上是 内置异常,但此处对其进行文档说明,因为它们在概念上属于警告机制。

用户代码可以通过继承标准警告类别之一来定义额外的警告类别。警告类别必须始终是 Warning 类的子类。

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

描述

警告

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

UserWarning

warn() 的默认类别。

DeprecationWarning

用于警告已弃用功能的基类别,当这些警告旨在供其他 Python 开发者使用时(默认情况下忽略,除非由 __main__ 中的代码触发)。

SyntaxWarning

用于警告可疑语法特征的基类别(通常在编译 Python 源代码时发出,因此可能无法通过运行时过滤器抑制)

RuntimeWarning

用于警告可疑运行时特征的基类别。

FutureWarning

用于警告已弃用功能的基类别,当这些警告旨在供用 Python 编写的应用程序的最终用户使用时。

PendingDeprecationWarning

用于警告将来会弃用功能的基类别(默认情况下忽略)。

ImportWarning

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

UnicodeWarning

用于与 Unicode 相关的警告的基类别。

BytesWarning

用于与 bytesbytearray 相关的警告的基类别。

ResourceWarning

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

版本 3.7 中的变更: 以前 DeprecationWarningFutureWarning 是根据功能是完全移除还是改变其行为来区分的。现在它们是根据其目标受众和默认警告过滤器处理它们的方式来区分的。

警告过滤器

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

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

  • action 是以下字符串之一

    处理方式

    "default"

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

    "error"

    将匹配的警告转换为异常

    "ignore"

    从不打印匹配的警告

    "always"

    始终打印匹配的警告

    "all"

    “always” 的别名

    "module"

    为发出警告的每个模块打印匹配警告的第一次出现(无论行号如何)

    "once"

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

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

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

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

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

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

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

重复警告抑制标准

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

  • "default": 只有当 (message, category, module, lineno) 全部相同时,警告才被视为重复。

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

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

描述警告过滤器

警告过滤器由传递给 Python 解释器命令行和 PYTHONWARNINGS 环境变量的 -W 选项初始化。解释器将所有提供的条目的参数保存在 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 中的变更: __main__ 中的代码直接触发 DeprecationWarning 时,默认情况下再次显示。

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

覆盖默认过滤器

用 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 规则而引发,那么无论设置了什么过滤器,除非与警告相关的警告注册表已被清除,否则警告将不会再次出现。

一旦上下文管理器退出,警告过滤器将恢复到进入上下文时的状态。这可以防止测试在测试之间以意想不到的方式更改警告过滤器,并导致不确定的测试结果。

备注

有关在多线程或异步函数程序中使用 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)。如果发出的特定警告被 警告过滤器 更改为错误,则此函数会引发异常。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()example.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(如果提供)应该是发出警告的代码正在使用的全局命名空间。(此参数用于支持显示 zipfile 或其他非文件系统导入源中找到的模块的源代码)。

source(如果提供)是发出 ResourceWarning 的销毁对象。

版本 3.6 中的变更: 添加了 source 参数。

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

将警告写入文件。默认实现调用 formatwarning(message, category, filename, lineno, line) 并将结果字符串写入 filefile 默认为 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 为 true,则插入到末尾。此函数检查参数类型,编译 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__ 属性中。如果应用于重载,装饰器必须在 @~typing.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 上下文管理器时的并发安全性,请参阅 上下文管理器的并发安全性

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

上下文管理器的并发安全性

catch_warnings 上下文管理器的行为取决于 sys.flags.context_aware_warnings 标志。如果该标志为 true,则上下文管理器以并发安全的方式运行,否则不安全。并发安全意味着它既是线程安全的,又可以在 asyncio 协程 和任务中使用。线程安全意味着在多线程程序中行为是可预测的。对于自由线程构建,该标志默认为 true,否则为 false。

如果 context_aware_warnings 标志为 false,则 catch_warnings 将修改 warnings 模块的全局属性。如果在并发程序(使用多个线程或使用 asyncio 协程)中使用,这是不安全的。例如,如果两个或更多线程同时使用 catch_warnings 类,则行为是未定义的。

如果该标志为 true,catch_warnings 不会修改全局属性,而是使用 ContextVar 来存储新建立的警告过滤状态。上下文变量提供线程局部存储,并使 catch_warnings 线程安全。

上下文处理器的 record 参数的行为也因标志的值而异。当 record 为 true 且标志为 false 时,上下文管理器通过替换并稍后恢复模块的 showwarning() 函数来工作。这不是并发安全的。

record 为 true 且标志为 true 时,showwarning() 函数不会被替换。相反,记录状态由上下文变量中的内部属性指示。在这种情况下,showwarning() 函数在退出上下文处理器时不会被恢复。

context_aware_warnings 标志可以通过 -X context_aware_warnings 命令行选项或通过 PYTHON_CONTEXT_AWARE_WARNINGS 环境变量进行设置。

备注

大多数希望警告模块具有线程安全行为的程序可能也希望将 thread_inherit_context 标志设置为 true。该标志导致由 threading.Thread 创建的线程以从中启动它的线程的上下文变量副本开始。当为 true 时,由 catch_warnings 在一个线程中建立的上下文也将适用于它启动的新线程。如果为 false,新线程将以空的警告上下文变量开始,这意味着由 catch_warnings 上下文管理器建立的任何过滤都将不再活动。

版本 3.14 中的变更: 添加了 sys.flags.context_aware_warnings 标志,并在该标志为 true 时,为 catch_warnings 使用上下文变量。Python 的早期版本表现得好像该标志始终设置为 false。