17.6. traceback — 异常和调用堆栈跟踪

未匹配的标注

本节目标:准确,清晰的打印出异常和追踪栈。

traceback 与调用栈一起工作,可以生成错误信息。一个 traceback 即为一个从异常处理器到异常被抛出的点的栈追踪器。 traceback 同样可以从当前调用点的调用栈访问(没有异常上下文),这样我们就能用它来找到一个函数的路径。

traceback 中我们可以用上层 API StackSummary 实例和 FrameSummary 实例来获取当前的栈。这些类都可以从 traceback 或当前执行栈中构建,然后以相同的方式被处理。

traceback 中的底层函数有几种分类。有的函数是用来从当前运行环境中提取原始追踪链的(环境可以是追踪的异常处理器或是常规栈)。提取出来的追踪栈是一个包含文件名,行数,函数名和源代码中此行代码的元组的序列。

一旦提取,我们就可以使用 format_exception(), format_stack() 等来格式化追踪栈。格式化函数会返回一个可以打印出来的带有消息的字符串列表。同样我们也有几个方便的打印格式化消息的函数。

尽管 traceback 中的函数所做的事非常像交互式解释器所做的事,我们还是可以在不能把所有栈追踪都输出到控制台上的情况下使用它。举个例子,web 应用程序需要格式化追踪消息,让它们更好得在 HTML 中显示,IDE 也需要把追踪栈中的元素转换成可点击的供开发者们浏览其源码。

支持函数

本节的例子所使用的模块 traceback_example.py

traceback_example.py

import traceback
import sys

def produce_exception(recursion_level=2):
    sys.stdout.flush()
    if recursion_level:
        produce_exception(recursion_level - 1)
    else:
        raise RuntimeError()

def call_function(f, recursion_level=2):
    if recursion_level:
        return call_function(f, recursion_level - 1)
    else:
        return f()

检查栈信息

要想检查当前栈,我们需要从 walk_stack() 中构建一只StackSummary

traceback_stacksummary.py

import traceback
import sys

from traceback_example import call_function

def f():
    summary = traceback.StackSummary.extract(
        traceback.walk_stack(None)
    )
    print(''.join(summary.format()))

print('Calling f() directly:')
f()

print()
print('Calling f() from 3 levels deep:')
call_function(f)

format() 方法会生成一个格式化后的字符串组成的序列。

$ python3 traceback_stacksummary.py

Calling f() directly:
  File "traceback_stacksummary.py", line 18, in f
    traceback.walk_stack(None)
  File "traceback_stacksummary.py", line 24, in <module>
    f()

Calling f() from 3 levels deep:
  File "traceback_stacksummary.py", line 18, in f
    traceback.walk_stack(None)
  File ".../traceback_example.py", line 26, in call_function
    return f()
  File ".../traceback_example.py", line 24, in call_function
    return call_function(f, recursion_level - 1)
  File ".../traceback_example.py", line 24, in call_function
    return call_function(f, recursion_level - 1)
  File "traceback_stacksummary.py", line 28, in <module>
    call_function(f)

StackSummary 是一个包含 FrameSummary 实例的可迭代的容器。

traceback_framesummary.py

import traceback
import sys

from traceback_example import call_function

template = (
    '{fs.filename:<26}:{fs.lineno}:{fs.name}:\n'
    '    {fs.line}'
)

def f():
    summary = traceback.StackSummary.extract(
        traceback.walk_stack(None)
    )
    for fs in summary:
        print(template.format(fs=fs))

print('Calling f() directly:')
f()

print()
print('Calling f() from 3 levels deep:')
call_function(f)

每个 FrameSummary 都对应一个栈的框架,主要包括源文件中执行上下文的位置信息。

$ python3 traceback_framesummary.py

Calling f() directly:
traceback_framesummary.py :23:f:
    traceback.walk_stack(None)
traceback_framesummary.py :30:<module>:
    f()

Calling f() from 3 levels deep:
traceback_framesummary.py :23:f:
    traceback.walk_stack(None)
.../traceback_example.py:26:call_function:
    return f()
.../traceback_example.py:24:call_function:
    return call_function(f, recursion_level - 1)
.../traceback_example.py:24:call_function:
    return call_function(f, recursion_level - 1)
traceback_framesummary.py :34:<module>:
    call_function(f)

TracebackException(追踪异常)

TracebackException 类是一个上层接口用来在处理追踪时创建 StackSummary

traceback_tracebackexception.py

import traceback
import sys

from traceback_example import produce_exception

print('with no exception:')
exc_type, exc_value, exc_tb = sys.exc_info()
tbe = traceback.TracebackException(exc_type, exc_value, exc_tb)
print(''.join(tbe.format()))

