Yuvalanmış deneme / hariç bloklarda bir istisna nasıl yeniden oluşturulur?


112

Bir istisnayı yeniden yükseltmek istersem raise, ilgili exceptblokta bağımsız değişkenler olmadan basit kullanım yapacağımı biliyorum . Ama iç içe geçmiş bir ifade verildiğinde

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

SomeErroryığın izini bozmadan nasıl yeniden yükseltebilirim ? raisetek başına bu durumda daha yeni olanı yeniden yükseltecekti AlsoFailsError. Veya bu sorunu önlemek için kodumu nasıl yeniden düzenleyebilirim?


2
Başarıya ve istisnaya plan_Bgeri dönen başka bir işlev koymayı denediniz mi? Sonra dış blok sadece olabilirTrueFalseexceptif not try_plan_B(): raise
Drew McGowen

@DrewMcGowen Maalesef daha gerçekçi durum bu keyfi nesneler kabul eden bir işlev içinde olmasıdır argve arama çalışacaktı arg.plan_B()bir doğurabilir AttributeErrornedeniyle argbir B planı sağlamıyor
Tobias Kienzler


@Paco Teşekkürler, yapacağım ( Bir cevap daha basit bir yol
gösterse de

@DrewMcGowen Yorumunuza dayanarak bir cevap yazdım , bu da user4815162342'nin cevabından daha az pitonik görünüyor . Ama bu benim de bir geri dönüş değerine sahip plan_B
olmayı istememden

Yanıtlar:


133

Python 3 itibariyle, geri izleme istisnada saklanır, bu nedenle basit raise e(çoğunlukla) doğru olanı yapar:

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

Üretilen traceback SomeError, işleme sırasında AlsoFailsError( raise eiçeride olduğu için except AlsoFailsError) meydana gelen ek bir bildirimi içerecektir . Bu yanıltıcıdır çünkü gerçekte olan tam tersidir - AlsoFailsErroriyileşmeye çalışırken karşılaştık ve hallettik SomeError. İçermeyen bir Traceback elde etmek AlsoFailsErroryerine raise esahip raise e from None.

Python 2'de, istisna türünü, değerini ve geri dönüşü yerel değişkenlerde saklar ve aşağıdaki üç argüman biçiminiraise kullanırsınız :

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

Mükemmel, ben de burada bulduğum şey bu , teşekkürler! Her ne kadar öneri raise self.exc_info[1], None, self.exc_info[2]sonra self.exc_info = sys.exc_info()- her [1]nedense ilk sıraya koyma
Tobias Kienzler

3
@TobiasKienzler raise t, None, tb, istisnanın değerini kaybedecek ve istisnayı raisetürden yeniden başlatmaya zorlayarak size daha az spesifik (veya sadece yanlış) bir istisna değeri verecektir . Örneğin, ortaya çıkarılan istisna ise KeyError("some-key"), KeyError()geriye dönük tam eksik anahtarı yeniden yükseltir ve çıkarır.
user4815162342

3
@TobiasKienzler Bunu Python 3'te olarak ifade etmek hala mümkün olmalı raise v.with_traceback(tb). (Değerin yeniden somutlaştırılmasını önermesi dışında yorumunuz bile çok şey söylüyor.)
user4815162342

2
Ayrıca, sys.exc_info()yerel bir değişkende saklanmaması için kırmızı uyarı Python 2.0'dan önce (13 yıl önce piyasaya sürüldü) mantıklıydı, ancak bugün gülünç sınırlar var. Önemsiz olmayan her Python kütüphanesi, döngüleri duraklamadan oluşturduğundan ve bunların doğru temizlenmesine bağlı olduğundan, modern Python döngü toplayıcı olmadan neredeyse yararsız olurdu.
user4815162342

1
@ user4815162342 "e'yi Yoktan yükselt" yazarak "başka bir hata oluştu" iç içe geçmiş hatayı ortadan kaldırabilirsiniz.
Matthias Urlichs

19

Kabul edilen çözüm doğru olsa bile, Python 2 + 3 çözümüne sahip Six kitaplığına six.reraise.

altı. yeniden oluşturma ( exc_type , exc_value , exc_traceback = Yok)

Muhtemelen farklı bir geri dönüşle bir istisnayı yeniden oluşturun. [...]

Yani yazabilirsiniz:

import six


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

1
İyi bir nokta - Altı'dan bahsetmişken, aynı zamanda başarısız olan six.raise_frombilgileri dahil etmek istiyorsanız da kullanabilirsiniz plan_B().
Tobias Kienzler

1
@TobiasKienzler: Bunun farklı bir kullanım olduğunu düşünüyorum: bir öncekiyle six.raise_frombağlantılı yeni bir istisna yaratırsanız , yeniden yükseltmezsiniz , bu nedenle geriye dönük izleme farklı olur.
Laurent LAPORTE

1
Demek istediğim tam olarak - eğer reraisesadece something()attığı izlenimi SomeErroralırsanız , bunun idam edilmesine raise_fromneden olduğunu da bilirseniz, plan_B()ancak AlsoFailsError. Yani kullanım durumuna göre değişir. Sanırım raise_fromhata ayıklamayı kolaylaştıracak
Tobias Kienzler

9

Gereğince Drew McGowen önerisi , ama (bir geri dönüş değeri genel durumda bakımı smevcutsa), burada bir alternatif var user4815162342 cevabı :

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
Bu yaklaşımın güzel yanı Python 2 ve 3'te değişmeden çalışması.
user4815162342

2
@ user4815162342 İyi bir nokta :) Bu arada Python3'te dikkate alsam raise fromda, yığın izleme B planının başarısız olmasına da izin verir. Bu arada Python 2'de taklit edilebilir .
Tobias Kienzler

5

Python 3.5+, geri izleme bilgilerini yine de hataya ekler, bu nedenle artık ayrı ayrı kaydetmek gerekmez.

>>> 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
Soru, sırasında meydana gelen başka bir istisna hakkındadır except. Ama haklısın, err = ediyelim ki yerine koyduğumda, raise AttributeErrorönce SyntaxErroryığın izini, ardından a During handling of the above exception, another exception occurred:ve yığın izini alırsın AttributeError. Bilmekte fayda var, ancak maalesef 3.5 + 'nın yüklenmesine güvenilemez. Not: ff verstehen nicht-Deutsche vermutlich nicht;)
Tobias Kienzler

Tamam, bu yüzden örneği başka bir istisna oluşturacak şekilde değiştirdim, bu da (asıl soru sorulduğu üzere) ilkini tekrar yükselttiğimde yok sayılıyor.
Matthias Urlichs

3
@TobiasKienzler 3.5+ (değiştirdim) küresel olarak tanınan bir format gibi görünüyor. Denkst du muydu? ;)
linusg

@linusg Kabul edildi :)
Tobias Kienzler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.