Bagaimana cara meningkatkan Pengecualian yang sama dengan pesan khusus dengan Python?

145

Saya memiliki tryblok ini dalam kode saya:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

Sebenarnya, saya sebenarnya mengangkat yang lain ValueError , bukan yang ValueErrordilemparkan oleh do_something...(), yang disebut errdalam kasus ini. Bagaimana cara melampirkan pesan khusus err? Saya mencoba kode berikut tetapi gagal karena err, ValueError misalnya , tidak dapat dipanggil:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)
Kit
sumber
13
@Hamish, melampirkan informasi tambahan dan meningkatkan kembali pengecualian dapat sangat membantu saat melakukan debugging.
Johan Lundberg
@Johan Absolutely - dan itulah gunanya stacktrace. Tidak bisa mengerti mengapa Anda mengedit pesan kesalahan yang ada alih-alih memunculkan kesalahan baru.
Hamish
@ Nyonya. Tentu tetapi Anda dapat menambahkan hal-hal lain. Untuk pertanyaan Anda, lihat jawaban saya dan contoh UnicodeDecodeError. Jika Anda memiliki komentar tentang itu, mungkin komentar saya adalah jawaban saya.
Johan Lundberg
Kemungkinan rangkap dari Menambahkan informasi ke pengecualian?
Trevor Boyd Smith
1
@Kit itu tahun 2020 dan python 3 ada di mana-mana. Mengapa tidak mengubah jawaban yang diterima menjadi jawaban Ben :-)
mit

Jawaban:

88

Pembaruan: Untuk Python 3, periksa jawaban Ben


Untuk melampirkan pesan ke pengecualian saat ini dan menaikkannya kembali: (coba luar / hanya untuk menunjukkan efeknya)

Untuk python 2.x di mana x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Ini juga akan melakukan hal yang benar jika erryang berasal dari ValueError. Sebagai contoh UnicodeDecodeError.

Perhatikan bahwa Anda dapat menambahkan apa pun yang Anda suka err. Sebagai contoh err.problematic_array=[1,2,3].


Sunting: @Ducan menunjuk dalam komentar di atas tidak bekerja dengan python 3 karena .messagebukan anggota ValueError. Alih-alih, Anda dapat menggunakan ini (valid python 2.6 atau lebih baru atau 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Sunting2:

Bergantung pada apa tujuannya, Anda juga dapat memilih untuk menambahkan informasi tambahan di bawah nama variabel Anda sendiri. Untuk python2 dan python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info
Johan Lundberg
sumber
9
Karena Anda telah berupaya menggunakan penanganan pengecualian gaya Python 3 dan print, Anda mungkin harus mencatat bahwa kode Anda tidak berfungsi di Python 3.x karena tidak ada messageatribut pada pengecualian. err.args = (err.args[0] + " hello",) + err.args[1:]dapat bekerja lebih andal (dan kemudian hanya mengonversi ke string untuk mendapatkan pesan).
Duncan
1
Sayangnya tidak ada jaminan bahwa args [0] adalah tipe string yang mewakili pesan kesalahan - "Tupel argumen yang diberikan kepada konstruktor pengecualian. Beberapa pengecualian bawaan (seperti IOError) mengharapkan sejumlah argumen dan memberikan makna khusus untuk elemen tuple ini, sementara yang lain biasanya dipanggil hanya dengan string tunggal yang memberikan pesan kesalahan. " Jadi kode tidak akan berfungsi arg [0] bukan pesan kesalahan (bisa berupa int, atau bisa berupa string yang mewakili nama file).
Trent
1
@Tara, Menarik. Apakah Anda punya referensi tentang itu? Kemudian saya akan menambah anggota yang sama sekali baru: err.my_own_extra_info. Atau merangkum semuanya dalam pengecualian saya sendiri menjaga informasi baru dan asli.
Johan Lundberg
2
Contoh nyata ketika args [0] bukan pesan kesalahan - docs.python.org/2/library/exceptions.html - "exception EnvironmentError Kelas dasar untuk pengecualian yang dapat terjadi di luar sistem Python: IOError, OSError. Ketika pengecualian dari tipe ini dibuat dengan 2-tuple, item pertama tersedia pada atribut errno instance (diasumsikan sebagai nomor kesalahan), dan item kedua tersedia pada atribut strerror (biasanya terkait pesan kesalahan). Tuple itu sendiri juga tersedia pada atribut args. "
Trent
2
Saya tidak mengerti ini sama sekali. Satu-satunya alasan pengaturan .messageatribut melakukan apa pun di sini adalah karena atribut ini dicetak secara eksplisit . Jika Anda menaikkan pengecualian tanpa menangkap dan mencetak, Anda tidak akan melihat .messageatribut melakukan sesuatu yang bermanfaat.
DanielSank
171

Jika Anda cukup beruntung hanya mendukung python 3.x, ini benar-benar menjadi hal yang indah :)

