Secara manual menaikkan (melempar) pengecualian dengan Python

2266

Bagaimana saya bisa menaikkan pengecualian dengan Python sehingga nantinya bisa ditangkap melalui exceptblok?

TIMEX
sumber

Jawaban:

2938

Bagaimana cara saya melempar / menaikkan pengecualian secara manual dengan Python?

Gunakan konstruktor Pengecualian yang paling spesifik yang secara semantik cocok dengan masalah Anda .

Spesifik dalam pesan Anda, misalnya:

raise ValueError('A very specific bad thing happened.')

Jangan tampilkan pengecualian umum

Hindari membesarkan obat generik Exception. Untuk menangkapnya, Anda harus menangkap semua pengecualian lain yang lebih spesifik yang mensubklasifikasikannya.

Masalah 1: Menyembunyikan bug

raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.

Sebagai contoh:

def demo_bad_catch():
    try:
        raise ValueError('Represents a hidden bug, do not catch this')
        raise Exception('This is the exception you expect to handle')
    except Exception as error:
        print('Caught this error: ' + repr(error))

>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)

Masalah 2: Tidak akan menangkap

Dan tangkapan yang lebih spesifik tidak akan menangkap pengecualian umum:

def demo_no_catch():
    try:
        raise Exception('general exceptions not caught by specific handling')
    except ValueError as e:
        print('we will not catch exception: Exception')


>>> demo_no_catch()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling

Praktik Terbaik: raisepernyataan

Alih-alih, gunakan konstruktor Pengecualian yang paling spesifik yang secara semantik cocok dengan masalah Anda .

raise ValueError('A very specific bad thing happened')

yang juga dengan mudah memungkinkan sejumlah argumen sewenang-wenang untuk diteruskan ke konstruktor:

raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz') 

Argumen ini diakses oleh argsatribut pada Exceptionobjek. Sebagai contoh:

try:
    some_code_that_may_raise_our_value_error()
except ValueError as err:
    print(err.args)

cetakan

('message', 'foo', 'bar', 'baz')    

Dalam Python 2.5, messageatribut yang sebenarnya ditambahkan untuk BaseExceptionmendorong pengguna ke subkelas Pengecualian dan berhenti menggunakan args, tetapi pengenalan messagedan penghentian arg yang asli telah ditarik kembali .

Praktik Terbaik: exceptklausa

Saat berada di dalam klausa kecuali, Anda mungkin ingin, misalnya, mencatat bahwa jenis kesalahan tertentu terjadi, dan kemudian naik kembali. Cara terbaik untuk melakukan ini sambil menjaga jejak stack adalah dengan menggunakan pernyataan kenaikan gaji. Sebagai contoh:

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise                 # just this!
    # raise AppError      # Don't do this, you'll lose the stack trace!

Jangan memodifikasi kesalahan Anda ... tetapi jika Anda bersikeras.

Anda dapat mempertahankan stacktrace (dan nilai kesalahan) dengan sys.exc_info(), tapi ini cara yang lebih rentan kesalahan dan memiliki masalah kompatibilitas antara Python 2 dan 3 , lebih suka menggunakan telanjang raiseuntuk menaikkan kembali.

Untuk menjelaskan - sys.exc_info()mengembalikan jenis, nilai, dan traceback.

type, value, traceback = sys.exc_info()

Ini adalah sintaks dalam Python 2 - perhatikan ini tidak kompatibel dengan Python 3:

    raise AppError, error, sys.exc_info()[2] # avoid this.
    # Equivalently, as error *is* the second object:
    raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]

Jika Anda mau, Anda dapat mengubah apa yang terjadi dengan kenaikan gaji baru Anda - mis. Pengaturan baru argsuntuk instance:

def error():
    raise ValueError('oops!')

def catch_error_modify_message():
    try:
        error()
    except ValueError:
        error_type, error_instance, traceback = sys.exc_info()
        error_instance.args = (error_instance.args[0] + ' <modification>',)
        raise error_type, error_instance, traceback

Dan kami telah melestarikan seluruh traceback sambil memodifikasi argumen. Perhatikan bahwa ini bukan praktik terbaik dan ini adalah sintaks yang tidak valid di Python 3 (membuat menjaga kompatibilitas jauh lebih sulit untuk diselesaikan).

>>> catch_error_modify_message()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in catch_error_modify_message
  File "<stdin>", line 2, in error
ValueError: oops! <modification>

