Jak radzić sobie z wyjątkami w składanych listach?


121

Mam trochę list składanych w Pythonie, w których każda iteracja może zgłosić wyjątek.

Na przykład , jeśli mam:

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

[1/egg for egg in eggs]

W ZeroDivisionErrortrzecim elemencie dostanę wyjątek.

Jak mogę obsłużyć ten wyjątek i kontynuować przetwarzanie listy?

Jedyny sposób, jaki przychodzi mi do głowy, to użycie funkcji pomocniczej:

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

Ale wydaje mi się to trochę kłopotliwe.

Czy jest lepszy sposób na zrobienie tego w Pythonie?

Uwaga: to jest prosty przykład (patrz „ na przykład ” powyżej), który wymyśliłem, ponieważ mój prawdziwy przykład wymaga kontekstu. Nie interesuje mnie unikanie błędów dzielenia przez zero, ale obsługa wyjątków przy przetwarzaniu list.


4
Istnieje PEP 463, który dodaje wyrażenie obsługujące wyjątki. W twoim przykładzie byłoby to [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Ale nadal jest w trybie roboczym. Mam przeczucie, że nie zostanie to zaakceptowane. Wyrażenia Imho mogą być zbyt nieporządne (sprawdzanie wielu wyjątków, bardziej złożone kombinacje (wiele operatorów logicznych, złożone
wyrażenia

1
Zauważ, że w tym konkretnym przykładzie możesz użyć numpy ndarrayz odpowiednimi ustawieniami w np.seterr. To by skutkowało 1/0 = nan. Ale zdaję sobie sprawę, że nie dotyczy to innych sytuacji, w których pojawia się taka potrzeba.
gerrit,

Odpowiedzi:


97

W Pythonie nie ma wbudowanego wyrażenia, które pozwala zignorować wyjątek (lub zwrócić alternatywne wartości & cw przypadku wyjątków), więc dosłownie niemożliwe jest „obsłużenie wyjątków przy przetwarzaniu listowym”, ponieważ wyrażenie listy jest wyrażeniem zawierające inne wyrażenie, nic więcej (tj. brak instrukcji i tylko instrukcje mogą przechwytywać / ignorować / obsługiwać wyjątki).

Wywołania funkcji są wyrażeniami, a treści funkcji mogą zawierać wszystkie żądane instrukcje, więc delegowanie oceny wyrażenia podrzędnego podatnego na wyjątki do funkcji, jak zauważyłeś, jest jednym możliwym obejściem (inne, jeśli to możliwe, to sprawdzanie wartości, które mogą wywoływać wyjątki, co sugeruje również inne odpowiedzi).

Prawidłowe odpowiedzi na pytanie „jak postępować z wyjątkami w zrozumieniu listowym” wyrażają część całej tej prawdy: 1) dosłownie, tj. Leksykalnie, W samym zrozumieniu nie możesz; 2) praktycznie delegujesz zadanie do funkcji lub sprawdzasz wartości podatne na błędy, jeśli jest to wykonalne. Twoje powtarzające się twierdzenie, że to nie jest odpowiedź, jest zatem bezpodstawne.


14
Widzę. Tak więc kompletna odpowiedź byłaby taka, że ​​powinienem: 1. użyć funkcji 2. nie używać funkcji list złożonych 3. raczej starać się zapobiec wyjątkowi niż go obsługiwać.
Nathan Fellman

9
Nie widzę „nieużywania wyrażeń listowych” jako części odpowiedzi na „jak obsługiwać wyjątki w wyrażeniach listowych”, ale myślę, że można rozsądnie postrzegać to jako możliwą konsekwencję „ leksykalnie W LC, nie można obsłużyć wyjątki ”, co jest rzeczywiście pierwszą częścią dosłownej odpowiedzi.
Alex Martelli

Czy potrafisz złapać błąd w wyrażeniu generatora lub zrozumieniu generatora?

1
@AlexMartelli, czy klauzula wyjątku byłaby tak trudna do zastosowania w przyszłych wersjach Pythona? [x[1] for x in list except IndexError pass]. Czy tłumacz nie mógł utworzyć tymczasowej funkcji do wypróbowania x[1]?
alancalvitti

@Nathan, 1, 2, 3 powyżej, prowadzą do ogromnego bólu głowy w funkcjonalnych przepływach danych, gdzie 1. zwykle chce się wbudować funkcje poprzez lambdy; 2. alternatywą jest użycie wielu zagnieżdżonych pętli for, które naruszają paradygmat funkcjonalny i prowadzą do kodu podatnego na błędy; 3. Często błędy są doraźnymi i ukrytymi, złożonymi zbiorami danych, które, jak łacińskie słowo oznaczające dane, są podane, więc nie można ich łatwo zapobiec.
alancalvitti

119

Zdaję sobie sprawę, że to pytanie jest dość stare, ale możesz także utworzyć ogólną funkcję, aby ułatwić tego rodzaju czynności:

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

Następnie, w twoim zrozumieniu:

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

Możesz oczywiście ustawić domyślną funkcję uchwytu, jaką chcesz (powiedzmy, że wolisz domyślnie zwrócić „Brak”).

Mam nadzieję, że pomoże to Tobie lub przyszłym widzom tego pytania!

Uwaga: w Pythonie 3 utworzyłbym tylko słowo kluczowe argumentu „handle” i umieściłbym je na końcu listy argumentów. To sprawiłoby, że faktyczne przekazywanie argumentów i tego typu przechwytywanie byłoby znacznie bardziej naturalne.


2
niezwykle przydatne, dzięki. Chociaż zgadzam się z komentarzami teoretycznymi, pokazuje to praktyczne podejście do rozwiązania problemu, który miałem wielokrotnie.
Paul

2
Witam odpowiedź. Jeden mod, który zasugerowałbym, to przejście argsi przejście również kwargsdo obsługi. W ten sposób możesz powrócić, powiedz eggzamiast zakodowanego na stałe 0, lub wyjątek, tak jak robisz.
Mad Physicist,

3
Możesz również chcieć, aby typ wyjątku był opcjonalnym argumentem (czy typy wyjątków mogą być sparametryzowane?), Aby nieoczekiwane wyjątki były generowane w górę, zamiast ignorować wszystkie wyjątki.
00prometeusz

3
@Bryan, czy możesz podać kod dla „w pythonie 3 utworzyłbym tylko słowo kluczowe argumentu„ handle ”i umieściłbym je na końcu listy argumentów.” próbował umieścić handlepo **kwargi otrzymałem SyntaxError. Czy masz na myśli wyłuskać jako kwargs.get('handle',e)?
alancalvitti

21

Możesz użyć

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

spowoduje to po prostu pominięcie elementów, które są zerowe.


28
To nie odpowiada na pytanie, jak obsługiwać wyjątki w przypadku przetwarzania list.
Nathan Fellman

8
umm, tak, to prawda. eliminuje potrzebę obsługi wyjątków. tak, nie zawsze jest to właściwe rozwiązanie, ale jest powszechne.
Peter

3
Rozumiem. Cofam komentarz (choć nie będę go usuwać, ponieważ ta krótka „dyskusja” poprawia odpowiedź).
Nathan Fellman

11

Nie, nie ma lepszego sposobu. W wielu przypadkach możesz użyć unikania, tak jak robi to Peter

Inną opcją jest nie używanie rozumienia

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

Do Ciebie należy decyzja, czy jest to bardziej uciążliwe, czy nie


1
Jak użyłbym tutaj rozumienia?
Nathan Fellman

@Nathan: nie zrobiłbyś tego. gnibbler mówi: Nie, nie ma lepszego sposobu
SilentGhost

Przepraszam ... przegapiłem „nie” w jego odpowiedzi :-)
Nathan Fellman

