如何在警告时自动切换到调试模式?

How to automatically switch to debug mode on a warning?

我想调试在代码执行过程中出现的警告。

一个简单的断点是行不通的,因为导致警告的行在第一次警告出现之前执行了数百万次而没有警告。

此外,发生这种情况的行在库代码中(更准确地说,在 pandas/core/common.py 中),所以我倾向于根本不修改代码。

我只想在程序发出警告时立即停止程序的执行,并在此时检查堆栈,使用 pdbipdb

Is there a way to configure either debugger to automatically enter single-step mode upon the issuing of a warning?

https://pypi.python.org/pypi/rpdb/

我发现 rpdb 当您无法控制启动程序时,调试此类问题非常方便。您需要的是临时修改 pandas/core/common.py 以添加

import rpdb
debugger = rpdb.Rpdb(port=12345)
debugger.set_trace()

当触发警告时,调试器将在那里等待连接。然后连接到调试器并检查堆栈。

你可以写一个脚本dbg.py:

import pdb, warnings, sys
import __builtin__

if __name__ == '__main__':
    args, n = [], len(sys.argv)
    if n < 2:
        sys.exit(1)
    elif n > 2:
        args.append(__builtin__.__dict__[sys.argv[2]])
        if n > 3:
            args.append(int(sys.argv[3]))
    warnings.simplefilter('error', *args)  # treat warnings as exceptions
    try:
        execfile(sys.argv[1])
    except:
        pdb.post_mortem(sys.exc_info()[-1])

然后您可以使用它来调试您的脚本。如果你想在任何警告时 运行 pdb,将你的脚本名称作为第一个参数传递:

$ python dbg.py yourscript.py

如果您只希望 pdb 在出现某些特定类型的警告时 运行,则将警告类型作为第二个参数传递:

$ python dbg.py yourscript.py DeprecationWarning

行号作为第三个参数:

$ python dbg.py yourscript.py DeprecationWarning 342

您也可以使用 warnings.filterwarnings 而不是 warnings.simplefilter 重写代码,使警告过滤更加灵活。

如果可以识别引发警告的代码范围,则可以使用 warnings.catch_warnings link 访问警告列表并将执行切换到 PDB 会话,而不是将警告视为错误。

但是我建议您使用 PDB 启动您的程序,设置一个断点以在检测到有问题的代码段之后警告列表的警告编号更改时中断执行。如果您的调试代码片段处于循环中,您将受益匪浅。

示例:

import warnings

with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter('always')
    warningNum = len(w)
    for i in range(someNumber):
        "your code probably throw warning"

        if len(w) != warningNum:
            warningNum = len(w) #set break point here

运行脚本加上python -m pdb yourscript.py,在warningNum = len(w)行设置断点,当检测到警告号变化时可以暂停执行。

我发现@user2683246 提供的答案优雅而有用。这是为与 Python3 兼容而修改的解决方案的变体(使用 Python 3.7 测试):

#!/usr/bin/env python

import pdb, warnings, sys
import builtins

if __name__ == '__main__':
    args, n = [], len(sys.argv)
    if n < 2:
        sys.exit(1)
    elif n > 2:
        args.append(builtins.__dict__[sys.argv[2]])
        if n > 3:
            args.append(int(sys.argv[3]))
    warnings.simplefilter('error', *args)  # treat warnings as exceptions
    try:
        with open(sys.argv[1]) as f:
            code = compile(f.read(), sys.argv[1], 'exec')
            exec(code)
    except:
        pdb.post_mortem(sys.exc_info()[-1])

显着变化:

  • execfile() 调用替换为 Python 3 变体;和
  • __builtin__替换为builtins