Dapatkan deskripsi pengecualian dan susun jejak yang menyebabkan pengecualian, semuanya sebagai string

423

Saya telah melihat banyak posting tentang jejak stack dan pengecualian dalam Python. Tetapi belum menemukan apa yang saya butuhkan.

Saya memiliki sepotong kode Python 2.7 yang dapat menimbulkan pengecualian. Saya ingin menangkapnya dan menetapkan ke string deskripsi lengkap dan jejak stack yang menyebabkan kesalahan (cukup semua yang kita gunakan untuk melihat di konsol). Saya perlu string ini untuk mencetaknya ke kotak teks di GUI.

Sesuatu seperti ini:

try:
    method_that_can_raise_an_exception(params)
except Exception as e:
    print_to_textbox(complete_exception_description(e))

Masalahnya adalah: apa fungsinya complete_exception_description?

kebiruan
sumber

Jawaban:

616

Lihat tracebackmodul, khususnya format_exc()fungsinya. Di sini .

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb
baik hati
sumber
2
Apakah ini hanya berfungsi dengan kesalahan terakhir? Apa yang terjadi jika Anda mulai meneruskan kesalahan ke bit kode lainnya? Saya sedang menulis log_error(err)fungsi.
AnnanFay
Ini berfungsi dengan kesalahan yang ditangkap dan ditangani.
hati
74

Mari kita membuat stacktrace yang cukup rumit, untuk menunjukkan bahwa kita mendapatkan stacktrace lengkap:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Logout stacktrace penuh

Praktik terbaik adalah membuat logger diatur untuk modul Anda. Ia akan mengetahui nama modul dan dapat mengubah level (di antara atribut lainnya, seperti penangan)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Dan kita bisa menggunakan logger ini untuk mendapatkan kesalahan:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Log mana:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Jadi kami mendapatkan output yang sama seperti ketika kami memiliki kesalahan:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Hanya mendapatkan string

Jika Anda benar-benar hanya menginginkan string, gunakan traceback.format_excfungsinya sebagai gantinya, menunjukkan pendataan string di sini:

import traceback
try:
    do_something_that_might_error()
except Exception as error:
    just_the_string = traceback.format_exc()
    logger.debug(just_the_string)

Log mana:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Aaron Hall
sumber
1
apakah ini metode terbaik saat menggunakan python 3 (dibandingkan dengan misalnya beberapa jawaban di bawah)?
Yunti
1
@Yunti Saya percaya API ini konsisten di Python 2 dan 3.
Aaron Hall
Pemformatan jawaban ini dibahas pada meta: meta.stackoverflow.com/questions/386477/… .
Lundin
Saya mengirim suntingan ke yang berikut tetapi tidak masuk sehingga menunjukkan sebagai anonim: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg
@arntg Saya menghargai Anda mencoba membantu, tetapi suntingan itu akan menjadi perubahan yang berbahaya. Harap lebih berhati-hati di masa depan untuk memahami sepenuhnya API yang Anda coba gunakan. Dalam hal ini, exc_infoargumen mengharapkan "tuple pengecualian" sedangkan errorturunan dari Exceptionobjek (atau subkelas), dan tidak perlu diubah errorke e.
Aaron Hall
39

Dengan Python 3, kode berikut akan memformat Exceptionobjek persis seperti yang akan diperoleh menggunakan traceback.format_exc():

import traceback

try: 
    method_that_can_raise_an_exception(params)
except Exception as ex:
    print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))

Keuntungannya adalah bahwa hanya Exceptionobjek yang diperlukan (berkat __traceback__atribut yang direkam ), dan karenanya dapat lebih mudah diteruskan sebagai argumen ke fungsi lain untuk diproses lebih lanjut.

Erwin Mayer
sumber
1
Itu lebih baik daripada sys.exc_info () yang bukan gaya OO yang baik dan menggunakan variabel global.
WeiChing 林 煒 清
Ini menanyakan secara khusus cara mendapatkan traceback dari objek pengecualian seperti yang Anda lakukan di sini: stackoverflow.com/questions/11414894/…
Ciro Santilli 郝海东 冠状 病 六四 事件 事件 法轮功
Ada cara Python3 yang lebih sederhana tanpa menggunakan .__traceback__dan type, lihat stackoverflow.com/a/58764987/5717886
don_vanchos
34
>>> import sys
>>> import traceback
>>> try:
...   5 / 0
... except ZeroDivisionError as e:
...   type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
['  File "<stdin>", line 2, in <module>\n']
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

Anda menggunakan sys.exc_info () untuk mengumpulkan informasi dan fungsi-fungsi dalam tracebackmodul untuk memformatnya. Berikut adalah beberapa contoh untuk memformatnya.

Seluruh string pengecualian ada di:

>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: integer division or modulo by zero\n']
lebih buruk
sumber
9

Bagi yang menggunakan Python-3

Menggunakan tracebackmodul dan exception.__traceback__seseorang dapat mengekstrak tumpukan-jejak sebagai berikut:

  • ambil saat tumpukan-jejak menggunakantraceback.extract_stack()
  • hapus tiga elemen terakhir (karena itu adalah entri dalam tumpukan yang membawa saya ke fungsi debug saya)
  • menambahkan __traceback__dari objek pengecualian menggunakantraceback.extract_tb()
  • memformat semuanya menggunakan traceback.format_list()
import traceback
def exception_to_string(excp):
   stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__)  # add limit=?? 
   pretty = traceback.format_list(stack)
   return ''.join(pretty) + '\n  {} {}'.format(excp.__class__,excp)

Peragaan sederhana:

def foo():
    try:
        something_invalid()
    except Exception as e:
        print(exception_to_string(e))

def bar():
    return foo()

