Python - 'jika foo di dict' vs 'coba: dict [foo]'

24

Ini bukan pertanyaan tentang sifat mengetik bebek dan lebih banyak tentang tetap pythonic, saya kira.

Pertama-tama - ketika berhadapan dengan dikt, khususnya ketika struktur dikt cukup dapat diprediksi dan kunci yang diberikan biasanya tidak ada tetapi kadang-kadang ada, saya pertama kali memikirkan dua pendekatan:

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

Dan tentu saja Ye Olde pendekatan 'maaf vs izin'.

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

Sebagai seorang pria pekerja harian Python, saya merasa seperti saya melihat yang terakhir disukai banyak, yang hanya terasa aneh kurasa karena dalam Python docs try/exceptstampaknya lebih disukai ketika ada kesalahan yang sebenarnya, yang bertentangan dengan, um ... tidak adanya kesuksesan?

Jika dikte sesekali tidak memiliki kunci dalam myDict, dan diketahui bahwa itu tidak akan selalu memiliki kunci itu, apakah percobaan / kecuali secara kontekstual menyesatkan? Ini bukan kesalahan pemrograman, ini hanya fakta dari data - dikt ini hanya tidak memiliki kunci tertentu.

Ini tampaknya sangat penting ketika Anda melihat sintaks coba / kecuali / lain, yang terlihat sangat berguna ketika datang untuk memastikan bahwa mencoba tidak menangkap terlalu banyak kesalahan. Anda dapat melakukan sesuatu seperti:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

Bukankah itu akan menyebabkan menelan semua jenis kesalahan aneh yang mungkin merupakan hasil dari beberapa kode buruk? Kode di atas mungkin hanya mencegah Anda melihat bahwa Anda mencoba untuk menambahkan 2 + {}dan Anda mungkin tidak pernah menyadari bahwa beberapa bagian dari kode Anda telah salah besar. Saya tidak menyarankan bahwa kita harus Memeriksa Semua Jenis, itu sebabnya itu Python dan bukan JavaScript - tapi sekali lagi dengan konteks coba / kecuali, sepertinya itu seharusnya menangkap program melakukan sesuatu yang seharusnya tidak dilakukan, sebagai gantinya memungkinkan untuk melanjutkan.

Saya menyadari contoh di atas adalah sesuatu dari argumen manusia jerami, dan pada kenyataannya sengaja buruk. Tetapi mengingat kredo pythonic better to ask forgiveness than permissionsaya tidak bisa membantu tetapi merasa seperti itu menimbulkan pertanyaan di mana garis di pasir sebenarnya adalah antara aplikasi yang benar jika / selain vs coba / kecuali adalah, khususnya ketika Anda tahu apa yang diharapkan dari data yang Anda kerjakan.

Saya bahkan tidak berbicara tentang masalah kecepatan atau praktik terbaik di sini, saya hanya agak diam-diam bingung dengan diagram Venn yang dirasakan tentang kasus-kasus di mana sepertinya itu bisa berjalan baik, tetapi orang-orang keliru dalam upaya mencoba / kecuali karena 'seseorang di suatu tempat mengatakan itu Pythonic'. Sudahkah saya menarik kesimpulan yang salah tentang penerapan sintaks ini?


sumber
Saya setuju bahwa kecenderungan terhadap coba-kecuali beresiko menyembunyikan kesalahan. Pendapat saya adalah bahwa Anda umumnya harus menghindari coba-kecuali untuk kondisi yang Anda harapkan (tentu saja beberapa pengecualian berlaku untuk aturan ini).
Gordon Bean

Jawaban:

26

Gunakan metode get saja:

some_dict.get(the_key, default_value)

... di mana default_valuenilai dikembalikan jika the_keytidak dalam some_dict. Jika Anda menghilangkan default_value, Nonedikembalikan jika kunci tidak ada.

Secara umum, dalam Python, orang cenderung lebih suka mencoba / kecuali daripada memeriksa sesuatu terlebih dahulu - lihat entri EAFP di glosarium . Perhatikan bahwa banyak fungsi "tes untuk keanggotaan" menggunakan pengecualian di belakang layar.

detly
sumber
Bukankah ini masalah yang sama? Jika kita mencoba untuk bekerja dengan dictdan melakukan pekerjaan berdasarkan apa yang ditemukan, rasanya seperti if myDict.get(a_key) is not None: do_work(myDict[a_key])lebih Wordier dan contoh lain dari tampilan implisit sebelum melompat - ditambah IMHO sedikit lebih mudah dibaca. Mungkin saya tidak mengerti dict.get(a_key, a_default)dalam konteks yang benar, tetapi sepertinya ini adalah awal dari penulisan 'not-a-switch-statement if / elses' atau nilai magis. Saya suka apa yang dilakukan metode ini, saya akan memeriksanya lebih dalam.
1
@Stick - Anda melakukan:, data = myDict.get(a_key, default)dan do_workakan melakukan hal yang benar ketika diberikan default(mis. None) Atau Anda lakukan if data: do_work(data). Hanya satu pencarian diperlukan dalam kedua kasus. (Mungkin if data is not None: ..., dalam kedua kasus itu, masih hanya satu pencarian dan kemudian logika Anda sendiri dapat mengambil alih.)
detly
1
Jadi saya telah memutuskan bahwa dict.get () telah cukup menghemat banyak upaya dalam proyek saya saat ini. Ini telah mengurangi jumlah baris saya, membersihkan banyak try/except/elsesampah yang tampak mengerikan dan secara keseluruhan meningkatkan IMHO keterbacaan kode saya. Saya telah belajar pelajaran berharga yang pernah saya dengar sebelumnya tetapi gagal untuk mengingat: "Jika Anda merasa terlalu banyak menulis kode, berhentilah dan lihat fitur-fitur bahasa." Terima kasih!!
@Stick - senang mendengarnya :) Saya suka saran itu, saya belum pernah mendengarnya sebelumnya.
detly
1
Secara teknis, mana yang tercepat?
Olivier Pons
4