Dengan Python 3 :

    raise error.with_traceback(sys.exc_info()[2])

Sekali lagi: hindari memanipulasi traceback secara manual. Itu kurang efisien dan lebih rentan kesalahan. Dan jika Anda menggunakan threading dan sys.exc_infoAnda bahkan mungkin mendapatkan traceback yang salah (terutama jika Anda menggunakan penanganan pengecualian untuk aliran kontrol - yang secara pribadi saya cenderung hindari.)

Python 3, rantai Pengecualian

Di Python 3, Anda bisa membuat rantai Pengecualian, yang mempertahankan traceback:

    raise RuntimeError('specific message') from error

Waspadalah:

  • ini tidak memungkinkan mengubah jenis kesalahan mengangkat, dan
  • ini tidak kompatibel dengan Python 2.

Metode yang sudah usang:

Ini dapat dengan mudah disembunyikan dan bahkan masuk ke kode produksi. Anda ingin mengajukan pengecualian, dan melakukannya akan memunculkan pengecualian, tetapi bukan yang dimaksudkan!

Berlaku di Python 2, tetapi tidak dalam Python 3 adalah sebagai berikut:

raise ValueError, 'message' # Don't do this, it's deprecated!

Hanya valid dalam versi Python yang jauh lebih lama (2.4 dan lebih rendah), Anda mungkin masih melihat orang-orang yang meningkatkan string:

raise 'message' # really really wrong. don't do this.

Di semua versi modern, ini sebenarnya akan menaikkan a TypeError, karena Anda tidak menaikkan BaseExceptionjenis. Jika Anda tidak memeriksa pengecualian yang tepat dan tidak memiliki pengulas yang mengetahui masalah ini, itu bisa mulai diproduksi.

Contoh Penggunaan

Saya mengajukan Pengecualian untuk memperingatkan konsumen tentang API saya jika mereka salah menggunakannya:

def api_func(foo):
    '''foo should be either 'baz' or 'bar'. returns something very useful.'''
    if foo not in _ALLOWED_ARGS:
        raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))

Buat jenis kesalahan Anda sendiri saat apropos

"Aku ingin membuat kesalahan dengan sengaja, sehingga akan masuk ke pengecualian"

Anda dapat membuat jenis kesalahan Anda sendiri, jika Anda ingin menunjukkan sesuatu yang spesifik salah dengan aplikasi Anda, cukup subkelas poin yang sesuai dalam hierarki pengecualian:

class MyAppLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''

dan penggunaan:

if important_key not in resource_dict and not ok_to_be_missing:
    raise MyAppLookupError('resource is missing, and that is not ok.')
Aaron Hall
sumber
19
Terima kasih untuk ini, tepat seperti yang saya butuhkan. Telanjang raiseadalah apa yang saya butuhkan untuk dapat melakukan debugging kesalahan kustom pada beberapa tingkat eksekusi kode tanpa melanggar jejak tumpukan.
CaffeineConnoisseur
Ini jawaban yang bagus. Tapi saya masih bekerja dengan banyak kode 2,7, dan saya sering menemukan diri saya ingin menambahkan informasi ke pengecualian yang tidak terduga, seperti posisi file input atau nilai-nilai beberapa variabel, tetapi simpan stack asli dan pengecualian. Saya dapat mencatatnya, tetapi kadang-kadang saya tidak ingin itu dicatat, misalnya jika kode induk akhirnya menanganinya. raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2]tampaknya melakukan apa yang saya inginkan, dan saya tidak pernah mengalami masalah dengannya. Tapi rasanya berantakan, dan bukan praktik yang diterima. Apakah ada cara yang lebih baik?
Michael Scheper
2
@brennanyoung Dalam konteks itu saya pikir itu bisa membingungkan untuk meningkatkan SyntaxError - mungkin Anda harus meningkatkan pengecualian khusus. Saya jelaskan caranya di sini: stackoverflow.com/a/26938914/541136
Aaron Hall
2
Perhatikan bahwa kutipan lengkapnya adalah "Semua pengecualian bawaan yang tidak keluar dari sistem berasal dari kelas ini. Semua pengecualian yang ditentukan pengguna juga harus diturunkan dari kelas ini." - Itu sebagian besar berarti bahwa Anda tidak boleh menggunakan salah satu dari 4 pengecualian yang tidak berasal dari Exceptionsebagai kelas induk Anda - Anda dapat mensubklasifikasikan sesuatu yang lebih spesifik, dan harus melakukannya jika itu masuk akal.
Aaron Hall
1
Dalam contoh untuk " Praktik Terbaik: kecuali klausa ", Anda menggunakan AppErrorpengecualian yang tidak ditentukan . Mungkin lebih baik menggunakan kesalahan AttributeError
bawaan
530