Kami mendapatkan output berikut saat kami menelepon bar():

  File "./test.py", line 57, in <module>
    bar()
  File "./test.py", line 55, in bar
    return foo()
  File "./test.py", line 50, in foo
    something_invalid()

  <class 'NameError'> name 'something_invalid' is not defined
Mike N
sumber
Sepertinya kode rumit yang tidak bisa dibaca. Dalam Python 3.5+ ada cara yang lebih elegan dan sederhana: stackoverflow.com/a/58764987/5717886
don_vanchos
6

Anda juga dapat mempertimbangkan untuk menggunakan modul Python bawaan , cgitb , untuk mendapatkan informasi pengecualian yang benar-benar bagus, diformat dengan baik termasuk nilai variabel lokal, konteks kode sumber, parameter fungsi dll.

Misalnya untuk kode ini ...

import cgitb
cgitb.enable(format='text')

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

kami mendapatkan output pengecualian ini ...

ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 c:\TEMP\cgittest2.py in <module>()
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
func1 = <function func1>

 c:\TEMP\cgittest2.py in func1(a=1, b=5)
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
    3
    4 def func2(a, divisor):
    5   return a / divisor
    6
    7 def func1(a, b):
a = 1
divisor = 0
ZeroDivisionError: division by zero
    __cause__ = None
    __class__ = <class 'ZeroDivisionError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of ZeroDivisionError object>
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __eq__ = <method-wrapper '__eq__' of ZeroDivisionError object>
    __format__ = <built-in method __format__ of ZeroDivisionError object>
    __ge__ = <method-wrapper '__ge__' of ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object>
    __gt__ = <method-wrapper '__gt__' of ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of ZeroDivisionError object>
    __le__ = <method-wrapper '__le__' of ZeroDivisionError object>
    __lt__ = <method-wrapper '__lt__' of ZeroDivisionError object>
    __ne__ = <method-wrapper '__ne__' of ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ('division by zero',)
    with_traceback = <built-in method with_traceback of ZeroDivisionError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgittest2.py", line 11, in <module>
    func1(1, 5)
  File "cgittest2.py", line 9, in func1
    return func2(a, c)
  File "cgittest2.py", line 5, in func2
    return a / divisor
ZeroDivisionError: division by zero
samaspin
sumber
5

Jika Anda ingin mendapatkan informasi yang sama ketika pengecualian tidak ditangani, Anda dapat melakukan sesuatu seperti ini. Lakukan import tracebackdan kemudian:

try:
    ...
except Exception as e:
    print(traceback.print_tb(e.__traceback__))

Saya menggunakan Python 3.7.

SamuelN
sumber
3

Untuk Python 3.5+ :

Jadi, Anda bisa mendapatkan stacktrace dari pengecualian Anda dari pengecualian lainnya. Gunakan traceback.TracebackExceptionuntuk itu (ganti saja exdengan pengecualian Anda):

print("".join(traceback.TracebackException.from_exception(ex).format())

Contoh yang diperluas dan fitur lain untuk melakukan ini:

import traceback

try:
    1/0
except Exception as ex:
    print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
    print("".join(traceback.TracebackException.from_exception(ex).format()))

Outputnya akan seperti ini:

True
Traceback (most recent call last):
  File "untidsfsdfsdftled.py", line 29, in <module>
    1/0
ZeroDivisionError: division by zero
don_vanchos
sumber
1

2-sen saya:

import sys, traceback
try: 
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))
pengguna 1155692
sumber
1

Jika tujuan Anda adalah membuat pesan pengecualian dan stacktrace terlihat persis seperti ketika python melakukan kesalahan, berikut ini berfungsi di kedua python 2 + 3:

import sys, traceback


def format_stacktrace():
    parts = ["Traceback (most recent call last):\n"]
    parts.extend(traceback.format_stack(limit=25)[:-2])
    parts.extend(traceback.format_exception(*sys.exc_info())[1:])
    return "".join(parts)

# EXAMPLE BELOW...

def a():
    b()


def b():
    c()


def c():
    d()


def d():
    assert False, "Noooh don't do it."


print("THIS IS THE FORMATTED STRING")
print("============================\n")

try:
    a()
except:
    stacktrace = format_stacktrace()
    print(stacktrace)

print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()

Ini bekerja dengan menghapus format_stacktrace()panggilan terakhir dari tumpukan dan bergabung dengan yang lainnya. Saat dijalankan, contoh di atas memberikan output berikut:

THIS IS THE FORMATTED STRING
============================

Traceback (most recent call last):
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

THIS IS HOW PYTHON DOES IT
==========================

Traceback (most recent call last):
  File "test.py", line 38, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.
Rune Kaagaard
sumber
0

Saya mendefinisikan kelas pembantu berikut:

import traceback
class TracedExeptions(object):
    def __init__(self):
        pass
    def __enter__(self):
        pass

    def __exit__(self, etype, value, tb):
      if value :
        if not hasattr(value, 'traceString'):
          value.traceString = "\n".join(traceback.format_exception(etype, value, tb))
        return False
      return True

Yang nantinya bisa saya gunakan seperti ini:

with TracedExeptions():
  #some-code-which-might-throw-any-exception

Dan nantinya bisa mengkonsumsinya seperti ini:

def log_err(ex):
  if hasattr(ex, 'traceString'):
    print("ERROR:{}".format(ex.traceString));
  else:
    print("ERROR:{}".format(ex));

(Latar belakang: Saya frustasi karena menggunakan Promises bersama dengan Exceptions, yang sayangnya melewati pengecualian yang muncul di satu tempat ke penangan on_rejected di tempat lain, dan karenanya sulit untuk mendapatkan traceback dari lokasi asli)

qbolec
sumber