Bagaimana menangani pengecualian dalam pemahaman daftar?

120

Saya memiliki beberapa pemahaman daftar dengan Python di mana setiap iterasi dapat menimbulkan pengecualian.

Misalnya , jika saya memiliki:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

Saya akan mendapatkan ZeroDivisionError pengecualian di elemen ke-3.

Bagaimana saya menangani pengecualian ini dan melanjutkan eksekusi pemahaman daftar?

Satu-satunya cara yang bisa saya pikirkan adalah dengan menggunakan fungsi helper:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Tapi ini terlihat agak merepotkan bagiku.

Apakah ada cara yang lebih baik untuk melakukan ini dengan Python?

Catatan: Ini adalah contoh sederhana (lihat " misalnya " di atas) yang saya buat karena contoh nyata saya memerlukan beberapa konteks. Saya tidak tertarik untuk menghindari kesalahan bagi dengan nol tetapi dalam menangani pengecualian dalam pemahaman daftar.

Nathan Fellman
sumber
4
Ada PEP 463 untuk menambahkan ekspresi untuk menangani pengecualian. Dalam contoh Anda, itu akan terjadi [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Tapi masih dalam mode draf. Naluri saya adalah bahwa itu tidak akan diterima. Ekspresi imho bisa menjadi terlalu berantakan (memeriksa beberapa pengecualian, memiliki kombinasi yang lebih kompleks (beberapa operator logika, pemahaman kompleks, dll)
cfi
1
Perhatikan bahwa untuk contoh khusus ini , Anda dapat menggunakan numpy ndarraydengan pengaturan yang sesuai di np.seterr. Itu akan menghasilkan 1/0 = nan. Tapi saya menyadari itu tidak menggeneralisasi situasi lain di mana kebutuhan ini muncul.
gerrit

Jawaban:

96

Tidak ada ekspresi bawaan dalam Python yang memungkinkan Anda mengabaikan pengecualian (atau mengembalikan nilai alternatif & c jika ada pengecualian), jadi tidak mungkin, secara harfiah, untuk "menangani pengecualian dalam pemahaman daftar" karena pemahaman daftar adalah ekspresi mengandung ekspresi lain, tidak lebih (yaitu, tidak pernyataan, dan hanya pernyataan yang dapat menangkap / mengabaikan / menangani pengecualian).

Panggilan fungsi adalah ekspresi, dan badan fungsi dapat menyertakan semua pernyataan yang Anda inginkan, jadi mendelegasikan evaluasi sub-ekspresi yang rentan terhadap pengecualian ke suatu fungsi, seperti yang Anda perhatikan, adalah salah satu solusi yang layak (yang lain, jika memungkinkan, adalah memeriksa nilai-nilai yang mungkin memicu pengecualian, seperti yang juga disarankan dalam jawaban lain).

Tanggapan yang benar untuk pertanyaan "bagaimana menangani pengecualian dalam pemahaman daftar" semuanya mengungkapkan sebagian dari semua kebenaran ini: 1) secara harfiah, yaitu secara leksikal DALAM pemahaman itu sendiri, Anda tidak bisa; 2) secara praktis, Anda mendelegasikan pekerjaan ke suatu fungsi atau memeriksa nilai-nilai rawan kesalahan jika memungkinkan. Klaim berulang Anda bahwa ini bukan jawaban dengan demikian tidak berdasar.

Alex Martelli
sumber
14
Saya melihat. Jadi jawaban lengkapnya adalah saya harus: 1. menggunakan fungsi 2. tidak menggunakan pemahaman daftar 3. mencoba mencegah pengecualian daripada menanganinya.
Nathan Fellman
9
Saya tidak melihat "tidak menggunakan pemahaman daftar" sebagai bagian dari jawaban untuk "cara menangani pengecualian dalam pemahaman daftar", tetapi saya rasa Anda dapat secara wajar melihatnya sebagai konsekuensi yang mungkin dari " secara leksikal DALAM LC, tidak mungkin untuk menangani pengecualian ", yang memang merupakan bagian pertama dari jawaban literal.
Alex Martelli
Dapatkah Anda menemukan kesalahan dalam ekspresi generator atau pemahaman generator?
1
@AlexMartelli, apakah klausa pengecualian akan sesulit itu untuk digunakan pada versi python di masa mendatang? [x[1] for x in list except IndexError pass]. Bisakah penerjemah membuat fungsi sementara untuk dicoba x[1]?
alancalvitti
@Nathan, 1,2,3 di atas berubah menjadi sakit kepala yang luar biasa dalam aliran data fungsional di mana 1. seseorang biasanya ingin menyebariskan fungsi melalui lambda; 2. Alternatifnya adalah menggunakan banyak nested for loops yang melanggar paradigma fungsional dan menyebabkan kode rawan error; 3. Seringkali kesalahan adalah kumpulan data kompleks ad-hoc dan laten yang, sebagaimana kata latin untuk data berarti, diberikan, sehingga tidak dapat dengan mudah dicegah.
alancalvitti
118