Apakah kunci yang hilang merupakan aturan atau pengecualian ?

Dari sudut pandang pragmatis, melempar / menangkap jauh lebih lambat daripada memeriksa apakah kuncinya ada dalam tabel. Jika kunci yang hilang adalah kejadian umum - Anda harus menggunakan kondisi.

Hal lain yang mendukung kondisi adalah klausa lain - jauh lebih mudah untuk memahami ketika salah satu blok jika dijalankan, pertimbangkan bahwa bahkan kelas pengecualian yang sama dapat dilemparkan dari beberapa pernyataan di blok coba.

Kode dalam kecuali klausa tidak boleh menjadi bagian dari cacat normal (mis. Jika kunci tidak ada, tambahkan) - itu harus menangani kesalahan.

Eugene
sumber
4
"Dari sudut pandang pragmatis, melempar / menangkap jauh lebih lambat daripada memeriksa apakah kuncinya ada dalam tabel" - tidak. Belum tentu. Terakhir saya periksa, implementasi Python yang paling umum melakukan try/ catchversi lebih cepat.
Detly
2
Lempar / tangkap dengan python tidak terlalu besar dari segi performa, sampai-sampai sering digunakan untuk kontrol aliran dalam python. Selain itu, ada kemungkinan dalam beberapa situasi di mana dikt itu dapat dimodifikasi di antara tes dan akses.
whatsisname
@whatsisname - Saya berpikir untuk menambahkan itu ke jawaban saya, tetapi TBH jika Anda memiliki bahaya ras seperti itu, Anda memiliki masalah yang jauh lebih besar daripada EAFP vs LBYL: P
detly
1

Dalam semangat menjadi "pythonic" dan, khususnya, mengetik bebek - coba / kecuali terlihat hampir rapuh bagi saya.

Yang Anda inginkan hanyalah sesuatu dengan akses seperti dict, tetapi __getitem__dapat diganti untuk melakukan sesuatu yang tidak sesuai harapan Anda. Misalnya, menggunakan defaultdictdari perpustakaan standar:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

Karena nilai pengembalian defaultnya adalah Falsy, pengecekan akses seperti itu akan berfungsi sebagian besar waktu dengan baik .. Tampaknya juga menjaga kode tetap sederhana, itulah sebabnya rekan kerja saya dan saya telah melakukannya selama berbulan-bulan.

Sayangnya, beberapa bulan kemudian, kunci tambahan ini benar-benar berakhir menyebabkan masalah dan bug yang sulit dilacak, karena 0menjadi nilai yang valid. Dan kadang-kadang kita akan menggunakan inuntuk memeriksa apakah kunci ada, membuat kode melakukan hal-hal yang berbeda padahal seharusnya identik.

Alasan saya menyarankan itu terlihat rusak adalah karena dalam semangat pengetikan bebek Python, semua yang Anda inginkan adalah sesuatu seperti dict, dan defaultdict cocok dengan persyaratan itu, seperti halnya benda apa pun yang mungkin Anda buat yang mewarisinya dict. Anda tidak dapat menjamin penelepon tidak akan mengubah implementasinya nanti, jadi Anda harus melakukan hal dengan efek samping paling sedikit.

Gunakan versi pertama if myKey in mydict,.


Juga, perhatikan bahwa ini secara khusus tentang contoh dalam pertanyaan. Pengakuan python "Lebih baik untuk meminta maaf daripada izin" sebagian besar dimaksudkan untuk memastikan Anda benar-benar bertindak dengan benar ketika Anda tidak mendapatkan apa yang Anda inginkan. Misalnya, memeriksa apakah ada file dan kemudian mencoba membacanya - setidaknya 3 hal yang dapat saya pikirkan di atas kepala saya bisa salah:

  • Syarat lomba adalah bahwa file tersebut mungkin telah dihapus / dipindahkan / dll
  • Anda tidak memiliki izin baca di file
  • File telah rusak

Yang pertama Anda dapat mencoba untuk "meminta izin", tetapi pada dasarnya kondisi ras itu tidak selalu berhasil; yang kedua terlalu mudah untuk dilupakan; dan yang ketiga tidak dapat diperiksa tanpa mencoba membaca file. Dalam hal apapun, apa yang Anda lakukan setelah gagal hampir pasti akan sama, sehingga dalam hal ini contoh, adalah lebih baik untuk "meminta maaf" dengan menggunakan mencoba / kecuali.

Izkata
sumber