จะเพิ่มข้อยกเว้นอีกครั้งในบล็อก try / except แบบซ้อนได้อย่างไร


112

ฉันรู้ว่าถ้าฉันต้องการเพิ่มข้อยกเว้นอีกครั้งฉันใช้ง่ายๆraiseโดยไม่มีข้อโต้แย้งในexceptบล็อกที่เกี่ยวข้อง แต่ได้รับการแสดงออกที่ซ้อนกันเช่น

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # I'd like to raise the SomeError as if plan_B()
                 # didn't raise the AlsoFailsError

ฉันจะเพิ่มอีกครั้งSomeErrorโดยไม่ทำลายสแต็กเทรซได้อย่างไร เพียงอย่างเดียวจะในกรณีนี้อีกครั้งเพิ่มมากขึ้นล่าสุดraise AlsoFailsErrorหรือฉันจะ refactor รหัสของฉันเพื่อหลีกเลี่ยงปัญหานี้ได้อย่างไร


2
คุณได้ลองใส่plan_Bฟังก์ชั่นอื่นที่ให้ผลTrueสำเร็จFalseหรือไม่? จากนั้นexceptบล็อกด้านนอกอาจเป็นif not try_plan_B(): raise
Drew McGowen

@DrewMcGowen น่าเสียดายที่กรณีที่เป็นจริงมากขึ้นคือมันอยู่ในฟังก์ชั่นที่ยอมรับวัตถุโดยพลการargและฉันจะลองโทรarg.plan_B()ซึ่งอาจเพิ่มขึ้นAttributeErrorเนื่องจากargไม่ได้ให้แผน B
Tobias Kienzler


@Paco ขอบคุณฉันจะ (แม้ว่าคำตอบจะแสดงวิธีที่ง่ายกว่าอยู่แล้ว)
Tobias Kienzler

@DrewMcGowen ผมเขียนขึ้นคำตอบขึ้นอยู่กับความคิดเห็นของคุณซึ่งมีลักษณะ pythonic น้อยกว่าคำตอบ user4815162342 ของแต่ แต่นั่นเป็นเพราะฉันต้องการที่จะมีมูลค่าคืนและอนุญาตให้plan_Bเพิ่มข้อยกเว้น
Tobias Kienzler

คำตอบ:


133

เนื่องจาก Python 3 การย้อนกลับจะถูกเก็บไว้ในข้อยกเว้นดังนั้นสิ่งง่ายๆraise eจะทำสิ่งที่ถูกต้อง (ส่วนใหญ่):

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # or raise e from None - see below

traceback ที่ผลิตจะมีการแจ้งให้ทราบเพิ่มเติมที่SomeErrorเกิดขึ้นในขณะที่การจัดการAlsoFailsError(เพราะraise eเป็นภายในexcept AlsoFailsError) นี้เป็นความเข้าใจผิดเพราะสิ่งที่เกิดขึ้นจริงเป็นวิธีอื่น ๆ - เราพบและจัดการมันในขณะที่พยายามที่จะกู้คืนจากAlsoFailsError SomeErrorหากต้องการรับการตรวจสอบย้อนกลับที่ไม่มีAlsoFailsErrorให้แทนที่raise eด้วยraise e from None.

ใน Python 2 คุณจะจัดเก็บประเภทข้อยกเว้นค่าและการย้อนกลับในตัวแปรโลคัลและใช้รูปแบบสามอาร์กิวเมนต์ของraise :

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb

สมบูรณ์แบบนั่นคือสิ่งที่ฉันเพิ่งพบที่นี่ขอบคุณ! แม้ว่าจะมีข้อเสนอแนะคือraise self.exc_info[1], None, self.exc_info[2]หลังจากself.exc_info = sys.exc_info()- วาง[1]ให้อยู่ในตำแหน่งแรกด้วยเหตุผลบางอย่าง
โทเบียส KIENZLER

3
@TobiasKienzler raise t, None, tbจะสูญเสียค่าของข้อยกเว้นและจะบังคับraiseให้สร้างอินสแตนซ์ใหม่จากประเภทดังกล่าวทำให้คุณมีค่าข้อยกเว้นที่เฉพาะเจาะจงน้อยลง (หรือไม่ถูกต้อง) ตัวอย่างเช่นหากมีข้อยกเว้นที่ยกขึ้นข้อยกเว้นKeyError("some-key")จะเพิ่มขึ้นอีกครั้งKeyError()และละเว้นคีย์ที่ขาดหายไปจากการย้อนกลับ
user4815162342

3
@TobiasKienzler ยังคงเป็นไปได้ที่จะแสดงสิ่งนั้นใน Python 3 เป็นraise v.with_traceback(tb). (ความคิดเห็นของคุณพูดได้มาก แต่เสนอให้สร้างอินสแตนซ์ค่าอีกครั้ง)
user4815162342

