获取警告的回溯信息

104
在numpy中,我们可以使用np.seterr(invalid='raise')来获取警告引发错误时的回溯信息(请参见此帖子)。
  • 有没有通用的方法来追踪警告?
  • 我能否让Python在引发警告时输出回溯信息?

追踪模块?http://docs.python.org/2/library/traceback.html - Jayanth Koushik
我猜没有一个答案完全满足您的需求;您能否再详细解释一下您需要什么呢? :) - mgab
@mgab 问题在于,有时很难找到警告的位置信息,因为它并没有提供调用信息。如果将所有警告都变成致命错误,程序将会在第一个被调用的警告后退出。我想知道是否有一种方法可以告诉 Python,在引发警告时打印回溯信息,或者其他任何确定警告来源的方式。 - embert
如果将警告作为错误引发,它确实会在第一个警告处停止,但您应该获得回溯报告原始错误(或警告)引发的完整堆栈。然而,我同意这并不是最佳选择,特别是如果您的脚本需要一段时间... 我更新了我的答案,以更好地回答您的问题。 - mgab
5个回答

141

通过将代码赋值给warnings.showwarning,您可以获得所需内容。 警告模块文档本身就建议您这样做,所以您并不是被源代码的黑暗面所诱惑。 :)

  

您可以通过将代码赋值给warnings.showwarning替换此函数为另一种实现。

您可以定义一个新函数,该函数执行warning.showwarning通常所执行的操作,并添加了打印栈的功能。然后将其替换为原始函数:

import traceback
import warnings
import sys

def warn_with_traceback(message, category, filename, lineno, file=None, line=None):

    log = file if hasattr(file,'write') else sys.stderr
    traceback.print_stack(file=log)
    log.write(warnings.formatwarning(message, category, filename, lineno, line))

warnings.showwarning = warn_with_traceback
在此之后,每个警告都将打印堆栈跟踪和警告消息。但是请注意,如果警告被忽略,因为它不是第一个,那么什么也不会发生,所以你仍然需要执行:

After this, every warning will print the stack trace as well as the warning message. Take into account, however, that if the warning is ignored because it is not the first one, nothing will happen, so you still need to execute:

warnings.simplefilter("always")

您可以通过warning模块的过滤器获得与numpy.seterr相似的控制。

如果您想让Python每次触发警告时都报告,而不仅仅是第一次,可以添加类似以下代码的内容:

import warnings
warnings.simplefilter("always")

通过传入不同的字符串作为参数,您可以获得其他行为。使用相同的函数,您还可以针对引发警告的模块、它们提供的消息、警告类、引起警告的代码行等指定不同的警告行为。

您可以在模块文档中查看列表。

例如,您可以将所有警告设置为引发异常,但应完全忽略DeprecationWarnings

import warnings
warnings.simplefilter("error")
warnings.simplefilter("ignore", DeprecationWarning)

这种方式可以为每个警告抛出的错误获取完整的回溯(仅第一个,因为执行将停止...但您可以逐个解决它们,并创建过滤器以忽略您不想再次听到的警告..。


5
这是一个非常有用的答案。@embert应该在这么多年之后将其标记为答案。 - beldaz
非常有用的答案,但愿我早点找到它,这样我就不必费力去寻找“二进制模式下不支持行缓冲(buffering=1)”的原因了。 - MadHatter

49

像这样运行您的程序

python -W error myprogram.py

这将使所有警告变为致命错误,请参见此处获取更多信息。


18
可以的。然而,有没有一种方法可以强制Python在每次发出警告时打印回溯信息?问题是,使用该选项时,当导入期间引发弃用警告时,程序将已经失败。 - embert
如果您需要进行单元测试(或模块测试),可以执行以下操作:python -W error -m pytest - Omry Yadan
2
顺便提一下,pytest理解-W标志,并且可以使用以下命令运行:pytest ... -W error(文档:https://docs.pytest.org/en/stable/warnings.html)。根据详细程度,会显示类似于任何其他测试失败的回溯。 - KyleKing
如果您通过Python的命令行界面运行程序,您也可以通过环境变量设置此标志:https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS - Ben Farmer

6
你可以使用 warnings.filterwarnings() 将选定的警告转换为异常并获取回溯。最小工作示例如下所示:
import warnings
warnings.filterwarnings(
    action='error', message='',
    category=RuntimeWarning
)

在我的情况下:
import warnings
warnings.filterwarnings(
    'error', 'DateTimeField .* received a naive datetime',
    RuntimeWarning, 'django.db.models.fields'
)

2
对于Python3,请参见警告模块文档中的stacklevel参数。当第三方警告被深埋在调用堆栈中时,这可能特别有帮助:将警告设置为stacklevel=2,查看回溯信息,在必要时进行更改,恢复/删除stacklevel到原始状态。

例如:

warnings.warn("It's dangerous to go alone! Take this.", stacklevel=2)

1

记录警告的选项,最小化对程序流程的干扰。
获取回溯信息而不触发错误:

import warnings
import traceback

warnings.filterwarnings("error")  # Treat warnings as errors
try:
    your_code()
except Warning:
    print(traceback.format_exc())  # print traceback
warnings.resetwarnings()  # Back to default behavior

如果您想以相同(或类似)的方式执行代码,可以在 except 块中执行它,这时忽略警告,因为您已经得到了其回溯:
import warnings
import traceback

warnings.filterwarnings("error")  # Treat warnings as errors
try:
    your_code()
except Warning:
    print(traceback.format_exc())  # print traceback
    warnings.filterwarnings("ignore")  # ignore warnings
    your_code()  # Execute either way (same code or alternative code)
warnings.resetwarnings()  # Back to default behavior

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