bangkit dari

Kami dapat memadukan pengecualian menggunakan kenaikan gaji dari .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

Dalam hal ini, pengecualian yang akan ditangkap oleh penelepon Anda memiliki nomor baris tempat kami menaikkan pengecualian kami.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Perhatikan pengecualian bawah hanya memiliki stacktrace dari tempat kami mengangkat pengecualian kami. Penelepon Anda masih bisa mendapatkan pengecualian asli dengan mengakses __cause__atribut pengecualian yang mereka tangkap.

with_traceback

Atau Anda bisa menggunakan with_traceback .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

Menggunakan formulir ini, pengecualian penelepon Anda akan menangkap memiliki traceback dari tempat kesalahan asli terjadi.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Perhatikan bahwa pengecualian bawah memiliki garis tempat kami melakukan pembagian yang tidak valid serta garis tempat kami mengeluarkan pengecualian.

Ben
sumber
1
Apakah mungkin menambahkan pesan khusus ke pengecualian tanpa traceback tambahan? Misalnya, dapat raise Exception('Smelly socks') from edimodifikasi untuk hanya menambahkan "Smelly socks" sebagai komentar pada traceback asli daripada memperkenalkan traceback baru sendiri.
joelostblom
Itulah perilaku yang akan Anda dapatkan dari jawaban Johan Lundberg
Ben
3
ini sangat indah. Terima kasih.
allanberry
3
Mengembalikan pengecualian baru atau pengecualian rantai kenaikan dengan pesan baru menciptakan lebih banyak kebingungan daripada yang dibutuhkan dalam banyak kasus. Dengan sendirinya pengecualian itu rumit untuk ditangani. Strategi yang lebih baik adalah hanya menambahkan pesan Anda ke argumen pengecualian asli jika memungkinkan seperti dalam err.args + = ("message",) dan menaikkan kembali pesan pengecualian. Traceback mungkin tidak membawa Anda ke nomor baris tempat pengecualian itu ditangkap tetapi itu akan membawa Anda ke tempat pengecualian itu terjadi.
user-asterix
2
Anda juga dapat secara eksplisit menekan tampilan rantai pengecualian dengan menentukan Tidak ada dalam klausa dari:raise RuntimeError("Something bad happened") from None
pfabri
10
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

cetakan:

There is a problem: invalid literal for int() with base 10: 'a'
eumiro
sumber
1
Saya bertanya-tanya apakah ada idiom Python untuk apa yang saya coba lakukan, selain membangkitkan contoh lain .
Kit
@Kit - Saya akan menyebutnya 'mengajukan kembali pengecualian': docs.python.org/reference/simple_stmts.html#raise
eumiro
1
@ eumiro, Tidak, Anda membuat pengecualian baru. Lihat jawaban saya. Dari tautan Anda: "... tetapi naikkan tanpa ekspresi harus lebih disukai jika pengecualian yang akan diajukan kembali adalah pengecualian yang paling baru aktif dalam lingkup saat ini."
Johan Lundberg
3
@JohanLundberg - raisetanpa parameter naik kembali. Jika OP ingin menambahkan pesan, ia harus mengajukan pengecualian baru dan dapat menggunakan kembali pesan / jenis pengecualian asli.
eumiro
2
Jika Anda ingin menambahkan pesan, Anda tidak dapat membuat pesan baru dari awal dengan melemparkan "ValueError". Dengan melakukannya, Anda menghancurkan informasi yang mendasari seperti apa ValueError itu (mirip dengan mengiris di C ++). Dengan melempar kembali pengecualian yang sama dengan kenaikan gaji tanpa argumen, Anda meneruskan objek asli dengan tipe spesifik yang benar (berasal dari ValueError).
Johan Lundberg
9

