Lebih baik 'mencoba' sesuatu dan menangkap pengecualian atau menguji apakah mungkin terlebih dahulu untuk menghindari pengecualian?

141

Haruskah saya menguji ifsesuatu yang valid atau hanya tryuntuk melakukannya dan menangkap pengecualian?

  • Apakah ada dokumentasi kuat yang mengatakan bahwa satu cara lebih disukai?
  • Apakah satu cara lebih pythonic ?

Misalnya, haruskah saya:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

Atau:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Beberapa pemikiran ...
PEP 20 mengatakan:

Kesalahan tidak boleh lewat diam-diam.
Kecuali dibungkam secara eksplisit.

Haruskah menggunakan a tryalih - alih an ifdiartikan sebagai kesalahan lewat diam-diam? Dan jika demikian, apakah Anda secara eksplisit membungkamnya dengan menggunakannya dengan cara ini, sehingga membuatnya oke?


Saya tidak mengacu pada situasi di mana Anda hanya dapat melakukan sesuatu dengan 1 cara; sebagai contoh:

try:
    import foo
except ImportError:
    import baz
chown
sumber

Jawaban:

162

Anda harus memilih try/exceptlebih if/elsejika itu menghasilkan

  • percepatan (misalnya dengan mencegah pencarian ekstra)
  • kode lebih bersih (lebih sedikit baris / lebih mudah dibaca)

Seringkali, ini berjalan seiring.


percepatan

Dalam kasus mencoba menemukan elemen dalam daftar panjang dengan:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

mencoba, kecuali adalah pilihan terbaik ketika indexmungkin ada dalam daftar dan IndexError biasanya tidak dimunculkan. Dengan cara ini Anda menghindari kebutuhan pencarian ekstra oleh if index < len(my_list).

Python mendorong penggunaan pengecualian, yang Anda tangani adalah frasa dari Dive Into Python . Contoh Anda tidak hanya menangani pengecualian (dengan anggun), daripada membiarkannya lewat secara diam - diam , juga pengecualian terjadi hanya dalam kasus luar biasa indeks tidak ditemukan (karenanya kata pengecualian !).


kode bersih

Dokumentasi resmi Python menyebutkan EAFP : Lebih mudah meminta pengampunan daripada izin dan Rob Knight mencatat bahwa menangkap kesalahan daripada menghindarinya , dapat menghasilkan kode yang lebih bersih, lebih mudah dibaca. Teladannya mengatakan seperti ini:

Lebih buruk (LBYL 'lihat sebelum Anda melompat') :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Lebih baik (EAFP: Lebih mudah meminta maaf daripada izin) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None
Remi
sumber
if index in mylistmenguji apakah indeks adalah elemen dari mylist, bukan indeks yang mungkin. Anda justru menginginkannya if index < len(mylist).
ychaouche
1
Ini masuk akal, tetapi di mana saya menemukan dokumentasi yang menjelaskan pengecualian apa yang mungkin dapat dilemparkan untuk int (). docs.python.org/3/library/functions.html#int Saya tidak dapat menemukan info ini di sini.
BrutalSimplicity
Sebaliknya, Anda dapat menambahkan kasus ini (dari off. Doc ) ke jawaban Anda, ketika Anda if/elsetry/catch
menginginkannya
21

Dalam kasus khusus ini, Anda harus menggunakan sesuatu yang lain sama sekali:

x = myDict.get("ABC", "NO_ABC")

Namun secara umum: Jika Anda berharap pengujian sering gagal, gunakan if. Jika pengujian relatif mahal daripada hanya mencoba operasi dan menangkap pengecualian jika gagal, gunakan try. Jika tidak satu pun dari kondisi ini berlaku, lakukan apa pun yang lebih mudah dibaca.

senja-tidak aktif-
sumber
2
1 untuk penjelasan di bawah contoh kode, yang tepat.
ktdrv
4
Saya pikir cukup jelas ini bukan yang dia tanyakan, dan dia sekarang mengedit postingan untuk membuatnya lebih jelas.
agf
9

Menggunakan trydan exceptsecara langsung daripada di dalam ifpenjaga harus selalu dilakukan jika ada kemungkinan kondisi balapan. Misalnya, jika Anda ingin memastikan bahwa direktori ada, jangan lakukan ini:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Jika utas atau proses lain membuat direktori antara isdirdan mkdir, Anda akan keluar. Sebaliknya, lakukan ini:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Itu hanya akan keluar jika direktori 'foo' tidak dapat dibuat.

John Cowan
sumber
7

