Mevcut bilgileri koruyarak farklı bir tür ve mesajla istisnayı yeniden yükseltin


139

Bir modül yazıyorum ve (örneğin FooErrortüm foomodülün özel istisnaları için soyut bir sınıftan miras) yükseltebilir istisnalar için birleşik bir istisna hiyerarşisi istiyorum . Bu, modül kullanıcılarının bu özel durumları yakalamasına ve gerektiğinde ayrı ayrı işlemesine olanak tanır. Ancak modülden yapılan istisnaların birçoğu başka bir istisna nedeniyle ortaya çıkar; örneğin, bir dosyadaki OSError nedeniyle bazı görevlerde başarısız olma.

Ihtiyacım ne farklı bir tür ve mesaj olacak şekilde yakalanan istisna "sarmak" , böylece bilgi istisna yakalayan ne tarafından yayılma hiyerarşisinde daha da kullanılabilir. Ama var olan türü, mesajı ve yığın izini kaybetmek istemiyorum; bu, sorunu ayıklamaya çalışan biri için faydalı bilgiler. Bir üst düzey istisna işleyicisi iyi değil, çünkü yayılım yığınını daha da ileriye götürmeden önce istisnayı süslemeye çalışıyorum ve üst düzey işleyici çok geç.

Bu kısmen, modülümün fooözel istisna türlerini mevcut türden (örn. class FooPermissionError(OSError, FooError)) Türeterek çözülür , ancak bu, mevcut istisna örneğini yeni bir türe kaydırmayı veya mesajı değiştirmeyi kolaylaştırmaz.

Python'un PEP 3134 “İstisna Zinciri ve Gömülü Geri İzlemeler”, mevcut bir istisnanın işlenmesi sırasında yeni bir istisnanın ortaya çıktığını göstermek için Python 3.0'da “zincirleme” istisna nesneleri için kabul edilen bir değişikliği tartışır.

Yapmaya çalıştığım şey şu: Daha önceki Python sürümlerinde de çalışmam gerekiyor ve zincirleme için değil, sadece polimorfizm için ihtiyacım var. Bunu yapmanın doğru yolu nedir?


İstisnalar zaten tamamen polimorfiktir - hepsi İstisna'nın alt sınıflarıdır. Ne yapmaya çalışıyorsun? Bir üst düzey istisna işleyicisi ile "farklı mesaj" oldukça önemsizdir. Neden sınıfı değiştiriyorsun?
S.Lott

Soruda açıklandığı gibi (şimdi, yorumunuz için teşekkürler): Yakaladığım bir istisnayı süslemeye çalışıyorum, böylece daha fazla bilgi ile daha fazla yayılabilir, ancak herhangi bir kaybetmeden. Üst düzey bir işleyici çok geç.
bignose

Lütfen Python 2.x'te istediğinizi yapabileceğiniz CausedException sınıfına bir göz atın . Ayrıca Python 3'te, istisnanızın nedeni olarak birden fazla orijinal istisna vermek istemeniz durumunda kullanılabilir. Belki de ihtiyaçlarınızı karşılar.
Alfe


Python-2 için @DevinJeanpierre benzer bir şey yapmak ama ben sadece yeni bir dize mesajı ekliyorum: except Exception as e-> raise type(e), type(e)(e.message + custom_message), sys.exc_info()[2]-> Bu çözüm başka bir SO sorusundan . Bu güzel değil, işlevsel.
Trevor Boyd Smith

Yanıtlar:


197

Python 3 , istisna zincirleme getirdi ( PEP 3134'te açıklandığı gibi ). Bu, bir istisnayı yükseltirken mevcut bir istisnayı “neden” olarak göstermeye izin verir:

try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc

Böylece yakalanan istisna, yeni istisnanın bir parçası olur (“nedeni” dir) ve yeni istisnayı yakalayan her kod için kullanılabilir.

Bu özelliği kullanarak özellik __cause__ayarlanır. Yerleşik istisna işleyici , istisnanın “nedeni” ve “bağlamının” geri izleme ile birlikte nasıl rapor edileceğini de bilir .


In Python 2 , kullanım örneği (aynı tarif iyi cevabı vardır görünür Ian Bicking ve Ned Batchelder ). Aylak.


4
Ian Bicking benim çözümümü tarif etmiyor mu? Böyle iğrenç bir cevap verdiğim için üzgünüm, ama bunun kabul edilmesi garip.
Devin Jeanpierre

1
@bignose Sadece haklı olmakla kalmayıp, "frobnicate" kullanımı için de benim fikrimi aldınız :)
David M.

5
İstisna zincirleme aslında varsayılan davranıştır, aslında iş gerektiren ilk istisnayı baskılayarak sorunun tam tersidir, bkz. PEP 409 python.org/dev/peps/pep-0409
Chris_Rands

1
Bunu python 2'de nasıl başardınız?
selotape

1
İyi çalışıyor gibi görünüyor (python 2.7)try: return 2 / 0 except ZeroDivisionError as e: raise ValueError(e)
alex

37

