Come gestire le eccezioni in una lista di comprensione?


120

Ho un elenco di comprensione in Python in cui ogni iterazione può generare un'eccezione.

Ad esempio , se ho:

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

[1/egg for egg in eggs]

Otterrò ZeroDivisionErrorun'eccezione nel 3 ° elemento.

Come posso gestire questa eccezione e continuare l'esecuzione della comprensione della lista?

L'unico modo a cui posso pensare è usare una funzione di supporto:

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

Ma questo mi sembra un po 'macchinoso.

C'è un modo migliore per farlo in Python?

Nota: questo è un semplice esempio (vedi " per esempio " sopra) che ho escogitato perché il mio esempio reale richiede un contesto. Non mi interessa evitare errori di divisione per zero ma gestire le eccezioni in una lista di comprensione.


4
C'è un PEP 463 per aggiungere un'espressione per gestire le eccezioni. Nel tuo esempio sarebbe [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Ma è ancora in modalità bozza. La mia sensazione istintiva è che non verrà accettato. Le espressioni Imho possono diventare troppo disordinate (controllare più eccezioni, avere combinazioni più complesse (più operatori logici, comprensioni complesse, ecc.)
CFI

1
Nota che per questo esempio specifico , potresti usare un numpy ndarraycon le impostazioni appropriate in np.seterr. Ciò risulterebbe 1/0 = nan. Ma mi rendo conto che non si generalizza ad altre situazioni in cui sorge questa esigenza.
gerrit

Risposte:


96

Non esiste un'espressione incorporata in Python che ti consenta di ignorare un'eccezione (o restituire valori alternativi e c in caso di eccezioni), quindi è impossibile, letteralmente parlando, "gestire le eccezioni in una comprensione di lista" perché una comprensione di lista è un'espressione contenente altre espressioni, niente di più (cioè, nessuna istruzione e solo le istruzioni possono catturare / ignorare / gestire eccezioni).

Le chiamate di funzione sono espressioni e i corpi delle funzioni possono includere tutte le istruzioni che desideri, quindi delegare la valutazione della sottoespressione soggetta a eccezioni a una funzione, come hai notato, è una soluzione possibile (altre, se fattibili, sono verifica dei valori che potrebbero provocare eccezioni, come suggerito anche in altre risposte).

Le risposte corrette alla domanda "come gestire le eccezioni in una comprensione di lista" esprimono tutte parte di tutta questa verità: 1) letteralmente, cioè lessicalmente NELLA comprensione stessa, non puoi; 2) in pratica, si delega il lavoro a una funzione o si verifica la presenza di valori inclini all'errore quando possibile. La tua ripetuta affermazione che questa non è una risposta è quindi infondata.


14
Vedo. Quindi una risposta completa sarebbe che dovrei: 1. usare una funzione 2. non usare la comprensione della lista 3. cercare di prevenire l'eccezione piuttosto che gestirla.
Nathan Fellman

9
Non vedo "non usare le liste di comprensione" come parte della risposta a "come gestire le eccezioni nelle liste di comprensione", ma immagino che potresti ragionevolmente vederlo come una possibile conseguenza di " lessicalmente NELLA LC, non è possibile gestire le eccezioni ", che è effettivamente la prima parte della risposta letterale.
Alex Martelli

Riesci a rilevare un errore in un'espressione del generatore o nella comprensione del generatore?

1
@AlexMartelli, una clausola tranne sarebbe così difficile da lavorare nelle future versioni di Python? [x[1] for x in list except IndexError pass]. L'interprete non potrebbe creare una funzione temporanea da provare x[1]?
alancalvitti

@ Nathan, 1,2,3 sopra si trasformano in tremendi mal di testa nei flussi di dati funzionali in cui 1. si vogliono normalmente funzioni inline tramite lambda; 2. l'alternativa è usare molti cicli for annidati che violano il paradigma funzionale e portano a codice soggetto a errori; 3. spesso gli errori sono dataset complessi ad-hoc e latenti che sono, come la parola latina significa dati, dati, quindi non possono essere facilmente prevenuti.
alancalvitti

