warnings — 警告控制 — Python 文档
warnings — 警告控制
源代码: :source:`Lib/warnings.py`
警告消息通常在以下情况下发出:警告用户程序中的某些情况很有用,该情况(通常)不保证引发异常和终止程序。 例如,当程序使用过时的模块时,人们可能想要发出警告。
Python 程序员通过调用此模块中定义的 warn() 函数来发出警告。 (C 程序员使用 PyErr_WarnEx();详见 异常处理)。
警告信息通常写入sys.stderr,但它们的处理方式可以灵活改变,从忽略所有警告到将它们变成异常。 警告的处置可能因 警告类别 、警告消息的文本以及发出警告的源位置而异。 同一源位置的特定警告的重复通常会被抑制。
警告控制有两个阶段:第一,每次发出警告时,确定是否应该发出消息; 接下来,如果要发布消息,则使用用户可设置的挂钩对其进行格式化和打印。
是否发出警告消息的决定由警告过滤器控制,这是一个匹配规则和动作的序列。 可以通过调用 filterwarnings() 将规则添加到过滤器,并通过调用 resetwarnings() 重置为其默认状态。
警告信息的打印是通过调用 showwarning() 来完成的,它可能会被覆盖; 此函数的默认实现通过调用 formatwarning() 来格式化消息,自定义实现也可以使用它。
警告类别
有许多表示警告类别的内置异常。 这种分类对于过滤警告组很有用。
虽然这些在技术上是 内置异常 ,但在此处进行了记录,因为从概念上讲,它们属于警告机制。
用户代码可以通过对标准警告类别之一进行子类化来定义其他警告类别。 警告类别必须始终是 Warning 类的子类。
当前定义了以下警告类别类:
班级 | 描述 |
---|---|
Warning
|
这是所有警告类别类的基类。 它是 Exception 的子类。 |
UserWarning
|
warn() 的默认类别。 |
DeprecationWarning
|
当这些警告是针对其他 Python 开发人员时,有关已弃用功能的警告的基本类别(默认情况下忽略,除非由 __main__ 中的代码触发)。
|
SyntaxWarning
|
有关可疑句法特征的警告的基本类别。 |
RuntimeWarning
|
有关可疑运行时功能的警告的基本类别。 |
FutureWarning
|
当这些警告面向用 Python 编写的应用程序的最终用户时,有关已弃用功能的警告的基本类别。 |
PendingDeprecationWarning
|
有关将来不推荐使用的功能的警告的基本类别(默认情况下会忽略)。 |
ImportWarning
|
在导入模块的过程中触发的警告的基本类别(默认忽略)。 |
UnicodeWarning
|
与 Unicode 相关的警告的基本类别。 |
BytesWarning
|
与 bytes 和 bytearray 相关警告的基本类别。 |
ResourceWarning
|
与资源使用相关的警告的基本类别。 |
3.7 版更改: 以前的 DeprecationWarning 和 FutureWarning 是根据功能是否被完全删除或改变其行为来区分的。 它们现在根据目标受众以及默认警告过滤器处理它们的方式进行区分。
警告过滤器
警告过滤器控制警告是被忽略、显示还是变成错误(引发异常)。
从概念上讲,警告过滤器维护过滤器规范的有序列表; 任何特定警告依次与列表中的每个过滤器规范匹配,直到找到匹配项; 过滤器确定匹配的处置。 每个条目都是一个形式为 (action, message, category, module, lineno)的元组,在哪里:
action 是以下字符串之一:
价值
处置
"default"
为发出警告的每个位置(模块 + 行号)打印第一次出现的匹配警告
"error"
将匹配的警告变成异常
"ignore"
从不打印匹配警告
"always"
总是打印匹配警告
"module"
为发出警告的每个模块打印第一次出现的匹配警告(无论行号如何)
"once"
无论位置如何,只打印第一次出现的匹配警告
message 是一个包含正则表达式的字符串,警告消息的开头必须匹配该正则表达式。 表达式被编译为始终不区分大小写。
category 是一个类(Warning 的子类),警告类别必须是其子类才能匹配。
module 是一个包含模块名称必须匹配的正则表达式的字符串。 表达式被编译为区分大小写。
lineno 是一个整数,警告发生的行号必须匹配,或者
0
匹配所有行号。
由于 Warning 类派生自内置的 Exception 类,为了将警告转化为错误,我们只需引发 category(message)
。
如果报告了警告并且不匹配任何注册的过滤器,则应用“默认”操作(因此得名)。
描述警告过滤器
警告过滤器由传递给 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"
# and any subpackages of "mymodule"
默认警告过滤器
默认情况下,Python 安装了几个警告过滤器,它们可以被 -W 命令行选项、PYTHONWARNINGS 环境变量覆盖并调用 过滤器警告()。
在常规发布版本中,默认警告过滤器具有以下条目(按优先顺序):
default::DeprecationWarning:__main__
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
ignore::ImportWarning
ignore::ResourceWarning
在调试版本中,默认警告过滤器列表为空。
在 3.2 版更改:除了 PendingDeprecationWarning 之外,现在默认忽略 DeprecationWarning。
在 3.7 版更改: DeprecationWarning 在由 __main__
中的代码直接触发时再次默认显示。
3.7 版更改:BytesWarning 不再出现在默认过滤器列表中,而是通过 sys.warnoptions 配置,当 -b 为指定了两次。
覆盖默认过滤器
用 Python 编写的应用程序的开发人员可能希望默认对用户隐藏 all Python 级别的警告,并且仅在运行测试或以其他方式处理应用程序时显示它们。 用于将过滤器配置传递给解释器的 sys.warnoptions 属性可用作指示是否应禁用警告的标记:
import sys
if not sys.warnoptions:
import warnings
warnings.simplefilter("ignore")
建议 Python 代码的测试运行器的开发人员确保默认情况下为被测代码显示 all 警告,使用如下代码:
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
的简写)或在环境。 这将启用对所有警告的默认处理,包括那些默认忽略的警告。 要更改对遇到的警告采取的操作,您可以更改传递给 -W 的参数(例如 -W error
)。 有关可能的更多详细信息,请参阅 -W 标志。
可用功能
- warnings.warn(message, category=None, stacklevel=1, source=None)
发出警告,或者忽略它或引发异常。 category 参数,如果给定,必须是一个 warning category class; 它默认为 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 版更改: 添加 源 参数。
- 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(如果提供)应该是发出警告的代码正在使用的全局命名空间。 (此参数用于支持显示在 zipfiles 或其他非文件系统导入源中找到的模块的源)。
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 是要包含在警告信息中的一行源代码; 如果未提供 line,showwarning() 将尝试读取由 filename 和 lineno 指定的行。
- warnings.formatwarning(message, category, filename, lineno, line=None)
- 以标准方式格式化警告。 这将返回一个字符串,其中可能包含嵌入的换行符并以换行符结尾。 line 是要包含在警告信息中的一行源代码; 如果未提供 line,formatwarning() 将尝试读取由 filename 和 lineno 指定的行。
- warnings.filterwarnings(action, message=, category=Warning, module=, lineno=0, append=False)
- 在 警告过滤器规范 列表中插入一个条目。 条目默认插入在前面; 如果 append 为真,则插入到末尾。 这会检查参数的类型,编译 message 和 module 正则表达式,并将它们作为元组插入警告过滤器列表中。 如果两个条目都匹配特定警告,则靠近列表前面的条目会覆盖列表中后面的条目。 省略的参数默认为匹配所有内容的值。
- warnings.simplefilter(action, category=Warning, lineno=0, append=False)
- 在 警告过滤器规范 列表中插入一个简单的条目。 函数参数的含义与filterwarnings()相同,但不需要正则表达式,因为只要类别和行号匹配,插入的过滤器总是匹配任何模块中的任何消息。
- warnings.resetwarnings()
- 重置警告过滤器。 这会丢弃所有先前调用 filterwarnings() 的影响,包括 -W 命令行选项和调用 simplefilter() 的影响。
可用的上下文管理器
- class warnings.catch_warnings(*, record=False, module=None)
复制并在退出时恢复警告过滤器和 showwarning() 函数的上下文管理器。 如果 record 参数为 False(默认值),则上下文管理器在输入时返回 None。 如果 record 是 True,则返回一个列表,该列表逐渐填充有自定义 showwarning() 函数(也抑制输出到
sys.stdout
)。 列表中的每个对象都具有与 showwarning() 的参数同名的属性。module 参数采用一个将被使用的模块,而不是在您导入其过滤器将受到保护的 warnings 时返回的模块。 这个论点主要用于测试 warnings 模块本身。
笔记
catch_warnings 管理器通过替换然后恢复模块的 showwarning() 函数和内部过滤器规范列表来工作。 这意味着上下文管理器正在修改全局状态,因此不是线程安全的。