กำลังเพิ่มข้อมูลลงในข้อยกเว้นหรือไม่


142

ฉันต้องการบรรลุสิ่งนี้:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

แต่สิ่งที่ฉันได้คือ:

Traceback..
  IOError('Stuff')

เบาะแสใด ๆ เกี่ยวกับวิธีการบรรลุสิ่งนี้? วิธีทำทั้งใน Python 2 และ 3


ในขณะที่กำลังมองหาเอกสารสำหรับmessageแอตทริบิวต์Exception ฉันพบคำถาม SO นี้BaseException.message เลิกใช้ใน Python 2.6ซึ่งดูเหมือนว่าจะบ่งบอกว่าการใช้งานนั้นไม่ได้รับการสนับสนุนในขณะนี้ (และทำไมจึงไม่อยู่ในเอกสาร)
martineau

น่าเศร้าที่ลิงค์นั้นดูเหมือนจะไม่ทำงานอีกต่อไป
Michael Scott Cuthbert

1
@MichaelScottCuthbert นี่เป็นทางเลือกที่ดี: itmaybeahack.com/book/python-2.6/html/p02/…
Niels Keurentjes

นี่เป็นสิ่งที่ดีจริงๆคำอธิบายของสิ่งที่สถานะของแอตทริบิวต์ข้อความและความสัมพันธ์กับ args แอตทริบิวต์และPEP 352 มันมาจากทักษะการสร้างหนังสือฟรีใน Pythonโดย Steven F. Lott
martineau

คำตอบ:


118

ฉันต้องการทำเช่นนี้ดังนั้นการเปลี่ยนประเภทในfoo()ไม่จำเป็นต้องเปลี่ยนมันbar()ด้วย

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

อัปเดต 1

นี่คือการปรับเปลี่ยนเล็กน้อยที่เก็บรักษาย้อนกลับดั้งเดิม:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

อัปเดต 2

สำหรับ Python 3.x รหัสในการอัปเดตครั้งแรกของฉันนั้นไม่ถูกต้องทางวากยสัมพันธ์บวกกับความคิดที่ว่าจะมีการเปิดใช้messageคุณลักษณะBaseExceptionนั้นในการเปลี่ยนเป็น PEP 352ใน 2012-05-16 (อัปเดตครั้งแรกของฉันเมื่อวันที่ 2012-03-12) . ดังนั้นในปัจจุบันในหลาม 3.5.2 แล้วคุณจะต้องทำอะไรบางอย่างตามบรรทัดเหล่านี้เพื่อรักษา traceback และไม่ hardcode bar()ประเภทของข้อยกเว้นในการทำงาน โปรดทราบว่าจะมีบรรทัด:

During handling of the above exception, another exception occurred:

ในข้อความย้อนกลับที่ปรากฏขึ้น

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

อัปเดต 3

commenter ถามว่ามีวิธีการที่จะทำงานทั้งในหลาม 2 และ 3 ถึงแม้ว่าคำตอบอาจดูเหมือนจะ "ไม่" เนื่องจากความแตกต่างไวยากรณ์มีเป็นทางรอบว่าด้วยการใช้ฟังก์ชั่นผู้ช่วยเหมือนreraise()ในsixAdd- ในโมดูล ดังนั้นหากคุณไม่ต้องการใช้ห้องสมุดด้วยเหตุผลบางอย่างด้านล่างเป็นเวอร์ชั่นสแตนด์อโลนที่ง่ายขึ้น

โปรดทราบด้วยเช่นกันว่าเนื่องจากมีการยกเว้นข้อยกเว้นภายในreraise()ฟังก์ชันที่จะปรากฏในสิ่งที่เพิ่มกลับ แต่ผลลัพธ์สุดท้ายคือสิ่งที่คุณต้องการ

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

3
นั่นทำให้สูญเสีย backtrace ซึ่งเป็นการเอาชนะจุดเพิ่มข้อมูลลงในข้อยกเว้นที่มีอยู่ นอกจากนี้จะไม่ทำงานกับข้อยกเว้น ctor ที่ใช้> 1 อาร์กิวเมนต์ (ชนิดเป็นสิ่งที่คุณไม่สามารถควบคุมได้จากสถานที่ที่คุณตรวจพบข้อยกเว้น)
VáclavSlavík

1
@ Václav: มันค่อนข้างง่ายที่จะป้องกันการสูญเสีย backtrace - ดังที่แสดงในการอัพเดตที่ฉันเพิ่ม แม้ว่าสิ่งนี้จะไม่จัดการกับข้อยกเว้นที่คาดเดาได้ทั้งหมด แต่ก็ใช้ได้กับกรณีที่คล้ายกับที่แสดงในคำถามของ OP
martineau

