"Pengecualian dalam" (dengan traceback) dengan Python?

146

Latar belakang saya adalah dalam C # dan saya baru saja memulai pemrograman dengan Python. Ketika pengecualian dilemparkan, saya biasanya ingin membungkusnya dalam pengecualian lain yang menambahkan lebih banyak informasi, sambil tetap menunjukkan jejak tumpukan penuh. Ini cukup mudah di C #, tetapi bagaimana cara melakukannya dengan Python?

Misalnya. di C # saya akan melakukan sesuatu seperti ini:

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

Dengan Python saya bisa melakukan hal serupa:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... tapi ini kehilangan jejak pengecualian batin!

Sunting: Saya ingin melihat pesan pengecualian dan tumpukan jejak serta menghubungkan keduanya. Yaitu, saya ingin melihat dalam output bahwa pengecualian X terjadi di sini dan kemudian pengecualian Y di sana - sama seperti yang saya lakukan di C #. Apakah ini mungkin di Python 2.6? Sepertinya yang terbaik yang bisa saya lakukan sejauh ini (berdasarkan jawaban Glenn Maynard) adalah:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Ini termasuk pesan dan traceback, tetapi tidak menunjukkan pengecualian mana yang terjadi di traceback.

EMP
sumber
3
Jawaban yang diterima sudah usang, mungkin Anda harus mempertimbangkan untuk menerima yang lain.
Aaron Hall
1
@AaronHall sayangnya OP belum pernah ada sejak tahun 2015.
Antti Haapala

Jawaban:

136

Python 2

Itu mudah; melewati traceback sebagai argumen ketiga untuk diajukan.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

Selalu lakukan ini saat menangkap satu pengecualian dan memunculkan kembali yang lain.

Glenn Maynard
sumber
4
Terima kasih. Itu mempertahankan traceback, tetapi kehilangan pesan kesalahan dari pengecualian asli. Bagaimana cara melihat kedua pesan dan kedua traceback?
EMP
6
raise MyException(str(e)), ..., dll.
Glenn Maynard
23
Python 3 menambahkan raise E() from tbdan.with_traceback(...)
Dima Tisnek
3
@ GlennMaynard itu adalah pertanyaan yang cukup lama, tetapi argumen tengah dari nilai raiseadalah untuk meneruskan ke pengecualian (jika argumen pertama adalah kelas pengecualian dan bukan sebuah instance). Jadi jika Anda ingin pengecualian swap, bukannya melakukan raise MyException(str(e)), None, sys.exc_info()[2], lebih baik untuk menggunakan ini: raise MyException, e.args, sys.exc_info()[2].
bgusach
8
Cara yang sesuai dengan Python2 dan 3 dimungkinkan menggunakan paket yang akan datang: python-future.org/compatible_idioms.html#raising-exceptions Misalnya from future.utils import raise_dan raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda
239

Python 3

Dalam python 3 Anda dapat melakukan hal berikut:

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

Ini akan menghasilkan sesuatu seperti ini:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

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

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")
Alexei Tenitski
sumber
17
raise ... from ...memang cara yang tepat untuk melakukan ini di Python 3. Ini membutuhkan lebih banyak upvotes.
Nakedible
NakedibleSaya pikir itu karena sayangnya kebanyakan orang masih tidak menggunakan Python 3.
Tim Ludwinski
Ini tampaknya terjadi bahkan dengan menggunakan 'dari' di python 3
Steve Vermeulen
Bisa di-backport ke Python 2. Semoga saja suatu hari nanti.
Marcin Wojnarski
4
@ogrisel Anda dapat menggunakan futurepaket untuk mencapai ini: python-future.org/compatible_idioms.html#raising-exceptions Misalnya from future.utils import raise_dan raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda
19

Python 3 memiliki raise... fromklausa untuk pengecualian rantai. Jawaban Glenn bagus untuk Python 2.7, tetapi hanya menggunakan traceback pengecualian asli dan membuang pesan kesalahan dan detail lainnya. Berikut adalah beberapa contoh di Python 2.7 yang menambahkan informasi konteks dari lingkup saat ini ke pesan kesalahan pengecualian asli, tetapi tetap detail lainnya tetap utuh.

