Menggunakan try vs if dalam python

145

Apakah ada alasan untuk memutuskan mana tryatau ifmembangun yang akan digunakan, ketika menguji variabel untuk memiliki nilai?

Misalnya, ada fungsi yang mengembalikan daftar atau tidak mengembalikan nilai. Saya ingin memeriksa hasilnya sebelum memprosesnya. Manakah dari berikut ini yang lebih disukai dan mengapa?

result = function();
if (result):
    for r in result:
        #process items

atau

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

Diskusi terkait:

Memeriksa keberadaan anggota dalam Python

artdanil
sumber
Ingatlah bahwa jika stanza #proses item Anda dapat melontarkan TypeError, Anda harus membungkusnya dengan percobaan lain: kecuali: blokir. Untuk contoh khusus ini saya hanya dengan menggunakan if:
richo

Jawaban:

237

Anda sering mendengar bahwa Python mendorong gaya EAFP ("lebih mudah untuk meminta maaf daripada izin") daripada gaya LBYL ("lihat sebelum Anda melompat"). Bagi saya, ini masalah efisiensi dan keterbacaan.

Dalam contoh Anda (katakan bahwa alih-alih mengembalikan daftar atau string kosong, fungsinya adalah mengembalikan daftar atau None), jika Anda berharap 99% dari waktu resultitu benar-benar akan mengandung sesuatu yang dapat diubah, saya akan menggunakan try/exceptpendekatan. Akan lebih cepat jika pengecualian benar-benar luar biasa. Jika resultini Nonelebih dari 50% dari waktu, kemudian menggunakan ifmungkin lebih baik.

Untuk mendukung ini dengan beberapa pengukuran:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Jadi, sementara sebuah ifpernyataan selalu membebani Anda, hampir bebas untuk membuat try/exceptblok. Tetapi ketika Exceptionbenar - benar terjadi, biayanya jauh lebih tinggi.

Moral:

  • Ini sangat OK (dan "pythonic") untuk digunakan try/exceptuntuk kontrol aliran,
  • tetapi paling masuk akal ketika Exceptions sebenarnya luar biasa.

Dari dokumen Python:

EAFP

Lebih mudah meminta maaf daripada izin. Gaya pengkodean Python yang umum ini mengasumsikan adanya kunci atau atribut yang valid dan menangkap pengecualian jika asumsi tersebut terbukti salah. Gaya bersih dan cepat ini ditandai dengan kehadiran banyak orang trydan exceptpernyataan. Teknik ini kontras dengan gaya LBYL yang umum untuk banyak bahasa lain seperti C.

Tim Pietzcker
sumber
1
Terima kasih. Saya melihat bahwa dalam hal ini alasannya bisa menjadi harapan hasilnya.
artdanil
6
.... dan itulah satu (dari banyak) alasan mengapa sangat sulit untuk melakukan JIT optimalisasi optimal untuk Python. Sebagaimana terbukti dalam beta LuaJIT 2 baru-baru ini, bahasa dinamis bisa sangat, sangat cepat; tapi itu sangat tergantung pada desain bahasa awal dan gaya yang didukungnya. (Pada catatan terkait, desain bahasa adalah mengapa JIT JavaScirpt terbaik sekalipun tidak dapat dibandingkan dengan LuaJIT 1, apalagi 2)
Javier
1
@ 2rs2ts: Saya baru saja melakukan timing yang serupa. Dalam Python 3, try/exceptadalah 25% lebih cepat daripada if key in d:untuk kasus-kasus di mana kuncinya ada di kamus. Itu jauh lebih lambat ketika kunci tidak ada dalam kamus, seperti yang diharapkan, dan konsisten dengan jawaban ini.
Tim Pietzcker
5
Saya tahu ini adalah jawaban lama, namun: menggunakan pernyataan seperti 1/1dengan timeit bukanlah pilihan yang bagus, karena mereka akan dioptimalkan (lihat dis.dis('1/1')dan perhatikan bahwa tidak ada pembagian yang terjadi).
Andrea Corbellini
1
Ini juga tergantung pada operasi yang ingin Anda lakukan. Satu divisi cepat, menangkap kesalahan agak mahal. Tetapi bahkan dalam kasus-kasus di mana biaya penanganan pengecualian dapat diterima, pikirkan dengan hati-hati tentang apa yang Anda minta komputer lakukan. Jika operasi itu sendiri sangat mahal terlepas dari hasilnya, dan ada cara murah untuk mengetahui apakah keberhasilan itu mungkin: Gunakan. Contoh: Jangan menyalin setengah file hanya untuk menyadari bahwa tidak ada cukup ruang.
Bachsau
14

Fungsi Anda seharusnya tidak mengembalikan tipe campuran (yaitu daftar atau string kosong). Seharusnya mengembalikan daftar nilai atau hanya daftar kosong. Maka Anda tidak perlu menguji apa pun, yaitu kode Anda runtuh menjadi:

for r in function():
    # process items
