追溯显示,直到装饰者

2022-04-07 00:00:00 python decorator traceback

问题描述

这个漂亮的小Python装饰器可以配置地禁用装饰函数:

enabled = get_bool_from_config()

def run_if_enabled(fn):
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs) if enabled else None
        except Exception:
            log.exception('')
            return None
    return wrapped

唉,如果在fn()内引发异常,回溯只显示到包装器:

Traceback (most recent call last):
  File "C:my_projun.py", line 46, in wrapped
    return fn(*args, **kwargs) if enabled else None
  File "C:my_projun.py", line 490, in a_decorated_function
    some_dict['some_value']
KeyError: 'some_value'
  1. 为什么?
  2. 我是否可以解决办法以查看完整的回溯?

解决方案

啊!好了,这是一个有趣的问题!

这里是相同的近似函数,但直接从sys.exc_info()抓取异常:

import sys
import traceback

def save_if_allowed(fn):
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs) if enabled else None
        except Exception:
            print "The exception:"
            print "".join(traceback.format_exception(*sys.exc_info()))
            return None
    return wrapped

@save_if_allowed
def stuff():
    raise Exception("stuff")


def foo():
    stuff()

foo()

这是真的:打印的回溯中不包括更高的堆栈帧:

$ python test.py
The exception:
Traceback (most recent call last):
  File "x.py", line 21, in wrapped
    return fn(*args, **kwargs) if enabled else None
  File "x.py", line 29, in stuff
    raise Exception("stuff")
Exception: stuff
现在,为了缩小范围,我怀疑这是因为堆栈帧在最近的try/except块…之前只包括堆栈信息因此,我们应该能够在没有装饰器的情况下重新创建它:

$ cat test.py
def inner():
    raise Exception("inner")

def outer():
    try:
        inner()
    except Exception:
        print "".join(traceback.format_exception(*sys.exc_info()))

def caller():
    outer()

caller()

$ python test.py
Traceback (most recent call last):
  File "x.py", line 42, in outer
    inner()
  File "x.py", line 38, in inner
    raise Exception("inner")
Exception: inner

阿哈!现在,经过反思,这在某种程度上是有意义的:在这一点上,异常只遇到了两个堆栈帧:inner()outer()--异常还不知道outer()是从哪里调用的。

因此,要获得完整的堆栈,您需要将当前堆栈与异常的堆栈组合:

$ cat test.py
def inner():
    raise Exception("inner")

def outer():
    try:
        inner()
    except Exception:
        exc_info = sys.exc_info()
        stack = traceback.extract_stack()
        tb = traceback.extract_tb(exc_info[2])
        full_tb = stack[:-1] + tb
        exc_line = traceback.format_exception_only(*exc_info[:2])
        print "Traceback (most recent call last):"
        print "".join(traceback.format_list(full_tb)),
        print "".join(exc_line)

def caller():
    outer()

caller()

$ python test.py
Traceback (most recent call last):
  File "test.py", line 56, in <module>
    caller()
  File "test.py", line 54, in caller
    outer()
  File "test.py", line 42, in outer
    inner()
  File "test.py", line 38, in inner
    raise Exception("inner")
Exception: inner

另见:

  • http://docs.python.org/2/library/sys.html
  • http://docs.python.org/2/library/traceback.html

相关文章