Làm cách nào để nâng lại một ngoại lệ trong các khối try / exception lồng nhau?


106

Tôi biết rằng nếu tôi muốn nâng lại một ngoại lệ, tôi sử dụng đơn giản raisemà không có đối số trong exceptkhối tương ứng . Nhưng với một biểu thức lồng nhau như

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

làm thế nào tôi có thể nâng cao lại SomeErrormà không phá vỡ dấu vết ngăn xếp? raisemột mình trong trường hợp này sẽ tăng lại gần đây hơn AlsoFailsError. Hoặc làm cách nào để cấu trúc lại mã của mình để tránh vấn đề này?


2
Bạn đã thử đưa plan_Bvào một hàm khác trả Truevề thành công và Falsengoại lệ chưa? Sau đó, exceptkhối bên ngoài chỉ có thể làif not try_plan_B(): raise
Drew McGowen

@DrewMcGowen trường hợp Thật không may là thực tế hơn là điều này là bên trong một hàm nhận đối tượng tùy ý argvà tôi muốn thử gọi điện thoại arg.plan_B()mà có thể nâng cao một AttributeErrordo argkhông cung cấp một kế hoạch B
Tobias KIENZLER


@Paco Cảm ơn, tôi sẽ (Mặc dù một câu trả lời đã cho thấy một cách đơn giản hơn)
Tobias KIENZLER

@DrewMcGowen Tôi đã viết một câu trả lời dựa trên nhận xét của bạn , câu trả lời này trông ít khó hiểu hơn câu trả lời của người dùng4815162342 . Nhưng đó của do tôi muốn cũng phải có một giá trị trả về và cho phép plan_Bngoại lệ tăng lương
Tobias KIENZLER

Câu trả lời:


127

Kể từ Python 3, dấu vết được lưu trữ trong ngoại lệ, vì vậy một cách đơn giản raise esẽ thực hiện (chủ yếu) đúng:

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

Theo dõi được tạo ra sẽ bao gồm một thông báo bổ sung đã SomeErrorxảy ra trong khi xử lý AlsoFailsError(vì ở raise ebên trong except AlsoFailsError). Điều này gây hiểu lầm bởi vì những gì thực sự xảy ra lại theo cách khác - chúng tôi đã gặp AlsoFailsErrorvà xử lý nó, trong khi cố gắng khôi phục SomeError. Để có được một dấu vết không bao gồm AlsoFailsError, hãy thay thế raise ebằng raise e from None.

Trong Python 2, bạn sẽ lưu trữ kiểu ngoại lệ, giá trị và truy xuất nguồn gốc trong các biến cục bộ và sử dụng dạng ba đối số củaraise :

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

Hoàn hảo, đó là những gì tôi cũng tìm thấy ở đây , cảm ơn! Mặc dù có gợi ý là raise self.exc_info[1], None, self.exc_info[2]sau self.exc_info = sys.exc_info()- đưa [1]đến vị trí đầu tiên đối với một số lý do
Tobias KIENZLER

3
@TobiasKienzler raise t, None, tbsẽ mất giá trị của ngoại lệ và sẽ buộc raisephải khởi tạo lại nó từ kiểu, cung cấp cho bạn giá trị ngoại lệ ít cụ thể hơn (hoặc đơn giản là không chính xác). Ví dụ: nếu ngoại lệ được nêu ra là KeyError("some-key"), nó sẽ chỉ nâng lên lại KeyError()và loại bỏ khóa bị thiếu chính xác khỏi truy xuất.
user4815162342

3
@TobiasKienzler Vẫn có thể diễn đạt điều đó trong Python 3 dưới dạng raise v.with_traceback(tb). (Cảm nhận của bạn thậm chí còn nói càng nhiều, ngoại trừ nó đề nghị tái thuyết minh giá trị.)
user4815162342