JANGAN LAKUKAN INI . Mengangkat telanjang Exceptionsama sekali bukan hal yang benar untuk dilakukan; lihat jawaban Aaron Hall sebagai gantinya.

Tidak bisa mendapatkan lebih banyak pythonic dari ini:

raise Exception("I know python!")

Lihat dokumen pernyataan kenaikan untuk python jika Anda ingin info lebih lanjut.

Gabriel Hurley
sumber
67
Tidak tolong! Ini menghilangkan potensi untuk menjadi spesifik tentang apa yang Anda tangkap. SEPENUHNYA cara yang salah untuk melakukannya. Lihatlah jawaban yang sangat baik Aaron Hall, bukan yang ini. Saat-saat seperti ini saya berharap bisa memberikan lebih dari satu downvote per jawaban.
Dawood ibn Kareem
27
@PeterR Sama buruknya dengan downvotes yang sangat sedikit. Kepada SEMUA ORANG membaca jawaban ini, JANGAN LAKUKAN INI! Jawaban yang benar adalah jawaban Aaron Hall.
Dawood ibn Kareem
6
Saya pikir harus ada penjelasan yang lebih terperinci tentang mengapa ini salah atau sangat buruk.
Charlie Parker
9
@CharlieParker Ada. Ini adalah bagian pertama dari jawaban Aaron Hall .
Dinei
5
Mengapa jawaban ini tidak dapat ditandai untuk dihapus? Sudah 93 downvote!
codeforester
55

Di Python3 ada 4 sintaks yang berbeda untuk rasing pengecualian:

1. raise exception 
2. raise exception (args) 
3. raise
4. raise exception (args) from original_exception

1. meningkatkan pengecualian vs 2. meningkatkan pengecualian (args)

Jika Anda menggunakan raise exception (args) untuk menaikkan pengecualian maka argsakan dicetak ketika Anda mencetak objek pengecualian - seperti yang ditunjukkan pada contoh di bawah ini.

  #raise exception (args)
    try:
        raise ValueError("I have raised an Exception")
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error I have raised an Exception 



  #raise execption 
    try:
        raise ValueError
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error 

3. naik

raisepernyataan tanpa argumen kembali memunculkan pengecualian terakhir. Ini berguna jika Anda perlu melakukan beberapa tindakan setelah menangkap pengecualian dan kemudian ingin menaikkannya kembali. Tetapi jika tidak ada pengecualian sebelumnya, raisepernyataan menimbulkan TypeErrorPengecualian.

def somefunction():
    print("some cleaning")

a=10
b=0 
result=None

try:
    result=a/b
    print(result)

except Exception:            #Output ->
    somefunction()           #some cleaning
    raise                    #Traceback (most recent call last):
                             #File "python", line 8, in <module>
                             #ZeroDivisionError: division by zero

4. meningkatkan exception (args) dari original_exception

Pernyataan ini digunakan untuk membuat rantai pengecualian di mana pengecualian yang dimunculkan sebagai respons terhadap pengecualian lain dapat berisi detail pengecualian asli - seperti yang ditunjukkan dalam contoh di bawah ini.

class MyCustomException(Exception):
pass

a=10
b=0 
reuslt=None
try:
    try:
        result=a/b

    except ZeroDivisionError as exp:
        print("ZeroDivisionError -- ",exp)
        raise MyCustomException("Zero Division ") from exp

except MyCustomException as exp:
        print("MyException",exp)
        print(exp.__cause__)

Keluaran:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero
N Randhawa
sumber
7
Harap dicatat bahwa PEP8 lebih suka exception(args)lebihexception (args)
Gloweye
Ada juga yang raise exception(args) from Nonemengatakan bahwa pengecualian aktif saat ini ditangani dan tidak lagi menarik. Kalau tidak, jika Anda menaikkan pengecualian di dalam exceptblok dan itu tidak ditangani, traceback untuk kedua pengecualian akan ditampilkan dipisahkan oleh pesan "Selama menangani pengecualian di atas, pengecualian lain terjadi"
cg909
35