1
นี้ไม่ได้ค่อนข้างขวา หากการแทนที่ประเภท (e) __str__คุณอาจได้ผลลัพธ์ที่ไม่พึงประสงค์ type(e)(type(e)(e.message)นอกจากนี้ทราบว่าอาร์กิวเมนต์ที่สองจะถูกส่งไปสร้างที่กำหนดโดยอาร์กิวเมนต์แรกซึ่งผลตอบแทนถัวเฉลี่ยที่ค่อนข้างไร้สาระ ประการที่สามe.message ถูกคัดค้าน e.args [0]
bukzor

1
ดังนั้นจึงไม่มีวิธีพกพาที่ใช้ได้ทั้ง Python 2 และ 3
Elias Dorneles

1
@martineau จุดประสงค์ของการนำเข้าภายในบล็อกยกเว้นคืออะไร เป็นการบันทึกหน่วยความจำด้วยการนำเข้าเมื่อจำเป็นเท่านั้นหรือไม่
AllTradesJack

115

ในกรณีที่คุณมาที่นี่เพื่อค้นหาวิธีแก้ปัญหาสำหรับ Python 3 คู่มือ บอกว่า:

เมื่อเพิ่มข้อยกเว้นใหม่ (แทนที่จะใช้ข้อยกเว้นraiseเพื่อยกระดับข้อยกเว้นที่กำลังได้รับการจัดการใหม่) บริบทข้อยกเว้นโดยนัยสามารถเสริมด้วยสาเหตุที่ชัดเจนโดยใช้จากด้วยการเพิ่ม:

raise new_exc from original_exc

ตัวอย่าง:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

ซึ่งดูเหมือนว่าในท้ายที่สุด:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

เปลี่ยนเป็นอึมครึมโดยสิ้นเชิงTypeErrorเป็นข้อความที่ดีพร้อมคำแนะนำถึงวิธีการแก้ปัญหาโดยไม่ทำให้เกิดข้อยกเว้นขึ้นมา


14
นี่คือทางออกที่ดีที่สุดเนื่องจากข้อยกเว้นที่เกิดขึ้นชี้ไปที่สาเหตุดั้งเดิมให้รายละเอียดเพิ่มเติม
JT

มีวิธีใดบ้างที่เราสามารถเพิ่มข้อความ แต่ยังไม่เพิ่มข้อยกเว้นใหม่ ฉันหมายถึงเพียงขยายข้อความของอินสแตนซ์ข้อยกเว้น
edcSam

Yaa ~~ ใช้งานได้ แต่รู้สึกเหมือนเป็นสิ่งที่ไม่ควรทำกับฉัน ข้อความถูกเก็บไว้e.argsแต่เป็น Tuple ดังนั้นจึงไม่สามารถเปลี่ยนแปลงได้ ดังนั้นคัดลอกแรกargsลงในรายการจากนั้นปรับเปลี่ยนจากนั้นคัดลอกกลับเป็น Tuple:args = list(e.args) args[0] = 'bar' e.args = tuple(args)
Chris

27

สมมติว่าคุณไม่ต้องการหรือไม่สามารถแก้ไข foo () คุณสามารถทำได้:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

นี่เป็นวิธีแก้ปัญหาเดียวที่นี่ที่แก้ปัญหาใน Python 3 โดยไม่น่าเกลียดและสับสน "ระหว่างการจัดการกับข้อยกเว้นข้างต้นมีข้อยกเว้นอื่นเกิดขึ้น"

ในกรณีที่ควรเพิ่มบรรทัดการเพิ่มอีกครั้งในการติดตามสแต็กการเขียนraise eแทนที่จะraiseทำเคล็ดลับ


แต่ในกรณีนี้ถ้าข้อยกเว้นการเปลี่ยนแปลงใน foo ฉันต้องเปลี่ยนแถบเช่นกันใช่มั้ย
anijhaw

1
หากคุณพบข้อยกเว้น (แก้ไขด้านบน) คุณสามารถตรวจสอบข้อยกเว้นของไลบรารีมาตรฐานใด ๆ (รวมถึงสิ่งที่สืบทอดมาจากข้อยกเว้นและการเรียกข้อยกเว้น. _ init__)
Steve Howard

6
ให้สมบูรณ์มากขึ้น / ความร่วมมือรวมถึงส่วนอื่น ๆ ของ tuple เดิม:e.args = ('mynewstr' + e.args[0],) + e.args[1:]
# # Dubslow

1
@ nmz787 นี่เป็นทางออกที่ดีที่สุดสำหรับ Python 3 ในความเป็นจริง ข้อผิดพลาดของคุณคืออะไร?
คริสเตียน

1
@Dubslow และ martineau ฉันรวมคำแนะนำของคุณไว้ในการแก้ไข
คริสเตียน

9

ฉันไม่ชอบคำตอบที่ให้ทั้งหมด พวกเขายังคง verbose imho เกินไป ไม่ว่าจะเป็นโค้ดและเอาต์พุตข้อความ

ทั้งหมดที่ฉันต้องการคือ stacktrace ที่ชี้ไปยังข้อยกเว้นซอร์สไม่มีสิ่งยกเว้นระหว่างนั้นดังนั้นจึงไม่มีการสร้างข้อยกเว้นใหม่เพียงแค่เพิ่มต้นฉบับขึ้นใหม่ด้วยสถานะเฟรมสแต็กที่เกี่ยวข้องทั้งหมดซึ่งนำไปสู่ที่นั่น

Steve Howardให้คำตอบที่ดีซึ่งฉันต้องการขยายไม่ลด ... ถึง python 3 เท่านั้น

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

สิ่งใหม่เพียงอย่างเดียวคือการขยายพารามิเตอร์ / เปิดออกซึ่งทำให้เล็กและง่ายพอสำหรับฉันที่จะใช้

ลองมัน:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

สิ่งนี้จะทำให้คุณ:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

พิมพ์สวยเรียบง่ายอาจเป็นสิ่งที่ชอบ

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

5

วิธีการที่สะดวกวิธีหนึ่งที่ฉันใช้คือการใช้ class attribute เป็นที่เก็บข้อมูลเพื่อดูรายละเอียดเนื่องจาก class class นั้นสามารถเข้าถึงได้ทั้งจากอ็อบเจกต์คลาสและอินสแตนซ์ของคลาส:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

จากนั้นในรหัสของคุณ:

raise CustomError({'data': 5})

และเมื่อพบข้อผิดพลาด:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

ไม่มีประโยชน์จริง ๆ เนื่องจาก OP ขอให้พิมพ์รายละเอียดเป็นส่วนหนึ่งของการติดตามสแต็กเมื่อมีการโยนข้อยกเว้นดั้งเดิมและไม่ถูกดักจับ
cowbert

ฉันคิดว่าทางออกเป็นสิ่งที่ดี แต่คำอธิบายไม่เป็นความจริง คุณลักษณะคลาสจะถูกคัดลอกไปยังอินสแตนซ์เมื่อคุณสร้างอินสแตนซ์ ดังนั้นเมื่อคุณปรับเปลี่ยนแอตทริบิวต์ "รายละเอียด" ของอินสแตนซ์แอตทริบิวต์ class จะยังคงเป็น None อย่างไรก็ตามเราต้องการพฤติกรรมนี้ที่นี่
Adam Wallner

2

ไม่เหมือนคำตอบก่อนหน้านี้ใช้งานได้เมื่อพบกับข้อยกเว้นที่แย่__str__มาก มันไม่ปรับเปลี่ยนชนิด แต่ในการสั่งซื้อปัจจัยการช่วยเหลือ__str__การใช้งาน

ฉันยังต้องการค้นหาการปรับปรุงเพิ่มเติมที่ไม่ได้แก้ไขประเภท

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

การติดตามย้อนกลับและประเภท (ชื่อ) ดั้งเดิมจะถูกเก็บรักษาไว้

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!

2

ฉันจะให้ข้อมูลโค้ดที่ฉันใช้บ่อยครั้งเมื่อใดก็ตามที่ฉันต้องการเพิ่มข้อมูลเพิ่มเติมลงในข้อยกเว้น ฉันทำงานทั้งใน Python 2.7 และ 3.6

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

รหัสข้างต้นส่งผลให้ผลลัพธ์ต่อไปนี้:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


ฉันรู้ว่าสิ่งนี้เบี่ยงเบนไปเล็กน้อยจากตัวอย่างที่ให้ไว้ในคำถาม แต่อย่างไรก็ตามฉันหวังว่าบางคนจะพบว่ามีประโยชน์


1

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

ตัวอย่างเช่น:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)

2
ที่อยู่ไม่จำเป็นต้องเปลี่ยน / ผนวกสิ่งที่อยู่messageในข้อยกเว้นเดิม (แต่อาจได้รับการแก้ไขฉันคิดว่า)
martineau

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