Jenis Pengecualian yang Dikenal

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

Rasa raisepernyataan itu mengambil tipe pengecualian sebagai ekspresi pertama, argumen konstruktor kelas pengecualian dalam tuple sebagai ekspresi kedua, dan traceback sebagai ekspresi ketiga. Jika Anda menjalankan lebih awal dari Python 2.2, lihat peringatan di sys.exc_info().

Semua Jenis Pengecualian

Berikut ini contoh lain yang lebih umum jika Anda tidak tahu pengecualian apa yang harus ditangkap oleh kode Anda. The downside adalah bahwa ia kehilangan tipe pengecualian dan hanya menimbulkan RuntimeError. Anda harus mengimpor tracebackmodul.

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Ubah Pesan

Berikut opsi lain jika jenis pengecualian akan memungkinkan Anda menambahkan konteks. Anda dapat mengubah pesan pengecualian dan kemudian mengembalikannya.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

Itu menghasilkan jejak tumpukan berikut:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

Anda dapat melihat bahwa itu menunjukkan baris di mana check_output()dipanggil, tetapi pesan pengecualian sekarang termasuk baris perintah.

Don Kirkby
sumber
1
Dari mana ex.strerrordatangnya? Saya tidak dapat menemukan klik yang relevan untuk itu di Python docs. Bukankah seharusnya begitu str(ex)?
Henrik Heimbuerger
1
IOErrorberasal dari EnvironmentError, @hheimbuerger, yang menyediakan atribut errornodan strerror.
Don Kirkby
Bagaimana saya membungkus arbitrer Error, misalnya ValueError, RuntimeErrordengan menangkap Exception? Jika saya mereproduksi jawaban Anda untuk kasus ini, stacktrace hilang.
Karl Richter
Saya tidak yakin apa yang Anda tanyakan, @karl. Bisakah Anda memposting sampel dalam pertanyaan baru dan kemudian menautkannya dari sini?
Don Kirkby
Saya mengedit duplikat pertanyaan OP di stackoverflow.com/questions/23157766/… dengan klarifikasi dengan memperhitungkan jawaban Anda secara langsung. Kita harus membahas di sana :)
Karl Richter
12

Dengan Python 3.x :

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

atau sederhana

except Exception:
    raise MyException()

yang akan menyebar MyExceptiontetapi mencetak kedua pengecualian jika tidak akan ditangani.

Dengan Python 2.x :

raise Exception, 'Failed to process file ' + filePath, e