Saya menyadari pertanyaan ini sudah cukup lama, tetapi Anda juga dapat membuat fungsi umum untuk mempermudah hal semacam ini:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

Kemudian, dalam pemahaman Anda:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

Anda tentu saja dapat membuat fungsi pegangan default apa pun yang Anda inginkan (katakanlah Anda lebih suka mengembalikan 'Tidak Ada' secara default).

Semoga ini bisa membantu Anda atau pemirsa pertanyaan ini di masa mendatang!

Catatan: di python 3, saya akan membuat kata kunci argumen 'handle' saja, dan meletakkannya di akhir daftar argumen. Ini akan membuat argumen yang lewat dan semacamnya melalui tangkapan jauh lebih alami.

Bryan Head
sumber
2
sangat berguna, terima kasih. Meskipun saya setuju dengan komentar teoretis, ini menunjukkan pendekatan praktis untuk memecahkan masalah yang saya alami berulang kali.
Paul
2
Jawaban yang bagus. Satu mod saya sarankan lewat argsdan kwargsmelalui pegangan juga. Dengan cara itu Anda bisa mengembalikan kata eggalih-alih hardcode 0, atau pengecualian seperti yang Anda lakukan.
Fisikawan Gila
3
Anda mungkin juga menginginkan tipe pengecualian sebagai argumen opsional (dapatkah tipe pengecualian menjadi parametrised?), Sehingga pengecualian tak terduga dilemparkan ke atas daripada mengabaikan semua pengecualian.
00prometheus
3
@ Bryan, dapatkah Anda memberikan kode untuk "dalam python 3, saya akan membuat kata kunci argumen 'pegangan' saja, dan meletakkannya di akhir daftar argumen." mencoba menempatkan handlesetelah **kwargdan mendapat SyntaxError. Apakah maksud Anda dereferensi sebagai kwargs.get('handle',e)?
alancalvitti
21

Kamu bisa memakai

[1/egg for egg in eggs if egg != 0]

ini hanya akan melewatkan elemen yang nol.

Peter
sumber
28
Ini tidak menjawab pertanyaan tentang bagaimana menangani pengecualian dalam pemahaman daftar.
Nathan Fellman
8
umm, ya, benar. itu meniadakan kebutuhan untuk menangani pengecualian. ya, ini bukanlah solusi yang tepat sepanjang waktu, tetapi ini adalah solusi yang umum.
Peter
3
Saya mengerti. Saya menarik kembali komentar tersebut (meskipun saya tidak akan menghapusnya karena 'diskusi' singkat itu memperbaiki jawabannya).
Nathan Fellman
11

Tidak, tidak ada cara yang lebih baik. Dalam banyak kasus, Anda dapat menggunakan penghindaran seperti yang dilakukan Peter

Pilihan Anda yang lain adalah tidak menggunakan pemahaman

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Terserah Anda untuk memutuskan apakah itu lebih rumit atau tidak

John La Rooy
sumber
1
Bagaimana saya menggunakan pemahaman di sini?
Nathan Fellman
@Nathan: Anda tidak akan. gnibbler mengatakan: Tidak, tidak ada cara yang lebih baik
SilentGhost
Maaf ... Saya melewatkan 'tidak' dalam jawabannya :-)
Nathan Fellman
4

Saya pikir fungsi pembantu, seperti yang disarankan oleh orang yang mengajukan pertanyaan awal dan juga Bryan Head, itu bagus dan tidak merepotkan sama sekali. Satu baris kode ajaib yang melakukan semua pekerjaan tidak selalu memungkinkan sehingga fungsi pembantu adalah solusi sempurna jika seseorang ingin menghindari forloop. Namun saya akan memodifikasinya menjadi yang ini:

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

Outputnya akan seperti ini [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Dengan jawaban ini Anda memiliki kendali penuh untuk melanjutkan dengan cara apa pun yang Anda inginkan.

Elmex80s
sumber
Bagus. Ini terlihat sangat mirip dengan Eithertipe dalam beberapa bahasa pemrograman fungsional (seperti Scala), di mana sebuah Eitherdapat berisi nilai dari satu jenis atau lainnya, tetapi tidak keduanya. Satu-satunya perbedaan adalah bahwa dalam bahasa-bahasa tersebut adalah idiomatis untuk meletakkan kesalahan di sisi kiri dan nilai di sisi kanan. Berikut artikel dengan informasi lebih lanjut .
Alex Palmer
3

Saya tidak melihat jawaban apapun menyebutkan ini. Tapi contoh ini akan menjadi salah satu cara untuk mencegah pengecualian dimunculkan untuk kasus gagal yang diketahui.

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]
Slakker
sumber
Bukankah itu sama dengan jawaban ini? stackoverflow.com/a/1528244/1084
Nathan Fellman
Ada perbedaan yang halus. Pemfilteran diterapkan pada keluaran daripada daftar masukan. Seperti yang Anda lihat dalam contoh yang diposting, saya telah menyatakan "Tidak Ada" untuk kasus yang mungkin menyebabkan pengecualian.
Slakker