12

この素敵な小さな 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_proj\run.py", line 46, in wrapped
    return fn(*args, **kwargs) if enabled else None
  File "C:\my_proj\run.py", line 490, in a_decorated_function
    some_dict['some_value']
KeyError: 'some_value'
  1. なんで?
  2. 完全なトレースバックを表示するための回避策はありますか?
4

1 に答える 1

8

ああ!わかりました、これは興味深い質問です。

これは同じ近似関数ですが、から直接例外を取得します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
例外:
トレースバック (最新の呼び出しが最後):
  ファイル「x.py」、21 行目、ラップ
    有効な場合は fn(*args, **kwargs) を返し、それ以外の場合はなし
  ファイル「x.py」、29行目、stuff
    raise 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

あはは!振り返ってみると、これはある意味で理にかなっています。この時点で、例外は 2 つのスタック フレームに遭遇しただけです。それは、そのフレーム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

以下も参照してください。

于 2013-01-25T18:33:36.547 に答える