118

Mi rendo conto che questa domanda è piuttosto vecchia, ma puoi anche creare una funzione generale per rendere più facile questo genere di cose:

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

Quindi, nella tua comprensione:

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

Ovviamente puoi fare in modo che la funzione handle predefinita sia quella che vuoi (diciamo che preferisci restituire 'Nessuno' per impostazione predefinita).

Spero che questo aiuti te o eventuali futuri spettatori di questa domanda!

Nota: in python 3, creerei solo la parola chiave dell'argomento 'handle' e la inserirò alla fine dell'elenco degli argomenti. Ciò renderebbe molto più naturale il passaggio di argomenti e simili.


2
estremamente utile, grazie. Anche se sono d'accordo con i commenti teorici, questo mostra un approccio pratico per risolvere un problema che ho avuto ripetutamente.
Paul

2
Ottima risposta. Una mod che suggerirei è anche di passaggio argse kwargsda gestire. In questo modo potresti tornare ad esempio egginvece di un hardcoded 0o un'eccezione mentre stai facendo.
Mad Physicist

3
Potresti anche volere il tipo di eccezione come argomento opzionale (i tipi di eccezione possono essere parametrizzati?), In modo che le eccezioni impreviste vengano lanciate verso l'alto invece di ignorare tutte le eccezioni.
00prometheus

3
@Bryan, puoi fornire il codice per "in python 3, creerei solo la parola chiave dell'argomento 'handle' e la metterei alla fine dell'elenco degli argomenti." ho provato a posizionare handledopo **kwarge ho ottenuto un file SyntaxError. Intendi dereferenziare come kwargs.get('handle',e)?
alancalvitti

21

Puoi usare

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

questo salterà semplicemente gli elementi che sono zero.


28
Questo non risponde alla domanda su come gestire le eccezioni in una lista di comprensione.
Nathan Fellman

8
umm, sì, lo fa. evita la necessità di gestire le eccezioni. sì, non è sempre la soluzione giusta, ma è comune.
Peter

3
Capisco. Ritiro il commento (anche se non lo cancellerò poiché quella breve "discussione" migliora la risposta).
Nathan Fellman

11

No, non c'è modo migliore. In molti casi puoi usare l'evitamento come fa Peter

La tua altra opzione è non usare le comprensioni

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

Sta a te decidere se è più ingombrante o meno


1
Come utilizzerei le comprensioni qui?
Nathan Fellman

@ Nathan: non lo faresti. gnibbler dice: No, non c'è un modo migliore
SilentGhost

Scusa ... ho perso il "non" nella sua risposta :-)
Nathan Fellman

4

Penso che una funzione di aiuto, come suggerito da colui che pone la domanda iniziale e anche da Bryan Head, sia buona e per nulla ingombrante. Una singola riga di codice magico che fa tutto il lavoro non è sempre possibile, quindi una funzione di supporto è una soluzione perfetta se si desidera evitare i forloop. Tuttavia lo modificherei in questo:

# 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

L'output sarà questo [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Con questa risposta hai il pieno controllo per continuare come preferisci.


Bello. Questo sembra molto simile al Eithertipo in alcuni linguaggi di programmazione funzionale (come Scala), dove un Eitherpuò contenere un valore di un tipo o di un altro, ma non di entrambi. L'unica differenza è che in quelle lingue è idiomatico mettere l'errore a sinistra e il valore a destra. Ecco un articolo con ulteriori informazioni .
Alex Palmer

3

Non ho visto nessuna risposta menzionarlo. Ma questo esempio potrebbe essere un modo per impedire la generazione di un'eccezione per casi noti di errore.

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


Output: [1, 0, None, 0, 0]

Non è la stessa di questa risposta? stackoverflow.com/a/1528244/1084
Nathan Fellman

C'è una sottile differenza. Il filtro viene applicato all'output anziché all'elenco di input. Come puoi vedere nell'esempio pubblicato, ho indicato "Nessuno" per il caso che potrebbe causare un'eccezione.
Slakker
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.