4

Myślę, że funkcja pomocnicza, jak sugeruje osoba zadająca pierwsze pytanie, a także Bryan Head, jest dobra i wcale nie uciążliwa. Pojedynczy wiersz magicznego kodu, który wykonuje całą pracę, po prostu nie zawsze jest możliwy, więc funkcja pomocnicza jest idealnym rozwiązaniem, jeśli chcesz uniknąć forpętli. Jednak zmodyfikowałbym to do tego:

# 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

Wynik będzie taki [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Dzięki tej odpowiedzi masz pełną kontrolę nad kontynuowaniem w dowolny sposób.


Miły. Wygląda to bardzo podobnie do Eithertypu w niektórych funkcjonalnych językach programowania (takich jak Scala), gdzie an Eithermoże zawierać wartość jednego lub drugiego typu, ale nie obu. Jedyna różnica polega na tym, że w tych językach idiomatyczne jest umieszczenie błędu po lewej stronie, a wartości po prawej stronie. Oto artykuł z dodatkowymi informacjami .
Alex Palmer

3

Nie widziałem żadnej odpowiedzi, która o tym wspominała. Ale ten przykład byłby jednym ze sposobów zapobiegania zgłaszaniu wyjątku dla znanych przypadków niepowodzenia.

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


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

Czy to nie to samo, co ta odpowiedź? stackoverflow.com/a/1528244/1084
Nathan Fellman

Jest subtelna różnica. Filtrowanie jest stosowane na wyjściu, a nie na liście wejść. Jak widać w opublikowanym przykładzie, oznaczyłem „Brak” dla przypadku, który może powodować wyjątek.
Slakker
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.