2013-05-07 30 views
57

Czy istnieje standardowy sposób korzystania z łańcuchów wyjątków w języku Python? Podobnie jak wyjątek Java "spowodowany"?Łańcuch wyjątków w języku Python

Oto niektóre tła.

Mam moduł z jednej głównej klasy wyjątków DSError:

class DSError(Exception): 
    pass 

Gdzieś w tym module nie będzie:

try: 
    v = my_dict[k] 
    something(v) 
except KeyError as e: 
    raise DSError("no key %s found for %s" % (k, self)) 
except ValueError as e: 
    raise DSError("Bad Value %s found for %s" % (v, self)) 
except DSError as e: 
    raise DSError("%s raised in %s" % (e, self)) 

Zasadniczo ten fragment powinien rzucać tylko DSError i powiedz mi, co się stało i czemu. Chodzi o to, że blok try może rzucać wiele innych wyjątków, więc wolałbym, jeśli mogę zrobić coś takiego:

try: 
    v = my_dict[k] 
    something(v) 
except Exception as e: 
    raise DSError(self, v, e) # Exception chained... 

Jest to standardowy sposób pythonic? Nie widziałem łańcuchów wyjątków w innych modułach, więc jak to zrobić w Pythonie?

+2

Woah, Dzięki @jamylak! Naprawiłeś moje cytaty, zanim je nawet zobaczyłem :-) – Ayman

+0

Tak i musiałem dodać niepotrzebne słowo, ponieważ SO nie pozwoliło mi wprowadzić tych * małych zmian * :( – jamylak

+0

Co chcesz, aby wyjście było? t powiedzieć, czy rzeczywiście chcesz użyć śledzenia stosu oryginalnego wyjątku, czy też chcesz go ukryć i mieć własny wyjątek z pojedynczą wiadomością, która podsumowuje oryginalny wyjątek? – BrenBarn

Odpowiedz

81

Exception chaining jest dostępna tylko w Pythonie 3, gdzie można napisać:

try: 
    v = {}['a'] 
except KeyError as e: 
    raise ValueError('failed') from e 

co przynosi wyjście jak

Traceback (most recent call last): 
    File "t.py", line 2, in <module> 
    v = {}['a'] 
KeyError: 'a' 

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

Traceback (most recent call last): 
    File "t.py", line 4, in <module> 
    raise ValueError('failed') from e 
ValueError: failed 

W większości przypadków nie potrzebujesz nawet from; Python 3 będzie domyślnie pokazują wszystkie wyjątki, które wystąpiły podczas obsługi wyjątków, na przykład:

Traceback (most recent call last): 
    File "t.py", line 2, in <module> 
    v = {}['a'] 
KeyError: 'a' 

During handling of the above exception, another exception occurred: 

Traceback (most recent call last): 
    File "t.py", line 4, in <module> 
    raise ValueError('failed') 
ValueError: failed 

Co można zrobić w Python 2 jest dodanie niestandardowych atrybutów do klasy wyjątków, takich jak:

class MyError(Exception): 
    def __init__(self, message, cause): 
     super(MyError, self).__init__(message + u', caused by ' + repr(cause)) 
     self.cause = cause 

try: 
    v = {}['a'] 
except KeyError as e: 
    raise MyError('failed', e) 
+2

Dla Pythona 2, jeśli chce się zapisać traceback - który jeden_musisz_ - [raise MyError (message + u ', spowodowane przez "+ repr (przyczyna)), None, sys.exc_info() [2]'] (http://stackoverflow.com/a/1350981/281545) –

+1

"W większości przypadków nie potrzebujesz nawet od" Czy masz przykład, gdzie jest potrzebny lub użyteczny? – timgeb

+2

@timgeb [PEP 3134] (https://www.python.org/dev/peps/pep-3134/) ma dwie sytuacje łączenia: jedna, w której kod obsługi błędów powoduje inny wyjątek, a drugi, w którym wyjątek został celowo przetłumaczony na inny wyjątek. Słowo 'od e' służy celowi i zmienia komunikat na wyjściu, jak pokazano w odpowiedzi powyżej. –

4

Czy o to prosisz?

class MyError(Exception): 
    def __init__(self, other): 
     super(MyError, self).__init__(other.message) 

>>> try: 
...  1/0 
... except Exception, e: 
...  raise MyError(e) 
Traceback (most recent call last): 
    File "<pyshell#27>", line 4, in <module> 
    raise MyError(e) 
MyError: division by zero 

Jeśli chcesz zachować oryginalny obiekt wyjątku, z pewnością można to zrobić na własną rękę klasy wyjątek na __init__. Może rzeczywiście chcesz zapisać traceback jak sam obiekt wyjątku nie zapewnia wiele przydatnych informacji o tym, gdzie wystąpił wyjątek:

class MyError(Exception): 
    def __init__(self, other): 
     self.traceback = sys.exc_info() 
     super(MyError, self).__init__(other.message) 

Po tym można uzyskać dostęp do atrybutu swojej wyjątkiem traceback aby uzyskać informacje o oryginale wyjątek. (Python 3 przewiduje już to jako atrybut obiektu wyjątku __traceback__).

+0

Prawie tak, ale uważałem, że to będzie "oszukiwanie", ponieważ pobiera tylko komunikat o łańcuchowym wyjściu, a nie o rzeczywistym obiekcie wyjątku, tzn. nie wiedziałbym, gdzie nastąpił faktyczny podział przez zero, tylko, że został złapany _somewhere_. – Ayman

+0

@Ayman: Zobacz moją zredagowaną odpowiedź.Wystarczy, że złapiesz system śledzenia i go zapiszesz. Jeśli jednak naprawdę chcesz, aby wszystkie informacje z oryginalnego wyjątku pokazywały się w systemie traceback jak w prawdziwym wyjątku, to phihag ma rację, że nie można tego zrobić w Pythonie 2. Trzeba by ręcznie wydrukować stary traceback jako część wiadomości Twojego wyjątku. – BrenBarn

+0

Dzięki. Nie wiedziałem o sys.exc_info(). Zaakceptuję to również jako odpowiedź :-) – Ayman