Untuk kasus umum di mana Anda perlu melempar pengecualian sebagai tanggapan terhadap beberapa kondisi yang tidak terduga, dan bahwa Anda tidak pernah berniat untuk menangkap, tetapi hanya gagal cepat untuk memungkinkan Anda men-debug dari sana jika itu terjadi - yang paling logis tampaknya adalah AssertionError:

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)
Evgeni Sergeev
sumber
19
Ini adalah kasus yang lebih baik ValueErrordaripada AssertionErrorkarena tidak ada masalah dengan pernyataan (karena tidak ada yang dibuat di sini) - masalahnya adalah dengan nilai. Jika Anda benar-benar menginginkannya AssertionError, tulislah assert distance > 0, 'Distance must be positive'. Tetapi Anda tidak boleh salah memeriksa seperti itu karena pernyataan dapat dimatikan ( python -O).
Alchemist Dua-Bit
1
@ Two-BitAlchemist Poin bagus. Gagasan itu hilang dalam penyederhanaan, ketika saya menulis contoh sederhana di atas. Dalam banyak kasus serupa, ini adalah kondisi yang tidak terkait dengan nilai tertentu. Sebaliknya, artinya adalah "aliran kontrol tidak boleh sampai di sini".
Evgeni Sergeev
2
@ Pernyataan Two-BitAlchemist dapat dimatikan, ya, tapi kemudian Anda tidak boleh menggunakannya untuk memeriksa kesalahan sama sekali?
Evgeni Sergeev
Yah itu tergantung. Saya tidak akan membiarkan itu menjadi satu-satunya kesalahan saya dalam memeriksa program yang ingin saya distribusikan. Di sisi lain, saya bisa membuat program hanya untuk rekan kerja saya dan memberi tahu mereka bahwa mereka menggunakannya dengan risiko sendiri jika mereka menjalankannya -O.
Alchemist Dua-Bit
1
@ Two-BitAlchemist Bagi saya peran asersi bukan pengecekan kesalahan per se (untuk apa pengujian), tetapi mereka membuat pagar di dalam kode yang tidak bisa dilewati bug tertentu. Jadi menjadi lebih mudah untuk melacak dan mengisolasi bug, yang pasti akan terjadi. Ini hanya kebiasaan baik yang membutuhkan sedikit usaha, sementara pengujian membutuhkan banyak usaha dan banyak waktu.
Evgeni Sergeev
12

Baca jawaban yang ada terlebih dahulu, ini hanya sebuah tambahan.

Perhatikan bahwa Anda dapat mengajukan pengecualian dengan atau tanpa argumen.

Contoh:

raise SystemExit

keluar dari program tetapi Anda mungkin ingin tahu apa yang terjadi. Jadi Anda dapat menggunakan ini.

raise SystemExit("program exited")

ini akan mencetak "program keluar" ke stderr sebelum menutup program.

Anant Prakash
sumber
2
Bukankah ini bertentangan dengan paradigma OOP? Saya berasumsi, kasus pertama melempar referensi kelas dan yang kedua contoh dari SystemExit. Bukankah raise SystemExit()itu pilihan yang lebih baik? Mengapa yang pertama bekerja?
burny
2

Cara lain untuk melempar pengecualian adalah assert. Anda dapat menggunakan menegaskan untuk memverifikasi suatu kondisi sedang dipenuhi jika tidak maka itu akan naik AssertionError. Untuk lebih jelasnya lihat di sini .

def avg(marks):
    assert len(marks) != 0,"List is empty."
    return sum(marks)/len(marks)

mark2 = [55,88,78,90,79]
print("Average of mark2:",avg(mark2))

mark1 = []
print("Average of mark1:",avg(mark1))
Rehan Haider
sumber
2

Hanya untuk diketahui: ada saatnya Anda DO ingin menangani pengecualian umum. Jika Anda memproses banyak file dan mencatat kesalahan Anda, Anda mungkin ingin mengetahui kesalahan apa pun yang terjadi pada suatu file, mencatatnya, dan melanjutkan memproses sisa file tersebut. Dalam hal ini, a

try:
    foo() 
except Exception as e:
    print(str(e)) # Print out handled error

memblokir cara yang baik untuk melakukannya. Anda masih ingin raisepengecualian khusus sehingga Anda tahu apa artinya.

markemus
sumber
0

Anda harus mempelajari pernyataan kenaikan python untuk itu. Itu harus disimpan di dalam blok percobaan. Contoh -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
Abhijeet.py
sumber