Anda dapat mencegah pencetakan kedua pengecualian dengan mematikan __context__atribut. Di sini saya menulis manajer konteks menggunakan itu untuk menangkap dan mengubah pengecualian Anda dengan cepat: (lihat http://docs.python.org/3.1/library/stdtypes.html untuk penjelasan bagaimana mereka bekerja)

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise
ilya n.
sumber
4
TypeError: menaikkan: arg 3 harus menjadi traceback atau Tidak ada
Glenn Maynard
Maaf, saya membuat kesalahan, entah bagaimana saya pikir itu juga menerima pengecualian dan mendapatkan atribut traceback mereka secara otomatis. Sesuai docs.python.org/3.1/reference/… , ini harus berupa e .__ traceback__
ilya n.
1
@ilyan .: Python 2 tidak memiliki e.__traceback__atribut!
Jan Hudec
5

Saya tidak berpikir Anda bisa melakukan ini di Python 2.x, tetapi sesuatu yang mirip dengan fungsi ini adalah bagian dari Python 3. Dari PEP 3134 :

Dalam implementasi Python hari ini, pengecualian terdiri dari tiga bagian: tipe, nilai, dan traceback. Modul 'sys', mengekspos pengecualian saat ini dalam tiga variabel paralel, exc_type, exc_value, dan exc_traceback, fungsi sys.exc_info () mengembalikan tuple dari ketiga bagian ini, dan pernyataan 'kenaikan' memiliki bentuk tiga argumen yang menerima bentuk tiga argumen tiga bagian ini. Memanipulasi pengecualian sering kali mengharuskan melewati ketiga hal ini secara paralel, yang bisa membosankan dan rentan kesalahan. Selain itu, pernyataan 'kecuali' hanya dapat memberikan akses ke nilai, bukan traceback. Menambahkan atribut ' traceback ' ke nilai pengecualian membuat semua informasi pengecualian dapat diakses dari satu tempat.

Perbandingan dengan C #:

Pengecualian dalam C # berisi properti 'InnerException' yang hanya bisa dibaca yang mungkin mengarah ke pengecualian lain. Dokumentasinya [10] mengatakan bahwa "Ketika sebuah pengecualian X dilemparkan sebagai akibat langsung dari pengecualian Y sebelumnya, properti InnerException dari X harus berisi referensi ke Y." Properti ini tidak diatur oleh VM secara otomatis; alih-alih, semua konstruktor pengecualian mengambil argumen opsional 'innerException' untuk mengaturnya secara eksplisit. The ' Penyebab ' atribut memenuhi tujuan yang sama sebagai InnerException, tapi PEP ini mengusulkan bentuk baru dari 'kenaikan gaji' daripada memperpanjang konstruktor dari semua pengecualian. C # juga menyediakan metode GetBaseException yang melompat langsung ke akhir rantai InnerException;

Perhatikan juga bahwa Java, Ruby dan Perl 5 juga tidak mendukung jenis hal ini. Mengutip lagi:

Adapun bahasa lain, Java dan Ruby membuang pengecualian asli ketika pengecualian lain terjadi dalam klausa 'menangkap' / 'penyelamatan' atau 'akhirnya' / 'memastikan'. Perl 5 tidak memiliki penanganan eksepsi terstruktur bawaan. Untuk Perl 6, RFC nomor 88 [9] mengusulkan mekanisme pengecualian yang secara implisit mempertahankan pengecualian berantai dalam array bernama @@.

ire_and_curses
sumber
Tapi, tentu saja, di Perl5 Anda bisa mengatakan "mengaku qq {OH NOES! $ @}" Dan tidak kehilangan jejak stack pengecualian lain. Atau Anda bisa menerapkan tipe Anda sendiri yang mempertahankan pengecualian.
jrockway
4

Untuk kompatibilitas maksimum antara Python 2 dan 3, Anda bisa menggunakannya raise_fromdi sixperpustakaan. https://six.readthedocs.io/#six.raise_from . Inilah contoh Anda (sedikit dimodifikasi untuk kejelasan):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)
LexH
sumber
3

Anda bisa menggunakan kelas CausedException saya untuk membuat pengecualian di Python 2.x (dan bahkan dalam Python 3 dapat berguna jika Anda ingin memberikan lebih dari satu pengecualian yang tertangkap sebagai penyebab pengecualian yang baru diangkat). Mungkin itu bisa membantu Anda.

Alfe
sumber
2

Mungkin Anda bisa mengambil informasi yang relevan dan meneruskannya? Saya sedang memikirkan sesuatu seperti:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

    def __str__(self):
        return repr(self.value)

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e
brool
sumber
2

Asumsi:

  • Anda memerlukan solusi, yang berfungsi untuk Python 2 (untuk raise ... fromsolusi lihat Python 3 murni )
  • hanya ingin memperkaya pesan kesalahan, misalnya menyediakan beberapa konteks tambahan
  • perlu jejak stack penuh

Anda dapat menggunakan solusi sederhana dari dokumen https://docs.python.org/3/tutorial/errors.html#raising-exceptions :

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

Hasil:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Sepertinya bagian kunci adalah kata kunci 'kenaikan' yang disederhanakan yang berdiri sendiri. Itu akan menaikkan kembali Exception di blok kecuali.

atreat
sumber
Ini adalah solusi yang kompatibel dengan Python 2 & 3! Terima kasih!
Andy Chase
Saya pikir idenya adalah untuk meningkatkan jenis pengecualian yang berbeda.
Tim Ludwinski
2
Ini bukan rantai pengecualian bersarang, hanya membangkitkan satu pengecualian
Karl Richter
Ini adalah solusi python 2 terbaik, jika Anda hanya perlu memperkaya pesan pengecualian dan memiliki jejak tumpukan penuh!
geekQ
Apa perbedaan antara menggunakan kenaikan dan kenaikan dari
variabel