在Python中,如何将警告信息捕获并处理成异常?

217

我在我的Python代码中使用的第三方库(用C语言编写)正在发出警告。我希望能够使用tryexcept语法来正确处理这些警告。有没有办法做到这一点?


2
这些警告只是写到 stderr 的文本消息吗? - Fenikso
1
Fenikso:我不确定,看起来像是真正的警告。 - Boris Gorelik
1
你如何识别“真正的警告”?我认为在C语言中,编译期间会出现真正的警告。 - Fenikso
warnings.filterwarnings 正好符合您的需求,我不明白您对它有什么问题? - Rosh Oxymoron
4
@Fenikso,@Rosh Oxymoron,你们是正确的。我犯了错误。warnings.filterwarnings('error')可以解决问题。我找不到最初提出此解决方案的答案。 - Boris Gorelik
8个回答

264

要将警告视为错误,只需使用以下代码:

-Werror
import warnings
warnings.filterwarnings("error")

完成后,您将能够像处理错误一样捕获警告,例如:

try:
    some_heavy_calculations()
except RuntimeWarning:
    breakpoint()

您也可以通过运行以下命令来重置警告的行为:

warnings.resetwarnings()

补充说明:添加此答案是因为评论中最佳答案存在拼写错误:filterwarnigns 而非 filterwarnings


9
如果你只想查看堆栈跟踪,前两行就足够了。 - z0r
7
这很完美。我只是想让我的脚本在发出警告后立即停止执行,以便我可以打印相关的调试信息并修复问题。 - Praveen
2
在Python 3中,您无需调用filterwarnings即可捕获警告,它会自动生效。 - naught101
1
被采纳的答案未能回答提问者的问题,而这个答案则能够解决问题。当我搜索到这个问题时,这就是我在寻找的答案。 - Biggsy
2
使用 warnings.resetwarnings() 来重置警告信息。 - Nick Koprowicz
显示剩余2条评论

75

引用自Python手册 (27.6.4. 测试警告):

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)

11
这里是一个回答,告诉你如何使用tryexcept语法。 - Unapiedra
2
这种方法比niekas的回答更有优势,因为如果“fnx”返回了某些内容,你可以保留该结果(并且仍然可以管理警告)。 - Pietro Battiston
这并没有回答 OP 的问题,OP 的问题是关于如何处理警告而不是测试它们。然而,下面 niekas 的回答展示了如何处理警告。 - Biggsy
请注意,如果您的函数只间歇性地返回警告,则上述函数将无法正常工作。因为如果 fxn() 没有返回警告,则 w 将为空列表。如果 w 是空列表(即 []),则运行代码将导致以下错误: IndexError: list index out of range。如果您只是想格式化或检查捕获的警告的属性,则最好使用 for 循环:for x in w: print(f'{x.category.__name__}: {str(x.message)}') - Steven M. Mortimer
1
如果想在不中断程序执行的情况下处理警告,这种方法非常有用。 - normanius

43

如果你只想让你的脚本在警告时失败,你可以使用-W参数来调用python

python -W error foobar.py

9
为了捕获特定类型的警告信息,例如:python -W error::RuntimeWarning foobar.py - Paul Price

24

这是一种变体,使得仅使用自定义警告更加清晰易懂。

import warnings
with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")

    # Call some code that triggers a custom warning.
    functionThatRaisesWarning()

    # ignore any non-custom warnings that may be in the list
    w = filter(lambda i: issubclass(i.category, UserWarning), w)

    if len(w):
        # do something with the first warning
        email_admins(w[0].message)

13

niekas的回答基础上进行拓展,但使用catch_warnings上下文管理器,在上下文退出后将警告行为重置为默认值:

import warnings

with warnings.catch_warnings():
     warnings.simplefilter("error")
     # Code in this block will raise exception for a warning
# Code in this block will have default warning behaviour

7

捕获所有警告可能存在问题。您可以捕获特定的警告。例如,我需要捕获一个Pillow警告:

import warnings
warnings.filterwarnings("error", category=Image.DecompressionBombWarning)

def process_images():
  try:
    some_process()

  except Image.DecompressionBombWarning as e:
    print(e)

6
在一些情况下,您需要使用 ctypes 将警告转换为错误。例如:
str(b'test')  # no error
import warnings
warnings.simplefilter('error', BytesWarning)
str(b'test')  # still no error
import ctypes
ctypes.c_int.in_dll(ctypes.pythonapi, 'Py_BytesWarningFlag').value = 2
str(b'test')  # this raises an error

这个答案的建设性在于展示了如何仅针对特定的警告类型报错。对于几乎任何大型软件项目,如果你执行 warnings.simplefilter('error'),你将无法获得日志中看到的警告的回溯信息,而是会得到之前过滤的警告的回溯信息。如果你有一些 CLI 调用,使用 simplefilter 也是最快的方法来得到你的答案。 - AlanSE

1

为了完整起见,您还可以导出环境变量:

PYTHONWARNINGS=error /usr/bin/run_my_python_utility

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接