Python «поднять от» использования

197

В чем разница между raiseи raise fromв Python?

try:
    raise ValueError
except Exception as e:
    raise IndexError

что дает

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

и

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

что дает

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
источник
9
Вы читали PEP-3134 ?
Джоншарп
4
Теперь используйте raise IndexError from None, скажем.
Мартин Питерс
11
Хех. raise IndexError from FalseВстает, а TypeErrorне IndexError. Сделал мой день.
Безумный физик
Связанный: В чем разница между __cause__ и __context__?
Соломон Уцко
Не уверен, что это правильное место, чтобы упомянуть об этом, но всем, кто использует Spyder: вся эта конструкция там не работает. Это проблема уже более 3 лет ( github.com/spyder-ide/spyder/issues/2943 ), но, похоже, они считают, что нет необходимости в связанных исключениях.
Эмиль Боде

Ответы:

230

Разница заключается в том, что при использовании from, то __cause__атрибут установлен , и сообщение заявляет , что исключение было непосредственно вызвано . Если вы опускаете, fromтогда no __cause__устанавливается, но __context__атрибут также может быть установлен, и затем трассировка отображает контекст, как во время обработки чего-то еще произошло .

Установка __context__происходит, если вы использовали raiseв обработчике исключений; если вы использовали raiseгде-либо еще, то не __context__устанавливается.

Если __cause__установлено, __suppress_context__ = Trueфлаг также установлен на исключение; когда __suppress_context__установлено значение True, __context__при печати трассировки игнорируется.

При вызове из обработчика исключений, когда вы не хотите показывать контекст (не хотите во время обработки другого сообщения об исключении, произошедшем ), используйте raise ... from Noneдля установки __suppress_context__значение True.

Другими словами, Python устанавливает контекст для исключений, чтобы вы могли проанализировать, где возникло исключение, позволяя увидеть, было ли заменено другое исключение. Вы также можете добавить причину в исключение, сделав трассировку явной для другого исключения (используйте другую формулировку), и контекст будет проигнорирован (но все еще может подвергаться самоанализу при отладке). Использование raise ... from Noneпозволяет подавить контекст печати.

Смотрите документацию raiseзаявления :

Предложение fromиспользуется для цепочки исключений: если дано, второе выражение должно быть другим классом или экземпляром исключения, который затем будет присоединен к возбужденному исключению в качестве __cause__атрибута (который доступен для записи). Если возникшее исключение не обработано, будут напечатаны оба исключения:

>>> 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

Подобный механизм работает неявно, если исключение вызывается внутри обработчика исключения или finallyпредложения: предыдущее исключение затем присоединяется как __context__атрибут нового исключения :

>>> 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

Также см. Документацию « Встроенные исключения» для получения подробной информации о контексте и информации о причинах, прикрепленной к исключениям.

Мартейн Питерс
источник
11
Есть ли какая-либо причина для явного связывания исключений с помощью fromи __cause__вместо неявных __context__? Есть ли случаи, когда кто-то может присоединить исключение, отличное от того, которое поймал except?
даркфелайн
13
@darkfeline: допустим, ваш API баз данных поддерживает открытие баз данных из различных источников, включая Интернет и диск. Ваш API всегда будет вызывать, DatabaseErrorесли открытие базы данных завершится неудачно. Но если сбой является результатом того, IOErrorчто файл не удалось открыть, или HTTPErrorURL-адреса не работает, то это контекст, который вы хотите явно включить, поэтому разработчик, использующий API, может отладить, почему это так. В этот момент вы используете raise DatabaseError from original_exception.
Мартин Питерс
4
@darkfeline: Если разработчик оборачивает использование API базы данных в их собственных API и хотел передать , что IOErrorи HTTPErrorна их потребителей, то они должны использовать raise NewException from databaseexception.__cause__, в настоящее время используют различные исключения из , DatabaseExceptionчто они просто поймали.
Мартин Питерс
2
@ дан3: нет, нет. Цепочка исключений - это просто особенность Python 3.
Мартин Питерс
5
@ laike9m: вы имеете в виду, когда обрабатываете исключение fooи хотите создать новое исключение bar? Тогда вы можете использовать raise bar from fooи иметь состояние Python, которое foo вызвано напрямуюbar . Если вы не используете from foo, то Python будет по- прежнему печатать как, но утверждают , что во время обращения foo, barбыл поднят , другое сообщение, предназначенное для флага возможную ошибку в обработке ошибок.
Мартин Питерс