Penggunaan "bangkit dari" Python

197

Apa perbedaan antara raisedan raise fromdalam Python?

try:
    raise ValueError
except Exception as e:
    raise IndexError

yang menghasilkan

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

dan

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

yang menghasilkan

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
sumber
9
Sudahkah Anda membaca PEP-3134 ?
jonrsharpe
4
Sekarang gunakan raise IndexError from None, katakanlah.
Martijn Pieters
11
Heh. raise IndexError from Falsememunculkan TypeError, bukan IndexError. Membuatku bahagia.
Fisikawan Gila
Tidak yakin apakah ini tempat yang tepat untuk menyebutkannya, tetapi kepada siapa pun yang menggunakan Spyder: Keseluruhan konstruk ini tidak berfungsi di sana. Sudah menjadi masalah selama lebih dari 3 tahun sekarang ( github.com/spyder-ide/spyder/issues/2943 ), tetapi mereka tampaknya berpikir tidak perlu untuk pengecualian berantai.
Emil Bode

Jawaban:

230

Perbedaannya adalah bahwa ketika Anda menggunakan from, __cause__atribut ditetapkan dan pesan menyatakan bahwa pengecualian itu secara langsung disebabkan oleh . Jika Anda menghilangkan frommaka tidak __cause__diatur, tetapi __context__atribut dapat diatur juga, dan traceback kemudian menunjukkan konteks saat menangani sesuatu yang lain terjadi .

Pengaturan __context__terjadi jika Anda menggunakan raisepenangan pengecualian; jika Anda digunakan di raisetempat lain tidak ada __context__yang diatur.

Jika a __cause__diatur, __suppress_context__ = Truebendera juga diatur pada pengecualian; ketika __suppress_context__diatur ke True, __context__diabaikan ketika mencetak traceback.

Ketika mengangkat dari penangan pengecualian di mana Anda tidak ingin menampilkan konteks (tidak ingin selama menangani pengecualian terjadi pesan lain), kemudian gunakan raise ... from Noneuntuk mengatur __suppress_context__ke True.

Dengan kata lain, Python menetapkan konteks pada pengecualian sehingga Anda dapat mengintrospeksi di mana pengecualian muncul, membiarkan Anda melihat apakah pengecualian lain digantikan olehnya. Anda juga dapat menambahkan penyebab ke pengecualian, membuat traceback eksplisit tentang pengecualian lain (menggunakan kata-kata yang berbeda), dan konteksnya diabaikan (tetapi masih dapat diintrospeksi saat debugging). Menggunakan raise ... from Nonememungkinkan Anda menekan konteks yang sedang dicetak.

Lihat raisedokumentasi pernyataan :

The fromklausul digunakan untuk pengecualian chaining: jika diberikan, kedua ekspresi harus menjadi kelas pengecualian lain atau misalnya, yang kemudian akan melekat pengecualian mengangkat sebagai __cause__atribut (yang ditulis). Jika pengecualian yang diangkat tidak ditangani, kedua pengecualian akan dicetak:

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

Mekanisme serupa bekerja secara implisit jika pengecualian muncul di dalam handler pengecualian atau finallyklausa: pengecualian sebelumnya kemudian dilampirkan sebagai __context__atribut pengecualian baru :

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

Juga lihat dokumentasi Pengecualian bawaan untuk detail tentang konteks dan menyebabkan informasi terlampir pada pengecualian.

Martijn Pieters
sumber
11
Apakah ada alasan untuk secara eksplisit rantai pengecualian menggunakan fromdan __cause__sebagai pengganti implisit __context__? Apakah ada kasus di mana seseorang akan melampirkan pengecualian yang berbeda dari yang tertangkap oleh except?
darkfeline
13
@darkfeline: Katakanlah API database Anda mendukung pembukaan basis data dari berbagai sumber, termasuk web dan disk. API Anda akan selalu naik DatabaseErrorjika pembukaan basis data gagal. Tetapi jika kegagalan adalah hasil dari IOErrorkarena file gagal dibuka atau HTTPErrorkarena URL gagal berfungsi maka itu adalah konteks yang ingin Anda sertakan secara eksplisit, sehingga pengembang yang menggunakan API dapat men-debug mengapa ini. Pada saat itu Anda gunakan raise DatabaseError from original_exception.
Martijn Pieters
4
@darkfeline: Jika pengembang itu membungkus penggunaan API basis data dalam API mereka sendiri dan ingin meneruskannya IOErroratau HTTPErrorkepada konsumen mereka , maka mereka harus menggunakannya raise NewException from databaseexception.__cause__, sekarang menggunakan pengecualian berbeda dari DatabaseExceptionyang baru saja mereka tangkap.
Martijn Pieters
2
@ dan3: tidak, tidak ada. Chaining pengecualian adalah murni fitur Python 3.
Martijn Pieters
5
@ laike9m: maksud Anda saat Anda menangani pengecualian foo, dan ingin mengajukan pengecualian barubar ? Kemudian Anda dapat menggunakan raise bar from foodan memiliki status Python yang foo secara langsung disebabkanbar . Jika Anda tidak menggunakan from foo, maka Python akan tetap mencetak keduanya, tetapi menyatakan bahwa selama penanganan foo, bardibesarkan , pesan berbeda, dimaksudkan untuk menandai kemungkinan bug dalam penanganan kesalahan.
Martijn Pieters