2
นอกจากนี้คำเตือนสีแดงที่ไม่ให้เก็บไว้sys.exc_info()ในตัวแปรท้องถิ่นนั้นสมเหตุสมผลก่อน Python 2.0 (เปิดตัวเมื่อ 13 ปีที่แล้ว) แต่วันนี้ไร้สาระ Python สมัยใหม่จะไร้ประโยชน์โดยไม่ต้องใช้ตัวรวบรวมวงจรเนื่องจากไลบรารี Python ที่ไม่สำคัญทุกแห่งจะสร้างวงจรโดยไม่ต้องหยุดชั่วคราวและขึ้นอยู่กับการล้างข้อมูลที่ถูกต้อง
user4815162342

1
@ user4815162342 คุณสามารถฆ่า "ข้อผิดพลาดอื่นที่เกิดขึ้น" ข้อผิดพลาดที่ซ้อนกันโดยการเขียน "เพิ่ม e จากไม่มี"
Matthias Urlichs

19

แม้ว่าโซลูชันที่ยอมรับจะถูกต้อง แต่ก็ควรชี้ไปที่ไลบรารีSixซึ่งมีโซลูชัน Python 2 + 3 โดยใช้six.reraise.

หก. reraise ( exc_type , exc_value , exc_traceback = ไม่มี)

ประเมินข้อยกเว้นใหม่อาจมีการย้อนกลับที่แตกต่างกัน [... ]

ดังนั้นคุณสามารถเขียน:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)

1
จุดดี - การพูดถึง Six คุณสามารถใช้six.raise_fromหากคุณต้องการรวมข้อมูลที่plan_B()ล้มเหลวด้วย
Tobias Kienzler

1
@TobiasKienzler: ฉันคิดว่ามันเป็นการใช้งานที่แตกต่างกัน: เมื่อsix.raise_fromคุณสร้างข้อยกเว้นใหม่ซึ่งเชื่อมโยงกับข้อก่อนหน้าคุณจะไม่เพิ่มขึ้นใหม่ดังนั้นการย้อนกลับจึงแตกต่างกัน
Laurent LAPORTE

1
จุดของฉันว่า - ถ้าคุณreraiseคุณจะได้รับความประทับใจเพียงsomething()โยนSomeErrorถ้าคุณraise_fromคุณยังรู้ว่านี้เกิดจากการplan_B()ที่จะดำเนินการ AlsoFailsErrorแต่การขว้างปา ดังนั้นจึงขึ้นอยู่กับ usecase ฉันคิดว่าraise_fromจะทำให้การดีบักง่ายขึ้น
Tobias Kienzler

9

ตามคำแนะนำของ Drew McGowenแต่การดูแลกรณีทั่วไป (ซึ่งมีค่าส่งคืนsอยู่) นี่เป็นทางเลือกสำหรับคำตอบของ user4815162342 :

try:
    s = something()
except SomeError as e:
    def wrapped_plan_B():
        try:
            return False, plan_B()
        except:
            return True, None
    failed, s = wrapped_plan_B()
    if failed:
        raise

1
สิ่งที่ดีเกี่ยวกับแนวทางนี้คือการทำงานไม่เปลี่ยนแปลงใน Python 2 และ 3
user4815162342

2
@ user4815162342 จุดดี :) แม้ว่าใน Python3 ฉันจะพิจารณาraise fromดังนั้นการติดตามสแต็กก็ทำให้ฉันวางแผน B ล้มเหลว ซึ่งสามารถจำลองได้ใน Python 2โดยวิธีการ
Tobias Kienzler

5

Python 3.5+ จะแนบข้อมูลการย้อนกลับไปยังข้อผิดพลาดดังนั้นจึงไม่จำเป็นต้องบันทึกแยกกันอีกต่อไป

>>> def f():
...   try:
...     raise SyntaxError
...   except Exception as e:
...     err = e
...     try:
...       raise AttributeError
...     except Exception as e1:
...       raise err from None
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in f
  File "<stdin>", line 3, in f
SyntaxError: None
>>> 

2
คำถามเกี่ยวกับข้อยกเว้นอื่นที่เกิดขึ้นระหว่างexcept. แต่คุณพูดถูกเมื่อฉันแทนที่err = eด้วยพูดว่าraise AttributeErrorคุณจะได้รับการSyntaxErrorติดตามสแต็กก่อนตามด้วย a During handling of the above exception, another exception occurred:และAttributeErrorสแต็กแทร็ก สิ่งที่ควรทราบแม้ว่าจะไม่สามารถพึ่งพา 3.5+ ที่ติดตั้งได้ PS: ff verstehen nicht-Deutsche vermutlich nicht;)
Tobias Kienzler

ตกลงดังนั้นฉันจึงเปลี่ยนตัวอย่างเพื่อเพิ่มข้อยกเว้นอื่นซึ่ง (ตามที่คำถามเดิมถาม) จะถูกเพิกเฉยเมื่อฉันเพิ่มข้อแรกอีกครั้ง
Matthias Urlichs

3
@TobiasKienzler 3.5+ (ซึ่งฉันเปลี่ยนเป็น) ดูเหมือนจะเป็นรูปแบบที่รู้จักกันทั่วโลก เป็น denkst du? ;)
linusg

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