2
Ngoài ra, cảnh báo màu đỏ không lưu trữ sys.exc_info()trong một biến cục bộ đã có ý nghĩa trước Python 2.0 (được phát hành cách đây 13 năm), nhưng lại trở nên vô lý ngày nay. Python hiện đại sẽ gần như vô dụng nếu không có bộ thu thập chu trình, vì mọi thư viện Python không tầm thường đều tạo ra các chu trình mà không cần tạm dừng và phụ thuộc vào việc dọn dẹp chính xác của chúng.
dùng4815162342

1
@ user4815162342 Bạn có thể loại bỏ lỗi lồng nhau "đã xảy ra lỗi khác" bằng cách viết "raise e from None".
Matthias Urlichs

19

Ngay cả khi giải pháp được chấp nhận là đúng, bạn nên trỏ đến thư viện Six có giải pháp Python 2 + 3 bằng cách sử dụng six.reraise.

sáu. reraise ( exc_type , exc_value , exc_traceback = None)

Đánh giá lại một trường hợp ngoại lệ, có thể bằng một dấu vết khác. [...]

Vì vậy, bạn có thể viết:

import six


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

1
Điểm tốt - nói về Six, bạn cũng có thể sử dụng six.raise_fromnếu bạn muốn bao gồm thông tin plan_B()cũng không thành công.
Tobias Kienzler

1
@TobiasKienzler: Tôi nghĩ đó là một cách sử dụng khác: với việc six.raise_frombạn tạo một ngoại lệ mới được liên kết với một ngoại lệ trước đó, bạn không nâng cấp lại , vì vậy dấu vết trở lại sẽ khác.
Laurent LAPORTE

1
Chính xác thì quan điểm của tôi - nếu bạn reraisenhận được ấn tượng chỉ được something()ném SomeError, nếu raise_frombạn cũng biết rằng điều này gây ra plan_B()được thực hiện nhưng ném AlsoFailsError. Vì vậy, nó phụ thuộc vào usecase. Tôi nghĩ rằng raise_fromsẽ làm cho gỡ lỗi dễ dàng hơn
Tobias KIENZLER

9

Theo gợi ý của Drew McGowen , nhưng quan tâm đến trường hợp chung (trong đó giá trị trả về scó mặt), đây là một giải pháp thay thế cho câu trả lời của người dùng4815162342 :

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
Những điều tốt đẹp về phương pháp này là nó hoạt động không thay đổi trong Python 2 và 3.
user4815162342

2
@ user4815162342 Điểm tốt :) Mặc dù trong khi đó tôi sẽ xem xét trong Python3 raise from, vì vậy dấu vết ngăn xếp cũng sẽ cho phép tôi kế hoạch B thất bại. Nhân tiện, có thể mô phỏng bằng Python 2 .
Tobias Kienzler

5

Python 3.5+ vẫn đính kèm thông tin truy xuất vào lỗi, vì vậy không còn cần thiết phải lưu riêng.

>>> 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
Câu hỏi là về một ngoại lệ khác xảy ra trong except. Nhưng bạn nói đúng, khi tôi thay thế err = ebởi, chẳng hạn, raise AttributeErrorbạn có được đầu tiên trên SyntaxErrorstack trace, tiếp theo là một During handling of the above exception, another exception occurred:AttributeErrorvết đống. Điều tốt để biết, mặc dù rất tiếc là người ta không thể dựa vào 3.5+ được cài đặt. Tái bút: ff verstehen nicht-Deutsche vermutlich nicht;)
Tobias Kienzler

OK, vì vậy tôi đã thay đổi ví dụ để nêu ra một ngoại lệ khác, ngoại lệ này (như câu hỏi ban đầu được yêu cầu) sẽ bị bỏ qua khi tôi nêu lại ngoại lệ đầu tiên.
Matthias Urlichs

3
@TobiasKienzler 3.5+ (mà tôi đã đổi thành) dường như là một định dạng được công nhận trên toàn cầu. Là denkst du? ;)
linusg

@linusg Đồng ý :)
Tobias Kienzler
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.