【问题标题】:How to limit python traceback to specific files如何将python回溯限制到特定文件
【发布时间】:2015-11-04 03:36:24
【问题描述】:

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

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

【问题讨论】:

  • 您可以在 main 或 lower 中捕获异常,并决定打印什么。使用回溯模块。 extract_tb() 可用于检索值。您可以查看它们来决定打印什么,不打印什么。
  • 作为一种不需要将所有现有代码包装在 try/except 块中的替代方案,您可以执行cgitb 所做的操作并使用sys.excepthook 来获得所需的结果。甚至可能有一种方法可以配置 cgitb 来做到这一点,但我不确定。 (通常用于扩展回溯输出。)
  • @Two-BitAlchemist,sys.excepthook 方法总体上看起来很棒,但我正在尝试调试 Flask 应用程序。我无法弄清楚,即使在绕过它自己的错误处理系统的调试模式下,如何覆盖默认的 sys.excepthook。

标签: python debugging traceback


【解决方案1】:

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

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

此函数将给定的回溯和异常打印到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

【讨论】:

    【解决方案2】:

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

    此函数将给定的回溯和异常打印到 sys.stderr

    当异常被引发但未被捕获时,解释器调用 sys.excepthook 并带有三个参数,异常类、异常实例和回溯对象。在交互式会话中,这发生在控制返回到提示之前;在 Python 程序中,这发生在程序退出之前。 可以通过将另一个三参数函数分配给 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。它返回“从回溯对象中提取的“预处理”堆栈跟踪条目列表”;它们都是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
    

    注意:不要忘记从 sys.__excepthook__ 恢复 sys.excepthook。不幸的是,您将无法使用上下文管理器“修补恢复”它。

    【讨论】:

    • 完美的解决方案,不像公认的答案,不需要在所有项目文件中添加全局变量。谢谢
    【解决方案3】:

    traceback.extract_tb(tb) 将返回格式为(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
    

    【讨论】:

    • 投反对票的人能否写一条评论,说明它值得投反对票吗?
    • 我看到 2 个用户对这个答案投了反对票。严格来说,它并没有真正回答问题,因为这里提供的信息不足以解决 OP 的问题。也许您会澄清提问者究竟需要做什么,而不是含糊地建议“玩 traceback.extract_tb”。
    猜你喜欢
    • 2017-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-09
    • 2015-01-27
    • 1970-01-01
    • 2011-06-04
    • 1970-01-01
    相关资源
    最近更新 更多