Jika sepele untuk memeriksa apakah sesuatu akan gagal sebelum Anda melakukannya, Anda mungkin harus mendukungnya. Lagi pula, membuat pengecualian (termasuk pelacakan balik yang terkait) membutuhkan waktu.

Pengecualian harus digunakan untuk:

  1. hal-hal yang tidak terduga, atau ...
  2. hal-hal di mana Anda perlu melompat lebih dari satu tingkat logika (misalnya ketika a breaktidak membuat Anda cukup jauh), atau ...
  3. hal-hal di mana Anda tidak tahu persis apa yang akan menangani pengecualian sebelumnya, atau ...
  4. hal-hal di mana memeriksa kegagalan sebelumnya mahal (relatif terhadap hanya mencoba operasi)

Perhatikan bahwa seringkali, jawaban sebenarnya adalah "tidak keduanya" - misalnya, pada contoh pertama Anda, yang seharusnya Anda lakukan hanyalah menggunakan .get()untuk memberikan default:

x = myDict.get('ABC', 'NO_ABC')
Amber
sumber
kecuali yang if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'sebenarnya seringkali lebih cepat daripada menggunakan get, sayangnya. Tidak mengatakan ini adalah kriteria yang paling penting, tetapi itu adalah sesuatu yang harus diperhatikan.
agf
@agf: Lebih baik tulis kode yang jelas dan ringkas. Jika ada yang perlu dioptimalkan nanti, mudah untuk kembali dan menulis ulang, tetapi c2.com/cgi/wiki?PrematureOptimization
Kuning
Saya tahu itu; Maksud saya adalah itu if / elsedan try / exceptdapat memiliki tempat mereka bahkan ketika ada alternatif khusus kasus karena mereka memiliki karakteristik kinerja yang berbeda.
agf
@agf, tahukah Anda jika metode get () akan ditingkatkan di versi mendatang menjadi (setidaknya) secepat mencari secara eksplisit? Omong-omong, ketika mencari dua kali (seperti jika 'ABC' di d: d ['ABC']), coba: d ['ABC'] kecuali KeyError: ... bukan yang tercepat?
Remi
2
@Remi Bagian lambat dari .get()adalah pencarian atribut dan overhead panggilan fungsi pada tingkat Python; menggunakan kata kunci pada built-in pada dasarnya langsung ke C. Saya tidak berpikir itu akan menjadi terlalu cepat dalam waktu dekat. Sejauh ifvs. try, metode read dict.get () mengembalikan sebuah pointer yang memiliki beberapa info performa. Rasio hit untuk miss matter ( trybisa lebih cepat jika kuncinya hampir selalu ada) seperti halnya ukuran kamus.
agf
6

Seperti yang disebutkan oleh posting lain, itu tergantung pada situasinya. Ada beberapa bahaya dengan menggunakan coba / kecuali sebagai pengganti memeriksa validitas data Anda terlebih dahulu, terutama saat menggunakannya pada proyek yang lebih besar.

  • Kode di blok percobaan mungkin memiliki kesempatan untuk mendatangkan segala macam malapetaka sebelum pengecualian tertangkap - jika Anda secara proaktif memeriksa sebelumnya dengan pernyataan jika Anda dapat menghindari ini.
  • Jika kode yang dipanggil dalam blok percobaan Anda memunculkan tipe pengecualian umum, seperti TypeError atau ValueError, Anda mungkin tidak benar-benar menangkap pengecualian yang sama dengan yang Anda harapkan untuk ditangkap - itu mungkin sesuatu yang lain yang memunculkan kelas pengecualian yang sama sebelum atau bahkan setelah mencapai garis di mana pengecualian Anda dapat dimunculkan.

mis., misalkan Anda memiliki:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError tidak mengatakan apa-apa tentang apakah itu terjadi saat mencoba mendapatkan elemen index_list atau my_list.

Peter
sumber
4

Haruskah menggunakan try alih-alih if diartikan sebagai kesalahan yang lewat secara diam-diam? Dan jika demikian, apakah Anda secara eksplisit membungkamnya dengan menggunakannya dengan cara ini, sehingga membuatnya oke?

Menggunakan tryberarti mengakui bahwa suatu kesalahan mungkin berlalu, yang merupakan kebalikan dari membuatnya berlalu secara diam-diam. Menggunakan exceptmenyebabkannya tidak lewat sama sekali.

Penggunaan try: except:lebih disukai dalam kasus di mana if: else:logika lebih rumit. Sederhana lebih baik daripada kompleks; kompleks lebih baik daripada rumit; dan lebih mudah meminta maaf daripada izin.