Brandon
sumber
2
Saya sepenuhnya setuju dengan Anda tentang hal itu; namun, fungsinya bukan milik saya, dan saya hanya menggunakannya.
artdanil
2
@artdanil: Jadi Anda bisa membungkus fungsi itu dalam satu yang Anda tulis yang berfungsi seperti yang dipikirkan Brandon Corfman.
quamrana
24
yang menimbulkan pertanyaan: haruskah pembungkus menggunakan jika atau mencoba untuk menangani kasus yang tidak dapat diperbaiki?
jcdyer
@quamrana yang persis apa yang saya coba lakukan. Jadi seperti yang ditunjukkan @jcd, Pertanyaannya adalah tentang bagaimana saya harus menangani situasi dalam fungsi wrapper.
artdanil
12

Harap abaikan solusi saya jika kode yang saya berikan tidak jelas pada pandangan pertama dan Anda harus membaca penjelasan setelah sampel kode.

Dapatkah saya berasumsi bahwa "tidak ada nilai yang dikembalikan" berarti nilai kembali adalah Tidak ada? Jika ya, atau jika "tidak ada nilai" adalah Boolean-bijaksana Salah, Anda dapat melakukan hal berikut, karena kode Anda pada dasarnya memperlakukan "tidak ada nilai" sebagai "jangan diulang":

for r in function() or ():
    # process items

Jika function()mengembalikan sesuatu yang tidak Benar, Anda beralih di atas tuple kosong, yaitu Anda tidak menjalankan iterasi apa pun. Ini pada dasarnya adalah LBYL.

tzot
sumber
4

Contoh kedua Anda rusak - kode tidak akan pernah membuang pengecualian TypeError karena Anda dapat beralih melalui string dan daftar. Iterasi melalui string atau daftar kosong juga valid - itu akan mengeksekusi tubuh loop kali nol.

Dave Kirby
sumber
4

Manakah dari berikut ini yang lebih disukai dan mengapa?

Look Before You Leap lebih disukai dalam hal ini. Dengan pendekatan pengecualian, TypeError dapat terjadi di mana saja di tubuh loop Anda dan itu akan tertangkap dan dibuang, yang bukan yang Anda inginkan dan akan membuat debugging menjadi rumit.

(Saya setuju dengan Brandon Corfman: mengembalikan None untuk 'no items' bukan daftar kosong yang rusak. Ini adalah kebiasaan yang tidak menyenangkan dari coders Java yang tidak boleh dilihat dengan Python. Atau Java.)

bobince
sumber
4

Secara umum, kesan yang saya dapatkan adalah bahwa pengecualian harus disediakan untuk keadaan luar biasa. Jika resultdiharapkan tidak pernah kosong (tetapi mungkin, jika, misalnya, disk macet, dll), pendekatan kedua masuk akal. Di sisi lain, jika sebuah kosong resultmasuk akal dalam kondisi normal, pengujian untuk itu dengan ifpernyataan lebih masuk akal.

Saya memikirkan skenario (lebih umum):

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

bukannya yang setara:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1
Managu
sumber
Ini adalah contoh dari perbedaan antara pendekatan Tim Pietzcker menyebutkan: Yang pertama adalah LBYL, yang kedua adalah EAFP
Managu
Hanya catatan singkat yang ++tidak berfungsi dalam python, gunakan += 1saja.
tgray
3

bobince dengan bijak menunjukkan bahwa membungkus case kedua juga dapat menangkap TypeErrors dalam loop, yang bukan yang Anda inginkan. Jika Anda benar-benar ingin menggunakan percobaan, Anda dapat menguji apakah itu dapat dilakukan sebelum loop

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

Seperti yang Anda lihat, ini agak jelek. Saya tidak menyarankannya, tetapi harus disebutkan untuk kelengkapannya.

AFoglia
sumber
Di mata saya, menjalankan kesalahan tipe yang disengaja selalu gaya pengkodean yang buruk. Pengembang harus tahu apa yang diharapkan dan siap menangani nilai-nilai itu tanpa kecuali. Setiap kali operasi melakukan apa yang seharusnya dilakukan (dari sudut pandang programmer) dan hasil yang diharapkan adalah daftar atau None, selalu periksa hasilnya dengan is Noneatau is not None. Di sisi lain itu juga gaya buruk untuk meningkatkan Pengecualian untuk hasil yang sah. Pengecualian adalah untuk hal-hal yang tidak terduga. Contoh: str.find()mengembalikan -1 jika tidak ada yang ditemukan, karena pencarian itu sendiri selesai tanpa kesalahan.
Bachsau
1

Sejauh menyangkut kinerja, menggunakan blok try untuk kode yang biasanya tidak meningkatkan pengecualian lebih cepat daripada menggunakan pernyataan if setiap saat. Jadi, keputusan tergantung pada probabilitas kasus pengecualian.

grayger
sumber
-5

Sebagai aturan umum, Anda tidak boleh menggunakan try / catch atau pengecualian untuk menangani hal-hal lain untuk mengontrol aliran. Meskipun di balik layar iterasi dikendalikan melalui peningkatan StopIterationpengecualian, Anda tetap harus memilih cuplikan kode pertama Anda dari yang kedua.

ilowe
sumber
7
Ini benar di Jawa. Python memeluk EAFP cukup banyak.
fengb
3
Ini masih praktik yang aneh. Otak kebanyakan pemrogram terhubung untuk melewati EAFP dalam pengalaman saya. Mereka akhirnya bertanya-tanya mengapa jalur tertentu dipilih. Dikatakan demikian, ada waktu dan tempat untuk melanggar setiap aturan.
ilowe