ดึงข้อมูลย้อนกลับจากออบเจ็กต์ข้อยกเว้น


111

การระบุอ็อบเจ็กต์ Exception (ไม่ทราบแหล่งกำเนิด) มีวิธีการติดตามย้อนกลับหรือไม่ ฉันมีรหัสดังนี้:

def stuff():
   try:
       .....
       return useful
   except Exception as e:
       return e

result = stuff()
if isinstance(result, Exception):
    result.traceback <-- How?

ฉันจะดึงการย้อนกลับจากอ็อบเจ็กต์ Exception ได้อย่างไรเมื่อมีแล้ว

คำตอบ:


92

คำตอบสำหรับคำถามนี้ขึ้นอยู่กับเวอร์ชันของ Python ที่คุณใช้

ใน Python 3

มันง่ายมาก: ข้อยกเว้นมาพร้อมกับ__traceback__แอตทริบิวต์ที่มีการย้อนกลับ แอตทริบิวต์นี้สามารถเขียนได้และสามารถตั้งค่าได้อย่างสะดวกโดยใช้with_tracebackวิธีการยกเว้น:

raise Exception("foo occurred").with_traceback(tracebackobj)

คุณลักษณะเหล่านี้อธิบายสั้น ๆ เป็นส่วนหนึ่งของraiseเอกสารประกอบ

เครดิตทั้งหมดสำหรับส่วนหนึ่งของคำตอบนี้ควรจะไป Vyctor ที่แรกที่โพสต์ข้อมูลนี้ ฉันรวมไว้ที่นี่เท่านั้นเนื่องจากคำตอบนี้ติดอยู่ที่ด้านบนและ Python 3 ก็กลายเป็นเรื่องธรรมดามากขึ้น

ใน Python 2

มันซับซ้อนน่ารำคาญ ปัญหาในการย้อนกลับคือมีการอ้างอิงถึงสแต็กเฟรมและสแต็กเฟรมมีการอ้างอิงถึงการย้อนกลับที่มีการอ้างอิงถึงสแต็กเฟรมที่มีการอ้างอิงถึง ...คุณจะได้รับแนวคิด สิ่งนี้ทำให้เกิดปัญหากับคนเก็บขยะ (ขอบคุณecatmur ที่ชี้ให้เห็นเป็นครั้งแรก)

วิธีที่ดีในการแก้ปัญหานี้คือการผ่าตัดตัดวงจรหลังจากออกจากexceptข้อซึ่งเป็นสิ่งที่ Python 3 ทำ โซลูชัน Python 2 นั้นน่าเกลียดกว่ามาก: คุณได้รับฟังก์ชันเฉพาะกิจsys.exc_info()ซึ่งใช้งานได้เฉพาะในส่วน except คำสั่งเท่านั้น จะส่งคืนทูเพิลที่มีข้อยกเว้นประเภทข้อยกเว้นและการตรวจสอบย้อนกลับสำหรับข้อยกเว้นใด ๆ ที่กำลังจัดการอยู่

ดังนั้นหากคุณอยู่ในexceptอนุประโยคคุณสามารถใช้ผลลัพธ์ของsys.exc_info()พร้อมกับtracebackโมดูลเพื่อทำสิ่งต่างๆที่เป็นประโยชน์:

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
... 
>>> raise_exception()
  File "<stdin>", line 3, in raise_exception

แต่ในขณะที่การแก้ไขของคุณบ่งชี้ว่าคุณกำลังพยายามที่จะได้รับ traceback ที่จะได้รับการตีพิมพ์ถ้าข้อยกเว้นของคุณไม่ได้รับการจัดการหลังจากที่มันได้แล้วรับการจัดการ นั่นเป็นคำถามที่ยากกว่ามาก น่าเสียดายที่sys.exc_infoส่งคืน(None, None, None)เมื่อไม่มีการจัดการข้อยกเว้น sysแอตทริบิวต์อื่น ๆ ที่เกี่ยวข้องก็ไม่ช่วยเช่นกัน sys.exc_tracebackเลิกใช้งานและไม่ได้กำหนดเมื่อไม่มีการจัดการข้อยกเว้น sys.last_tracebackดูเหมือนจะสมบูรณ์แบบ แต่ดูเหมือนจะกำหนดเฉพาะในช่วงการโต้ตอบเท่านั้น

หากคุณสามารถควบคุมวิธีเพิ่มข้อยกเว้นคุณอาจสามารถใช้inspectและข้อยกเว้นที่กำหนดเองเพื่อจัดเก็บข้อมูลบางส่วน แต่ฉันไม่แน่ใจทั้งหมดว่าจะได้ผลอย่างไร

เพื่อบอกความจริงการจับและส่งคืนข้อยกเว้นถือเป็นเรื่องผิดปกติที่ต้องทำ นี่อาจเป็นสัญญาณว่าคุณต้องทำการ refactor อยู่ดี


ฉันยอมรับว่าการส่งคืนข้อยกเว้นนั้นไม่ธรรมดา แต่ดูคำถามอื่นของฉันเพื่อดูเหตุผลบางอย่างที่อยู่เบื้องหลังนี้
georg

@ thg435 โอเคนี่เข้าท่ากว่าแล้ว พิจารณาวิธีแก้ปัญหาข้างต้นของฉันโดยใช้sys.exc_infoร่วมกับวิธีการติดต่อกลับที่ฉันแนะนำสำหรับคำถามอื่น ๆ ของคุณ
ส่ง

ข้อมูลเพิ่มเติม (มีน้อยมาก) ในวัตถุย้อนกลับ : docs.python.org/3/library/types.html#types.TracebackType docs.python.org/3/reference/datamodel.html#traceback-objects
0xfede7c8

