如何限制 python 回溯到特定文件

How to limit python traceback to specific files

我编写了很多使用外部库的 Python 代码。我经常会写一个错误,当我 运行 代码时,我会在 Python 控制台中得到一个很长的回溯。 99.999999% 的时间是由于我的代码中的编码错误,而不是因为包中的错误。但是回溯一直到包代码中的错误行,要么需要大量滚动回溯才能找到我写的代码,要么回溯深入到包中以至于我自己的代码没有' t甚至出现在回溯中。

有没有办法 "black-box" 包代码,或者以某种方式只显示我的代码中的回溯行?我希望能够向系统指定我希望从中查看回溯的目录或文件。

traceback.extract_tb(tb) 会 return 错误帧的元组格式 (file, line_no, type, error_statement) ,你可以玩格式化追溯。另请参阅 https://pymotw.com/2/sys/exceptions.html

import sys
import traceback

def handle_exception(ex_type, ex_info, tb):
    print ex_type, ex_info, traceback.extract_tb(tb)

sys.excepthook = handle_exception

为了打印您自己的堆栈跟踪,您需要自己处理所有未处理的异常;这就是 sys.excepthook 变得得心应手的方式。

这个函数的签名是sys.excepthook(type, value, traceback),它的工作是:

This function prints out a given traceback and exception to sys.stderr.

因此,只要您可以使用回溯并仅提取您关心的部分就可以了。测试框架经常这样做;他们有自定义 assert 函数,通常不会出现在回溯中,换句话说,他们会跳过属于测试框架的帧。此外,在这些情况下,测试通常也由测试框架启动。

您最终得到如下所示的回溯:

[ custom assert code ] + ... [ code under test ] ... + [ test runner code ]

如何识别您的代码。

您可以在您的代码中添加一个全局变量:

__mycode = True

然后识别框架:

def is_mycode(tb):
  globals = tb.tb_frame.f_globals
  return globals.has_key('__mycode')

如何提取帧。

  1. 跳过与您无关的帧(例如自定义断言代码)
  2. 确定代码中有多少帧 -> length
  3. 提取 length

    def mycode_traceback_levels(tb):
      length = 0
      while tb and is_mycode(tb):
        tb = tb.tb_next
        length += 1
      return length
    

示例处理程序。

def handle_exception(type, value, tb):
  # 1. skip custom assert code, e.g.
  # while tb and is_custom_assert_code(tb):
  #   tb = tb.tb_next
  # 2. only display your code
  length = mycode_traceback_levels(tb)
  print ''.join(traceback.format_exception(type, value, tb, length))

安装处理程序:

sys.excepthook = handle_exception

下一步是什么?

如果您仍想了解有关您自己代码之外的故障位置的信息,您可以调整 length 以添加一个或多个级别。

另见 https://gist.github.com/dnozay/b599a96dc2d8c69b84c6

正如其他人所建议的,您可以使用 sys.excepthook:

This function prints out a given traceback and exception to sys.stderr.

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.

(强调我的)

可以根据指定的目录过滤extract_tb(或traceback模块中的类似功能)提取的回溯。

两个可以提供帮助的函数:

from os.path import join, abspath
from traceback import extract_tb, format_list, format_exception_only

def spotlight(*show):
    ''' Return a function to be set as new sys.excepthook.
        It will SHOW traceback entries for files from these directories. '''
    show = tuple(join(abspath(p), '') for p in show)

    def _check_file(name):
        return name and name.startswith(show)

    def _print(type, value, tb):
        show = (fs for fs in extract_tb(tb) if _check_file(fs.filename))
        fmt = format_list(show) + format_exception_only(type, value)
        print(''.join(fmt), end='', file=sys.stderr)

    return _print

def shadow(*hide):
    ''' Return a function to be set as new sys.excepthook.
        It will HIDE traceback entries for files from these directories. '''
    hide = tuple(join(abspath(p), '') for p in hide)

    def _check_file(name):
        return name and not name.startswith(hide)

    def _print(type, value, tb):
        show = (fs for fs in extract_tb(tb) if _check_file(fs.filename))
        fmt = format_list(show) + format_exception_only(type, value)
        print(''.join(fmt), end='', file=sys.stderr)

    return _print

他们都使用 traceback.extract_tb. It returns "a list of “pre-processed” stack trace entries extracted from the traceback object"; all of them are instances of traceback.FrameSummary(命名元组)。每个 traceback.FrameSummary 对象都有一个 filename 字段,用于存储相应文件的绝对路径。我们检查它是否以作为单独函数参数提供的任何目录路径开头,以确定我们是否需要排除条目(或保留它)。


这是一个例子:

标准库中的 enum 模块不允许重复使用密钥,

import enum
enum.Enum('Faulty', 'a a', module=__name__)

产量

Traceback (most recent call last):
  File "/home/vaultah/so/shadows/main.py", line 23, in <module>
    enum.Enum('Faulty', 'a a', module=__name__)
  File "/home/vaultah/cpython/Lib/enum.py", line 243, in __call__
    return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start)
  File "/home/vaultah/cpython/Lib/enum.py", line 342, in _create_
    classdict[member_name] = member_value
  File "/home/vaultah/cpython/Lib/enum.py", line 72, in __setitem__
    raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'a'

我们可以将堆栈跟踪条目限制在我们的代码中(在 /home/vaultah/so/shadows/main.py 中)。

import sys, enum
sys.excepthook = spotlight('/home/vaultah/so/shadows')
enum.Enum('Faulty', 'a a', module=__name__)

import sys, enum
sys.excepthook = shadow('/home/vaultah/cpython/Lib')
enum.Enum('Faulty', 'a a', module=__name__)

给出相同的结果:

  File "/home/vaultah/so/shadows/main.py", line 22, in <module>
    enum.Enum('Faulty', 'a a', module=__name__)
TypeError: Attempted to reuse key: 'a'

有一种方法可以排除所有站点目录(安装第 3 方包的位置 - 请参阅 site.getsitepackages

import sys, site, jinja2
sys.excepthook = shadow(*site.getsitepackages())
jinja2.Template('{%}')
# jinja2.exceptions.TemplateSyntaxError: unexpected '}'
#     Generates ~30 lines, but will only display 4

Note: Don't forget to restore sys.excepthook from sys.__excepthook__. Unfortunately, you won't be able to "patch-restore" it using a context manager.