print('\nwith exception:')
try:
    produce_exception()
except Exception as err:
    exc_type, exc_value, exc_tb = sys.exc_info()
    tbe = traceback.TracebackException(
        exc_type, exc_value, exc_tb,
    )
    print(''.join(tbe.format()))

    print('\nexception only:')
    print(''.join(tbe.format_exception_only()))

format() 方法会生成所有追踪格式化后的信息,format_exception_only() 则会显示出信息。

$ python3 traceback_tracebackexception.py

with no exception:
None: None

with exception:
Traceback (most recent call last):
  File "traceback_tracebackexception.py", line 22, in <module>
    produce_exception()
  File ".../traceback_example.py", line 17, in produce_exception
    produce_exception(recursion_level - 1)
  File ".../traceback_example.py", line 17, in produce_exception
    produce_exception(recursion_level - 1)
  File ".../traceback_example.py", line 19, in produce_exception
    raise RuntimeError()
RuntimeError

exception only:
RuntimeError

底层异常 API

另一个处理异常报告的方法是使用 print_exc()。它使用 sys.exc_info() 来为当前线程存储异常信息,格式化结果,打印文本到文件句柄(默认是 sys.stderr)。

traceback_print_exc.py

import traceback
import sys

from traceback_example import produce_exception

print('print_exc() with no exception:')
traceback.print_exc(file=sys.stdout)
print()

try:
    produce_exception()
except Exception as err:
    print('print_exc():')
    traceback.print_exc(file=sys.stdout)
    print()
    print('print_exc(1):')
    traceback.print_exc(limit=1, file=sys.stdout)

本例中,我们用 sys.stdout 来代替文件句柄,这样信息和追踪消息会正确显示:

$ python3 traceback_print_exc.py

print_exc() with no exception:
NoneType: None

print_exc():
Traceback (most recent call last):
  File "traceback_print_exc.py", line 20, in <module>
    produce_exception()
  File ".../traceback_example.py", line 17, in produce_exception
    produce_exception(recursion_level - 1)
  File ".../traceback_example.py", line 17, in produce_exception
    produce_exception(recursion_level - 1)
  File ".../traceback_example.py", line 19, in produce_exception
    raise RuntimeError()
RuntimeError

print_exc(1):
Traceback (most recent call last):
  File "traceback_print_exc.py", line 20, in <module>
    produce_exception()
RuntimeError

print_exc() 只是 print_exception() 的快捷方式,只不过我们使用后者时需要准确的参数。

traceback_print_exception.py

import traceback
import sys

from traceback_example import produce_exception

try:
    produce_exception()
except Exception as err:
    print('print_exception():')
    exc_type, exc_value, exc_tb = sys.exc_info()
    traceback.print_exception(exc_type, exc_value, exc_tb)

print_exception() 的参数通过 sys.exc_info() 获取到。

$ python3 traceback_print_exception.py

Traceback (most recent call last):
  File "traceback_print_exception.py", line 16, in <module>
    produce_exception()
  File ".../traceback_example.py", line 17, in produce_exception
    produce_exception(recursion_level - 1)
  File ".../traceback_example.py", line 17, in produce_exception
    produce_exception(recursion_level - 1)
  File ".../traceback_example.py", line 19, in produce_exception
    raise RuntimeError()
RuntimeError
print_exception():

print_exception() 使用的是 format_exception() 来准备要打印的文本。

traceback_format_exception.py

import traceback
import sys
from pprint import pprint

from traceback_example import produce_exception

try:
    produce_exception()
except Exception as err:
    print('format_exception():')
    exc_type, exc_value, exc_tb = sys.exc_info()
    pprint(
        traceback.format_exception(exc_type, exc_value, exc_tb),
        width=65,
    )

format_exception() 要使用的三个参数与 print_exception() 一样,都是 exception type, exception value, traceback

$ python3 traceback_format_exception.py

format_exception():
['Traceback (most recent call last):\n',
 '  File "traceback_format_exception.py", line 17, in
<module>\n'
 '    produce_exception()\n',
 '  File '
 '".../traceback_example.py", '
 'line 17, in produce_exception\n'
 '    produce_exception(recursion_level - 1)\n',
 '  File '
 '".../traceback_example.py", '
 'line 17, in produce_exception\n'
 '    produce_exception(recursion_level - 1)\n',
 '  File '
 '".../traceback_example.py", '
 'line 19, in produce_exception\n'
 '    raise RuntimeError()\n',
 'RuntimeError\n']