69

เนื่องจากPython 3.0 [PEP 3109]คลาสในตัวExceptionมี__traceback__แอตทริบิวต์ที่มีtraceback object(พร้อม Python 3.2.3):

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

ปัญหาคือหลังจากGoogling ไป__traceback__ระยะหนึ่งฉันพบเพียงไม่กี่บทความ แต่ไม่มีบทความใดเลยที่อธิบายว่าคุณควร (ไม่) ใช้__traceback__หรือไม่

อย่างไรก็ตามเอกสาร Python 3 สำหรับraiseระบุว่า:

โดยปกติอ็อบเจ็กต์การย้อนกลับจะถูกสร้างขึ้นโดยอัตโนมัติเมื่อมีการยกข้อยกเว้นและแนบเป็น__traceback__แอ็ตทริบิวต์ซึ่งสามารถเขียนได้

ดังนั้นฉันคิดว่ามันควรจะใช้


4
ใช่มันมีขึ้นเพื่อใช้ จากมีอะไรใหม่ใน Python 3.0 "PEP 3134: ตอนนี้อ็อบเจ็กต์ Exception เก็บ traceback เป็นแอ็ตทริบิวต์tracebackซึ่งหมายความว่าอ็อบเจ็กต์ข้อยกเว้นมีข้อมูลทั้งหมดที่เกี่ยวข้องกับข้อยกเว้นและมีเหตุผลน้อยกว่าที่จะใช้ sys.exc_info () ( แม้ว่าจะไม่ถูกลบออก) "
Maciej Szpakowski

ฉันไม่เข้าใจจริงๆว่าทำไมคำตอบนี้จึงลังเลและไม่มีความชัดเจน เป็นคุณสมบัติที่มีเอกสาร ทำไมจึงไม่ "หมายถึงใช้"?
Mark Amery

2
@MarkAmery อาจเป็น__ในชื่อที่บ่งบอกว่าเป็นรายละเอียดการใช้งานไม่ใช่ทรัพย์สินสาธารณะ?
พื้นฐาน

4
@ พื้นฐานนั่นไม่ใช่สิ่งที่บ่งบอกที่นี่ ตามปกติแล้วใน Python __fooเป็นวิธีส่วนตัว แต่__foo__(มีขีดล่างด้วย) เป็นวิธี "มายากล" (ไม่ใช่แบบส่วนตัว)
Mark Amery

1
FYI __traceback__แอตทริบิวต์นี้ปลอดภัย 100% ที่จะใช้ตามที่คุณต้องการโดยไม่มีผลกระทบของ GC มันยากที่จะบอกได้ว่าจากเอกสาร แต่ ecatmur พบหลักฐานยาก
ส่ง

39

วิธีรับการย้อนกลับเป็นสตริงจากอ็อบเจ็กต์ข้อยกเว้นใน Python 3:

import traceback

# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...)ส่งคืนรายการสตริง ''.join(...)รวมเข้าด้วยกัน สำหรับข้อมูลอ้างอิงเพิ่มเติมโปรดไปที่: https://docs.python.org/3/library/traceback.html#traceback.format_tb


21

นอกจากนี้หากคุณต้องการรับการย้อนกลับแบบเต็มตามที่คุณเห็นพิมพ์ไปยังเทอร์มินัลของคุณคุณต้องการสิ่งนี้:

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

หากคุณใช้format_tbคำตอบข้างต้นแนะนำว่าคุณจะได้รับข้อมูลน้อยลง:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>

4
ในที่สุด! นี่น่าจะเป็นคำตอบอันดับต้น ๆ ขอบคุณแดเนียล!
Dany

3
อ๊ะฉันใช้เวลา 20 นาทีสุดท้ายในการพยายามหาสิ่งนี้ก่อนที่ฉันจะพบสิ่งนี้ :-) etype=type(exc)สามารถละเว้นได้แล้วตอนนี้ btw: "เปลี่ยนแปลงในเวอร์ชัน 3.5: อาร์กิวเมนต์ etype ถูกละเว้นและอนุมานจากประเภทของค่า" docs.python.org/3.7/library/…ทดสอบใน Python 3.7.3
Ciro Santilli 郝海东冠状病六四事件

8

มีเหตุผลที่ดีมากที่การย้อนกลับจะไม่ถูกเก็บไว้ในข้อยกเว้น เนื่องจากการย้อนกลับมีการอ้างอิงถึงโลคัลของสแต็กสิ่งนี้จะส่งผลให้การอ้างอิงแบบวงกลมและหน่วยความจำรั่วไหล (ชั่วคราว) จนกว่า GC แบบวงกลมจะเริ่มทำงาน (นี่คือเหตุผลที่คุณไม่ควรเก็บการย้อนกลับไว้ในตัวแปรโลคัล )

เกี่ยวกับสิ่งเดียวที่ฉันคิดได้คือให้คุณใช้stuffglobals ของMonkeypatch ดังนั้นเมื่อมันคิดว่ามันจับExceptionได้มันจับประเภทพิเศษได้จริงและข้อยกเว้นจะแพร่กระจายไปยังคุณในฐานะผู้โทร:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()

7
นี่เป็นสิ่งที่ไม่ถูกต้อง Python 3 ใส่วัตถุการย้อนกลับในข้อยกเว้นเช่นe.__traceback__.
Glenn Maynard

6
@GlennMaynard หลาม 3 แก้ไขปัญหาโดยการลบเป้าหมายข้อยกเว้นในการออกจากexceptบล็อกต่อPEP 3110
ecatmur
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.