Tampaknya semua jawaban menambahkan info ke e.args [0], sehingga mengubah pesan kesalahan yang ada. Apakah ada kerugian untuk memperpanjang tuple args saja? Saya pikir kemungkinan terbalik adalah, Anda dapat meninggalkan pesan kesalahan asli sendirian untuk kasus-kasus di mana parsing string itu diperlukan; dan Anda bisa menambahkan beberapa elemen ke tuple jika penanganan kesalahan khusus Anda menghasilkan beberapa pesan atau kode kesalahan, untuk kasus-kasus di mana traceback akan diuraikan secara program (seperti melalui alat pemantauan sistem).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

atau

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Bisakah Anda melihat kelemahan dari pendekatan ini?

Chris Johnson
sumber
Jawaban saya yang lebih lama tidak mengubah e.args [0].
Johan Lundberg
4

Templat kode ini harus memungkinkan Anda untuk meningkatkan pengecualian dengan pesan khusus.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")
Ruggero Turra
sumber
3
Ini tidak menyimpan jejak tumpukan.
plok
Penanya tidak menentukan bahwa jejak tumpukan dipertahankan.
shrewmouse
4

Entah menaikkan pengecualian baru dengan menggunakan pesan kesalahan Anda

raise Exception('your error message')

atau

raise ValueError('your error message')

di tempat Anda ingin menaikkannya ATAU melampirkan (mengganti) pesan kesalahan ke dalam pengecualian saat ini menggunakan 'from' (hanya didukung oleh Python 3.x):

except ValueError as e:
  raise ValueError('your message') from e
Alexey Antonenko
sumber
Thanx, @gberger, pendekatan 'from e' sebenarnya tidak didukung oleh python 2.x
Alexey Antonenko
3

Ini adalah fungsi yang saya gunakan untuk mengubah pesan pengecualian di Python 2.7 dan 3.x sambil mempertahankan traceback asli. Itu membutuhkansix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)
Bryce Guinta
sumber
3

Pengecualian bawaan Python 3 memiliki strerrorbidang:

except ValueError as err:
  err.strerror = "New error message"
  raise err
pengguna3761308
sumber
Ini sepertinya tidak berhasil. Apakah Anda kehilangan sesuatu?
MasayoMusic
2

Jawaban saat ini tidak berfungsi dengan baik untuk saya, jika pengecualian tidak ditangkap kembali pesan terlampir tidak ditampilkan.

Tetapi melakukan seperti di bawah keduanya menyimpan jejak dan menunjukkan pesan yang ditambahkan terlepas dari apakah pengecualian ditangkap kembali atau tidak.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(Saya menggunakan Python 2.7, belum mencobanya dengan Python 3)

Zitrax
sumber
1

Tidak satu pun dari solusi di atas melakukan apa yang saya inginkan, yaitu menambahkan beberapa informasi ke bagian pertama dari pesan kesalahan yaitu saya ingin pengguna saya melihat pesan khusus saya terlebih dahulu.

Ini bekerja untuk saya:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)
RobinL
sumber
0

Ini hanya berfungsi dengan Python 3 . Anda dapat mengubah argumen asli pengecualian dan menambahkan argumen Anda sendiri.

Pengecualian mengingat argumen yang dibuat dengannya. Saya kira ini adalah agar Anda dapat memodifikasi pengecualian.

Dalam fungsi ini, reraisekami menambahkan argumen asli pengecualian dengan argumen baru apa pun yang kami inginkan (seperti pesan). Akhirnya kami kembali meningkatkan pengecualian sambil mempertahankan jejak-kembali sejarah.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

keluaran

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')
shrewmouse
sumber
-3

Jika Anda ingin menyesuaikan jenis kesalahan, hal sederhana yang dapat Anda lakukan adalah mendefinisikan kelas kesalahan berdasarkan ValueError.

igni
sumber
bagaimana itu membantu dalam kasus ini?
Johan Lundberg