Apa "kesalahan tidak boleh lewat diam-diam" adalah peringatan tentang, adalah kasus di mana kode dapat memunculkan pengecualian yang Anda ketahui, dan di mana desain Anda mengakui kemungkinan itu, tetapi Anda belum merancang dengan cara untuk menangani pengecualian. Membungkam kesalahan secara eksplisit, dalam pandangan saya, akan melakukan sesuatu seperti passdalam exceptblok, yang seharusnya hanya dilakukan dengan pemahaman bahwa "tidak melakukan apa-apa" sebenarnya adalah penanganan kesalahan yang benar dalam situasi tertentu. (Ini adalah salah satu dari beberapa saat di mana saya merasa komentar dalam kode yang ditulis dengan baik mungkin benar-benar diperlukan.)

Namun, dalam contoh khusus Anda, tidak ada yang sesuai:

x = myDict.get('ABC', 'NO_ABC')

Alasan semua orang menunjukkan hal ini - meskipun Anda mengakui keinginan Anda untuk memahami secara umum, dan ketidakmampuan untuk memberikan contoh yang lebih baik - adalah bahwa langkah-langkah yang setara sebenarnya ada di banyak kasus, dan mencarinya adalah langkah pertama dalam memecahkan masalah.

Karl Knechtel
sumber
3

Setiap kali Anda menggunakan try/exceptaliran kontrol, tanyakan pada diri Anda:

  1. Apakah mudah untuk melihat kapan trypemblokiran berhasil dan kapan gagal?
  2. Apakah Anda mengetahui semua efek samping di dalam tryblok?
  3. Apakah Anda mengetahui semua kasus di mana tryblok memunculkan pengecualian?
  4. Jika implementasi tryblok berubah, apakah aliran kontrol Anda akan tetap berfungsi seperti yang diharapkan?

Jika jawaban untuk satu atau lebih dari pertanyaan ini adalah 'tidak', mungkin ada banyak pengampunan untuk diminta; kemungkinan besar dari diri Anda di masa depan.


Sebuah contoh. Saya baru-baru ini melihat kode dalam proyek yang lebih besar yang terlihat seperti ini:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

Berbicara dengan programmer ternyata aliran kontrol yang dimaksud adalah:

Jika x adalah bilangan bulat, lakukan y = foo (x).

Jika x adalah daftar bilangan bulat, lakukan y = bar (x).

Ini berhasil karena foomembuat kueri database dan kueri akan berhasil jika xberupa integer dan melempar ProgrammingErrorjika xadalah daftar.

Menggunakan try/exceptadalah pilihan yang buruk di sini:

  1. Nama pengecualian,, ProgrammingErrortidak memberikan masalah sebenarnya (yang xbukan bilangan bulat), yang membuatnya sulit untuk melihat apa yang sedang terjadi.
  2. The ProgrammingErrordinaikkan selama panggilan database, yang waktu limbah. Hal-hal akan menjadi sangat mengerikan jika ternyata foomenulis sesuatu ke database sebelum mengeluarkan pengecualian, atau mengubah status beberapa sistem lain.
  3. Tidak jelas apakah ProgrammingErrorhanya dimunculkan ketika xdaftar bilangan bulat. Misalkan ada kesalahan ketik dalam fooquery database. Ini mungkin juga menimbulkan a ProgrammingError. Konsekuensinya adalah yang bar(x)sekarang juga dipanggil when xis a integer. Ini mungkin menimbulkan pengecualian samar atau menghasilkan hasil yang tidak terduga.
  4. The try/exceptblok menambahkan persyaratan untuk semua implementasi masa depan foo. Setiap kali kita berubah foo, sekarang kita harus memikirkan bagaimana menangani daftar dan memastikan bahwa itu melempar ProgrammingErrordan tidak, katakanlah, AttributeErrorkesalahan atau tidak sama sekali.
Elias Strehle
sumber
0

Untuk arti umum, Anda dapat mempertimbangkan untuk membaca Idiom dan Anti-Idiom dengan Python: Pengecualian .

Dalam kasus khusus Anda, seperti yang dinyatakan orang lain, Anda harus menggunakan dict.get():

dapatkan (kunci [, default])

Kembalikan nilai untuk kunci jika kunci ada dalam kamus, jika tidak default. Jika default tidak diberikan, defaultnya adalah None, sehingga metode ini tidak pernah memunculkan KeyError.

etuardu
sumber
Saya tidak berpikir tautan tersebut mencakup situasi ini sama sekali. Contohnya adalah tentang menangani hal - hal yang merepresentasikan kesalahan nyata , bukan tentang apakah akan menggunakan penanganan pengecualian untuk situasi yang diharapkan.
agf
Tautan pertama sudah usang.
Timofei Bondarev