Geri izlemeyi almak için sys.exc_info () yöntemini kullanabilir ve söz konusu geri izlemeyle (PEP'in belirttiği gibi) yeni özel durumunuzu yükseltebilirsiniz. Eski türü ve iletiyi korumak istiyorsanız, bunu istisnada yapabilirsiniz, ancak bu yalnızca istisnanızı yakalayan ne varsa ararsa yararlıdır.

Örneğin

import sys

def failure():
    try: 1/0
    except ZeroDivisionError, e:
        type, value, traceback = sys.exc_info()
        raise ValueError, ("You did something wrong!", type, value), traceback

Tabii ki, bu gerçekten kullanışlı değil. Öyle olsaydı, bu PEP'e ihtiyacımız olmazdı. Bunu tavsiye etmem.


Devin, orada geri izlemeye bir referans saklıyorsun, bu referansı açıkça silmemelisin?
Arafangion

2
Hiçbir şey saklamadım, izlemeyi muhtemelen kapsam dışına çıkan yerel bir değişken olarak bıraktım. Evet, öyle düşünülemez, ancak işlevler yerine küresel kapsamda böyle istisnalar ortaya çıkarırsanız, daha büyük sorunlarınız olur. Şikayetiniz yalnızca küresel bir kapsamda yürütülebiliyorsa, uygun çözüm, açıklanması gereken ve kullanımların% 99'uyla ilgili olmayan alakasız bir kazan plakası eklemek değil, çözümü bu şekilde yeniden yazmaktır. sanki benim yaptığım gibi hiçbir şey farklı görünmüyor.
Devin Jeanpierre

4
Arafangion , @Devin için Python belgelerindesys.exc_info() bir uyarıya atıfta bulunabilir. "Bir istisna işleyen bir işlevde yerel değişkene geri izleme dönüş değeri atamak, döngüsel başvuruya neden olur." Bununla birlikte, aşağıdaki not Python 2.2'den bu yana döngünün temizlenebileceğini, ancak bundan kaçınmanın daha verimli olduğunu söylüyor.
Don Kirkby

5
İki aydınlanmış pitondan Python'daki istisnaları yeniden yükseltmenin farklı yolları hakkında daha fazla ayrıntı: Ian Bicking ve Ned Batchelder
Rodrigue

11

Hangi istisnayı yakalarsanız genişleten kendi istisna türünüzü oluşturabilirsiniz .

class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)

Ancak çoğu zaman, istisnayı yakalamak, ele almak ve raiseorijinal istisnayı (ve izlemeyi korumak) ya da daha basit olacağını düşünüyorum raise NewException(). Kodunuzu çağırıyordum ve özel istisnalarınızdan birini aldıysam, kodunuzun yakalamanız gereken istisnaları zaten ele aldığını beklerdim. Bu yüzden kendime erişmem gerekmiyor.

Düzenleme: Ben kendi istisna atmak ve orijinal istisna tutmak yolları bu analizi bulundu . Güzel çözümler yok.


1
Açıkladığım kullanım durumu istisnayı ele almak için değil ; o özellikle ilgili oluyor değil hallediyorum ama çağrı yığını daha yukarı ele alınması, böylece bazı ekstra bilgi (ek sınıf ve yeni bir ileti) ekleyerek.
bignose

2

Ayrıca birçok kez ben bazı "sarma" yükseltilmiş hatalar gerekli bulundu.

Bu, hem bir işlev kapsamına dahil edildi hem de bazen bir işlevin yalnızca bazı satırlarını sarar.

Kullanılacak bir sarıcı oluşturuldu decoratorve context manager:


uygulama

import inspect
from contextlib import contextmanager, ContextDecorator
import functools    

class wrap_exceptions(ContextDecorator):
    def __init__(self, wrapper_exc, *wrapped_exc):
        self.wrapper_exc = wrapper_exc
        self.wrapped_exc = wrapped_exc

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            return
        try:
            raise exc_val
        except self.wrapped_exc:
            raise self.wrapper_exc from exc_val

    def __gen_wrapper(self, f, *args, **kwargs):
        with self:
            for res in f(*args, **kwargs):
                yield res

    def __call__(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            with self:
                if inspect.isgeneratorfunction(f):
                    return self.__gen_wrapper(f, *args, **kw)
                else:
                    return f(*args, **kw)
        return wrapper

Kullanım örnekleri

dekoratör

@wrap_exceptions(MyError, IndexError)
def do():
   pass

doyöntemi çağırırken , endişelenme IndexError, sadeceMyError

try:
   do()
except MyError as my_err:
   pass # handle error 

içerik yöneticisi

def do2():
   print('do2')
   with wrap_exceptions(MyError, IndexError):
       do()

içeride do2, içinde context managereğer IndexErroryükseltilir, bu sarılmış ve yükseltilecekMyError


1
Lütfen "sarma" nın orijinal istisna için ne yapacağını açıklayın. Kodunuzun amacı nedir ve hangi davranışı etkinleştirir?
alexis

@alexis - bazı örnekler ekledi, umarım yardımcı olur
Aaron_ab

-2

İhtiyaçlarınıza en hızlı çözüm şu olmalıdır:

try:
     upload(file_id)
except Exception as upload_error:
     error_msg = "Your upload failed! File: " + file_id
     raise RuntimeError(error_msg, upload_error)

Bu şekilde daha sonra mesajınızı ve yükleme fonksiyonu tarafından atılan belirli hatayı yazdırabilirsiniz


1
Bu , istisna nesnesini yakalar ve atar , bu nedenle hayır, sorunun ihtiyaçlarını karşılamaz. Soru nasıl sorar tutmak yığın anlatmaya devam, tüm, içerdiği yararlı bilgiler aynı durum mevcut durum ve izin verir.
bignose
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.