要想把 traceback 用其他方式处理,比如弄成不同的输出格式,我们可以用 extract_tb() 获取出数据。

traceback_extract_tb.py

import traceback
import sys
import os
from traceback_example import produce_exception

template = '{filename:<23}:{linenum}:{funcname}:\n    {source}'

try:
    produce_exception()
except Exception as err:
    print('format_exception():')
    exc_type, exc_value, exc_tb = sys.exc_info()
    for tb_info in traceback.extract_tb(exc_tb):
        filename, linenum, funcname, source = tb_info
        if funcname != '<module>':
            funcname = funcname + '()'
        print(template.format(
            filename=os.path.basename(filename),
            linenum=linenum,
            source=source,
            funcname=funcname)
        )

返回的值是包含 traceback 中每层栈入口的列表。每个入口都是4个部分的元组:原文件名,发生的文件行数,相关的函数名和对应的源文件文本并带有空格符。

$ python3 traceback_extract_tb.py

format_exception():
traceback_extract_tb.py:18:<module>:
    produce_exception()
traceback_example.py   :17:produce_exception():
    produce_exception(recursion_level - 1)
traceback_example.py   :17:produce_exception():
    produce_exception(recursion_level - 1)
traceback_example.py   :19:produce_exception():
    raise RuntimeError()

底层栈 API

有几个与 traceback 相同的函数来对当前调用栈做相同的操作。print_stack() 会打印出当前栈但不引发异常。

traceback_print_stack.py

import traceback
import sys

from traceback_example import call_function

def f():
    traceback.print_stack(file=sys.stdout)

print('Calling f() directly:')
f()

print()
print('Calling f() from 3 levels deep:')
call_function(f)

输出很像 traceback 但并无错误信息。

$ python3 traceback_print_stack.py

Calling f() directly:
  File "traceback_print_stack.py", line 21, in <module>
    f()
  File "traceback_print_stack.py", line 17, in f
    traceback.print_stack(file=sys.stdout)

Calling f() from 3 levels deep:
  File "traceback_print_stack.py", line 25, in <module>
    call_function(f)
  File ".../traceback_example.py", line 24, in call_function
    return call_function(f, recursion_level - 1)
  File ".../traceback_example.py", line 24, in call_function
    return call_function(f, recursion_level - 1)
  File ".../traceback_example.py", line 26, in call_function
    return f()
  File "traceback_print_stack.py", line 17, in f
    traceback.print_stack(file=sys.stdout)

format_stack() 准备栈追踪的方式与 format_exception() 一样。

traceback_format_stack.py

import traceback
import sys
from pprint import pprint

from traceback_example import call_function

def f():
    return traceback.format_stack()

formatted_stack = call_function(f)
pprint(formatted_stack)

它返回是的字符串列表,每行输出对应一个元素。

$ python3 traceback_format_stack.py

['  File "traceback_format_stack.py", line 21, in <module>\n'
 '    formatted_stack = call_function(f)\n',
 '  File '
 '".../traceback_example.py", '
 'line 24, in call_function\n'
 '    return call_function(f, recursion_level - 1)\n',
 '  File '
 '".../traceback_example.py", '
 'line 24, in call_function\n'
 '    return call_function(f, recursion_level - 1)\n',
 '  File '
 '".../traceback_example.py", '
 'line 26, in call_function\n'
 '    return f()\n',
 '  File "traceback_format_stack.py", line 18, in f\n'
 '    return traceback.format_stack()\n']

extract_stack() 函数与 extract_tb() 差不多。

traceback_extract_stack.py

import traceback
import sys
import os

from traceback_example import call_function

template = '{filename:<26}:{linenum}:{funcname}:\n    {source}'

def f():
    return traceback.extract_stack()

stack = call_function(f)
for filename, linenum, funcname, source in stack:
    if funcname != '<module>':
        funcname = funcname + '()'
    print(template.format(
        filename=os.path.basename(filename),
        linenum=linenum,
        source=source,
        funcname=funcname)
    )

它还可以接受参数,但我们在这里并未使用,该参数的作用是使其从堆栈帧中的其他位置(层级)开始或限制遍历深度。

$ python3 traceback_extract_stack.py

traceback_extract_stack.py:23:<module>:
    stack = call_function(f)
traceback_example.py      :24:call_function():
    return call_function(f, recursion_level - 1)
traceback_example.py      :24:call_function():
    return call_function(f, recursion_level - 1)
traceback_example.py      :26:call_function():
    return f()
traceback_extract_stack.py:20:f():
    return traceback.extract_stack()

参阅

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/tracebac...

译文地址:https://learnku.com/docs/pymotw/tracebac...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~