Wykorzystanie Pythona do „podniesienia z”

197

Jaka jest różnica między raisei raise fromw Pythonie?

try:
    raise ValueError
except Exception as e:
    raise IndexError

co daje

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError
IndexError

i

try:
    raise ValueError
except Exception as e:
    raise IndexError from e

co daje

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

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

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError from e
IndexError
Darkfeline
źródło
9
Czy czytałeś PEP-3134 ?
jonrsharpe
4
Teraz użyj raise IndexError from None, powiedzmy.
Martijn Pieters
11
Heh raise IndexError from Falsepodnosi a TypeErrornie an IndexError. Zrobił mi dzień.
Mad Physicist
Nie jestem pewien, czy jest to właściwe miejsce, aby o tym wspomnieć, ale dla każdego, kto używa Spyder: Cały konstrukt tam nie działa. Jest to problem od ponad 3 lat ( github.com/spyder-ide/spyder/issues/2943 ), ale wydaje się, że uważają, że nie ma potrzeby łańcuchowych wyjątków.
Emil Bode

Odpowiedzi:

230

Różnica polega na tym, że gdy używasz from, __cause__atrybut jest ustawiony, a komunikat stwierdza, że ​​wyjątek był bezpośrednio spowodowany . Jeśli pominiesz, fromto nie __cause__jest ustawione, ale __context__atrybut może być również ustawiony, a traceback pokazuje kontekst, jak podczas obsługi czegoś innego .

Ustawienie __context__dzieje się, jeśli użyłeś raisew module obsługi wyjątków; jeśli używałeś raisenigdzie indziej, nie __context__jest ustawione.

Jeśli __cause__ustawiony jest a, __suppress_context__ = Trueflaga jest również ustawiana na wyjątku; gdy __suppress_context__jest ustawiony na True, to __context__jest ignorowane podczas drukowania śledzenia.

W przypadku zgłaszania zdarzenia z procedury obsługi wyjątków, w której nie chcesz pokazywać kontekstu (nie chcesz, aby podczas obsługi pojawił się inny komunikat o zdarzeniu wyjątku ), użyj przycisku, raise ... from Noneaby ustawić __suppress_context__na True.

Innymi słowy, Python określa kontekst wyjątków, dzięki czemu można introspekcji, gdzie został zgłoszony wyjątek, pozwalając zobaczyć, czy inny wyjątek został przez niego zastąpiony. Możesz także dodać przyczynę do wyjątku, dzięki czemu funkcja śledzenia wyraźnie mówi o innym wyjątku (użyj innego sformułowania), a kontekst jest ignorowany (ale nadal może być introspekcyjny podczas debugowania). Użycie raise ... from Nonepozwala pominąć drukowany kontekst.

Zobacz dokumentację raiseoświadczenia :

fromPunkt stosuje się wyjątek łańcuchowych: jeśli podano, drugie wyrażenie może być inna klasa wyjątkiem lub wystąpienie, co zostanie dołączony do uniesionego wyjątkiem jako __cause__atrybut, (który jest zapisywalny). Jeśli zgłoszony wyjątek nie zostanie obsłużony, zostaną wydrukowane oba wyjątki:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

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

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Podobny mechanizm działa domyślnie, jeśli wyjątek zostanie zgłoszony w procedurze obsługi wyjątku lub w finallyklauzuli: poprzedni wyjątek jest następnie dołączany jako __context__atrybut nowego wyjątku :

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Zobacz także dokumentację Wbudowane wyjątki, aby uzyskać szczegółowe informacje na temat kontekstu i informacje o przyczynie związane z wyjątkami.

Martijn Pieters
źródło
11
Czy jest jakiś powód, aby jawnie łączyć wyjątki za pomocą fromi __cause__zamiast domniemanych __context__? Czy istnieją przypadki, w których można załączyć inny wyjątek niż ten złapany przez except?
darkfeline
13
@darkfeline: Załóżmy, że interfejs API bazy danych obsługuje otwieranie baz danych z różnych źródeł, w tym z sieci i dysku. Twój interfejs API zawsze podniesie, DatabaseErrorjeśli otwarcie bazy danych nie powiedzie się. Ale jeśli awaria jest spowodowana IOErrorniepowodzeniem otwierania pliku lub HTTPErrorniedziałaniem adresu URL, to jest to kontekst, który chcesz jawnie uwzględnić, więc programista korzystający z interfejsu API może debugować, dlaczego tak jest. W tym momencie używasz raise DatabaseError from original_exception.
Martijn Pieters
4
@darkfeline: Jeśli deweloper jest owijanie wykorzystanie API bazy danych we własnym API i chciał przejść na tym IOErrorlub HTTPErrorna ich konsumentów, wtedy musieliby użytku raise NewException from databaseexception.__cause__, teraz za pomocą innego wyjątku od DatabaseExceptionże po prostu złapać.
Martijn Pieters
2
@ dan3: nie, nie ma. Łączenie wyjątków jest czysto funkcją Pythona 3.
Martijn Pieters
5
@ laike9m: masz na myśli, kiedy obsługujesz wyjątek fooi chcesz zgłosić nowy wyjątek bar? Następnie możesz użyć raise bar from fooi ustawić stan Python, który foo bezpośrednio spowodowałbar . Jeśli nie używać from foo, a następnie Python będzie nadal drukować zarówno, ale stwierdzają, że podczas przenoszenia foo, barzostał podniesiony , inny komunikat, przeznaczony do flaga możliwy błąd w obsłudze błędów.
Martijn Pieters