Często widzę komentarze na temat innych pytań związanych z przepełnieniem stosu, dotyczące tego, jak except: pass
odradza się korzystanie z nich . Dlaczego to takie złe? Czasami po prostu nie dbam o to, jakie są błędy i chcę po prostu kontynuować kod.
try:
something
except:
pass
Dlaczego używanie except: pass
bloku jest złe? Co sprawia, że jest źle? Czy to fakt, że pass
popełniłem błąd lub że except
popełniłem błąd?
logging
modułu na poziomie DEBUG, aby uniknąć przesyłania strumieniowego w produkcji, ale zachowaj je w fazie rozwoju.Odpowiedzi:
Jak słusznie zgadłeś, istnieją dwie strony: Wyłapanie dowolnego błędu przez określenie typu wyjątku po
except
i przekazanie go bez podejmowania żadnych działań.Moje wyjaśnienie jest „trochę” dłuższe - więc tl; dr dzieli się na następujące:
Ale przejdźmy do szczegółów:
Nie wychwytuj żadnych błędów
Korzystając z
try
bloku, zwykle robisz to, ponieważ wiesz, że istnieje szansa na wygenerowanie wyjątku. Jako taki, masz już przybliżone pojęcie o tym, co może się złamać i jaki wyjątek może zostać zgłoszony. W takich przypadkach wychwytujesz wyjątek, ponieważ możesz go pozytywnie odzyskać . Oznacza to, że jesteś przygotowany na wyjątek i masz alternatywny plan, którego będziesz przestrzegać w przypadku tego wyjątku.Na przykład, gdy poprosisz użytkownika o wprowadzenie liczby, możesz przekonwertować dane wejściowe za pomocą,
int()
który może podnieść liczbęValueError
. Możesz to łatwo odzyskać, po prostu prosząc użytkownika o ponowną próbę, więc złapanieValueError
i ponowne monitowanie użytkownika byłoby właściwym planem. Innym przykładem może być odczytanie pewnej konfiguracji z pliku, a plik ten nie istnieje. Ponieważ jest to plik konfiguracyjny, możesz mieć domyślną konfigurację zastępczą, więc plik nie jest koniecznie potrzebny.FileNotFoundError
Dobrym pomysłem byłoby więc złapanie i po prostu zastosowanie domyślnej konfiguracji. Teraz w obu tych przypadkach mamy bardzo specyficzny wyjątek, którego się spodziewamy, i mamy równie konkretny plan, aby go naprawić. W związku z tym w każdym przypadku wyraźnie tylkoexcept
to pewne wyjątek.Gdybyśmy jednak złapali wszystko , to - oprócz tych wyjątków, z których jesteśmy gotowi wyzdrowieć - istnieje również szansa, że otrzymamy wyjątki, których się nie spodziewaliśmy i z których w rzeczywistości nie możemy wyzdrowieć; lub nie powinien wyzdrowieć.
Weźmy przykład pliku konfiguracyjnego z góry. W przypadku brakującego pliku zastosowaliśmy naszą domyślną konfigurację i w późniejszym czasie możemy zdecydować o automatycznym zapisaniu konfiguracji (więc następnym razem plik będzie istniał). Teraz wyobraź sobie, że dostaniemy
IsADirectoryError
alboPermissionError
zamiast. W takich przypadkach prawdopodobnie nie chcemy kontynuować; nadal możemy zastosować naszą domyślną konfigurację, ale później nie będziemy mogli zapisać pliku. I prawdopodobne jest, że użytkownik miał również konfigurację niestandardową, więc użycie wartości domyślnych prawdopodobnie nie jest pożądane. Chcielibyśmy więc natychmiast poinformować o tym użytkownika i prawdopodobnie przerwać również wykonywanie programu. Ale nie chcemy tego robić gdzieś głęboko w jakiejś małej części kodu; jest to coś ważnego na poziomie aplikacji, więc powinno się z tym obchodzić na górze - pozwól więc, by wyjątek wybuchł.Kolejny prosty przykład jest również wspomniany w dokumencie idiomów Python 2 . W kodzie znajduje się prosta literówka, która powoduje jej uszkodzenie. Ponieważ łapiemy każdy wyjątek, łapiemy również
NameError
s iSyntaxError
s . Oba są błędami, które przytrafiają się nam wszystkim podczas programowania; i oba są błędami, których absolutnie nie chcemy uwzględniać przy wysyłaniu kodu. Ale ponieważ złapaliśmy je również, nie będziemy nawet wiedzieć, że tam się pojawiły i nie stracimy żadnej pomocy, aby poprawnie debugować.Ale są też bardziej niebezpieczne wyjątki, na które nie jesteśmy przygotowani. Na przykład SystemError jest zwykle czymś, co zdarza się rzadko i na co tak naprawdę nie możemy zaplanować; oznacza to, że dzieje się coś bardziej skomplikowanego, coś, co prawdopodobnie uniemożliwia nam kontynuowanie bieżącego zadania.
W każdym razie jest bardzo mało prawdopodobne, że jesteś przygotowany na wszystko w małej części kodu, więc tak naprawdę powinieneś wychwycić tylko te wyjątki, na które jesteś przygotowany. Niektórzy sugerują, przynajmniej połowu
Exception
, ponieważ nie będzie obejmować takie rzeczy jakSystemExit
iKeyboardInterrupt
który według projektu mają zakończyć aplikację, ale jestem zdania, że jest to nadal zbyt niespecyficzne. Jest tylko jedno miejsce, w którym osobiście akceptuję łapanieException
lub tylko dowolnewyjątek, który znajduje się w jednym globalnym module obsługi wyjątków na poziomie aplikacji, którego jedynym celem jest zarejestrowanie każdego wyjątku, na który nie byliśmy przygotowani. W ten sposób możemy zachować tyle informacji o nieoczekiwanych wyjątkach, które możemy następnie wykorzystać do rozszerzenia naszego kodu, aby jawnie obsługiwać te wyjątki (jeśli możemy je odzyskać) lub - w przypadku błędu - utworzyć przypadki testowe, aby upewnić się to się więcej nie powtórzy. Ale oczywiście działa to tylko wtedy, gdy wychwycimy tylko te wyjątki, których się już spodziewaliśmy, więc te, których się nie spodziewaliśmy, naturalnie wybuchną.Staraj się unikać wchodzenia, z wyjątkiem bloków
Gdy wyraźnie wychwytuje się niewielki wybór konkretnych wyjątków, istnieje wiele sytuacji, w których wszystko będzie dobrze, po prostu nic nie robiąc. W takich przypadkach samo posiadanie
except SomeSpecificException: pass
jest w porządku. Jednak przez większość czasu tak nie jest, ponieważ prawdopodobnie potrzebujemy trochę kodu związanego z procesem odzyskiwania (jak wspomniano powyżej). Może to być na przykład coś, co ponownie próbuje wykonać akcję lub zamiast tego ustawić wartość domyślną.Jeśli tak nie jest, na przykład dlatego, że nasz kod jest już tak skonstruowany, aby powtarzał się, dopóki się nie powiedzie, to wystarczy przekazać. Biorąc nasz przykład z góry, możemy poprosić użytkownika o wprowadzenie numeru. Ponieważ wiemy, że użytkownicy nie chcą robić tego, o co ich proszymy, możemy po prostu umieścić to w pętli, aby wyglądało to tak:
Ponieważ próbujemy, dopóki nie zostanie zgłoszony żaden wyjątek, nie musimy robić nic specjalnego w bloku oprócz, więc jest w porządku. Ale oczywiście można argumentować, że przynajmniej chcemy pokazać użytkownikowi komunikat o błędzie, aby powiedzieć mu, dlaczego musi powtórzyć dane wejściowe.
Jednak w wielu innych przypadkach samo przekazanie znaku
except
jest znakiem, że tak naprawdę nie byliśmy przygotowani na wyjątek, który łapiemy. O ile te wyjątki nie są proste (jakValueError
lubTypeError
), a powód, dla którego możemy przejść, jest oczywisty, staraj się unikać po prostu przejścia. Jeśli naprawdę nie ma nic do zrobienia (i jesteś absolutnie pewien), rozważ dodanie komentarza, dlaczego tak jest; w przeciwnym razie rozwiń blok oprócz, aby faktycznie zawierał kod odzyskiwania.except: pass
Najgorszym sprawcą jest jednak połączenie obu. Oznacza to, że chętnie wychwytujemy każdy błąd, chociaż absolutnie nie jesteśmy na to przygotowani i nie robimy z tym nic. Ci przynajmniej chcą zalogować błąd i prawdopodobnie również przebić go jeszcze zakończyć działanie aplikacji (jest to mało prawdopodobne, można kontynuować jak normalne po MemoryError). Samo przekazanie nie tylko utrzyma działanie aplikacji (oczywiście w zależności od tego, gdzie złapiesz), ale także wyrzuci wszystkie informacje, uniemożliwiając wykrycie błędu - co jest szczególnie prawdziwe, jeśli to nie ty go odkrywasz.
Więc sedno jest następujące: Złap tylko wyjątki, których naprawdę oczekujesz i jesteś przygotowany na powrót do zdrowia; wszystkie inne to albo błędy, które powinieneś naprawić, albo coś, na co i tak nie jesteś przygotowany. Przekazywanie określonych wyjątków jest w porządku, jeśli naprawdę nie musisz nic z nimi robić. We wszystkich innych przypadkach jest to tylko znak domniemania i lenistwa. I na pewno chcesz to naprawić.
źródło
except
, ale następnie dzwoniąraise
bez argumentów, aby kontynuować tworzenie wyjątku, co powoduje zakończenie aplikacji. Podoba mi się: ianbicking.org/blog/2007/09/re-raising-exceptions.html . Wygląda na solidny wyjątek od reguły nieużywania kocaexcept
.raise
. Zwykle robisz to tylko w kilku lokalizacjach w aplikacji, aby zarejestrować wyjątek.Główny problem polega na tym, że ignoruje on wszystkie błędy: brak pamięci, procesor się pali, użytkownik chce się zatrzymać, program chce wyjść, Jabberwocky zabija użytkowników.
To zdecydowanie za dużo. W twojej głowie myślisz „Chcę zignorować ten błąd sieci”. Jeśli coś nieoczekiwanego pójdzie nie tak, twój kod po cichu kontynuuje i psuje się w całkowicie nieprzewidywalny sposób, którego nikt nie może debugować.
Dlatego powinieneś ograniczyć się do ignorowania tylko niektórych błędów, a resztę przejść.
źródło
Wykonanie pseudo kodu dosłownie nie powoduje żadnego błędu:
tak jakby to był całkowicie poprawny fragment kodu, zamiast rzucać
NameError
. Mam nadzieję, że nie tego chcesz.źródło
Przechwytuje każdy możliwy wyjątek, w tym
GeneratorExit
,KeyboardInterrupt
iSystemExit
- które są wyjątkami, których prawdopodobnie nie zamierzasz wychwycić. To tak samo jak łapanieBaseException
.Starsze wersje dokumentacji mówią :
Hierarchia wyjątków Python
Jeśli złapiesz nadrzędną klasę wyjątków, złapiesz także wszystkie ich klasy podrzędne. Bardziej eleganckie jest wychwytywanie tylko wyjątków, z którymi jesteś przygotowany.
Oto hierarchia wyjątków Python 3 - czy naprawdę chcesz je wszystkie złapać ?:
Nie rób tego
Jeśli używasz tej formy obsługi wyjątków:
Wtedy nie będziesz w stanie przerwać
something
bloku za pomocą Ctrl-C. Twój program przeoczy każdy możliwy wyjątek wtry
bloku kodu.Oto kolejny przykład, który będzie miał takie samo niepożądane zachowanie:
Zamiast tego spróbuj złapać tylko określony wyjątek, którego szukasz. Na przykład, jeśli wiesz, że podczas konwersji może wystąpić błąd wartości:
Dalsze objaśnienia z innym przykładem
Być może robisz to, ponieważ robisz przeglądanie stron internetowych i dostajesz odpowiedź
UnicodeError
, ale ponieważ użyłeś najszerszego wychwytywania wyjątków, Twój kod, który może mieć inne fundamentalne wady, będzie próbował działać do końca, marnując przepustowość , czas przetwarzania, zużycie sprzętu, brak pamięci, zbieranie danych śmieci itp.Jeśli inne osoby proszą cię o wypełnienie, aby mogły polegać na twoim kodzie, rozumiem, że czuję się zmuszony do obsługi wszystkiego. Ale jeśli zechcesz głośno upaść w miarę rozwoju, będziesz miał możliwość naprawienia problemów, które mogą pojawiać się sporadycznie, ale byłyby to długotrwałe kosztowne błędy.
Dzięki bardziej precyzyjnej obsłudze błędów kod może być bardziej niezawodny.
źródło
Oto moja opinia. Ilekroć znajdziesz błąd, powinieneś zrobić coś, aby sobie z nim poradzić, tj. Zapisać go w pliku dziennika lub coś innego. Przynajmniej informuje cię, że kiedyś był błąd.
źródło
Powinieneś użyć przynajmniej,
except Exception:
aby uniknąć wyłapywania wyjątków systemowych, takich jakSystemExit
lubKeyboardInterrupt
. Oto link do dokumentów.Zasadniczo należy wyraźnie zdefiniować wyjątki, które chcesz przechwycić, aby uniknąć przechwytywania niepożądanych wyjątków. Powinieneś wiedzieć, jakie wyjątki ignorujesz .
źródło
Po pierwsze, narusza dwie zasady Zen Pythona :
Oznacza to, że umyślnie sprawiasz, że błąd cicho przechodzi. Co więcej, nie wiesz, który błąd dokładnie wystąpił, ponieważ
except: pass
złapie każdy wyjątek.Po drugie, jeśli spróbujemy oderwać się od Zen Pythona i mówić w kategoriach czystego rozsądku, powinieneś wiedzieć, że używanie nie
except:pass
pozostawia cię bez wiedzy i kontroli w twoim systemie. Zasadą ogólną jest zgłoszenie wyjątku w przypadku wystąpienia błędu i podjęcie odpowiednich działań. Jeśli nie wiesz z góry, jakie to powinny być działania, przynajmniej zaloguj gdzieś błąd (i lepiej ponownie podnieś wyjątek):Ale zwykle, jeśli próbujesz złapać jakiś wyjątek, prawdopodobnie robisz coś złego!
źródło
except:pass
Konstrukt zasadniczo wycisza wszelkie nadzwyczajne warunki, które pojawią się natomiast kod objęte wtry:
bloku jest prowadzony.Co sprawia, że ta zła praktyka jest taka, że zwykle nie jest to, czego naprawdę chcesz. Częściej pojawia się jakiś szczególny warunek, który chcesz wyciszyć, i
except:pass
jest to zbyt tępy instrument. Wykona to zadanie, ale także zamaskuje inne warunki błędu, których prawdopodobnie nie spodziewałeś się, ale z dużym prawdopodobieństwem zechcesz sobie z nimi poradzić w inny sposób.To, co sprawia, że jest to szczególnie ważne w Pythonie, polega na tym, że według idiomów tego języka wyjątki niekoniecznie są błędami . Oczywiście są one często używane w ten sposób, podobnie jak w większości języków. Ale w szczególności Python od czasu do czasu używał ich do implementacji alternatywnej ścieżki wyjścia z niektórych zadań kodu, która tak naprawdę nie jest częścią normalnego działającego przypadku, ale wciąż wiadomo, że pojawia się od czasu do czasu, a nawet w większości przypadków można się tego spodziewać.
SystemExit
został już wspomniany jako stary przykład, ale najczęstszym przykładem może być obecnieStopIteration
. Stosowanie wyjątków w ten sposób wywołało wiele kontrowersji, szczególnie gdy iteratory i generatory zostały po raz pierwszy wprowadzone do Pythona, ale ostatecznie pomysł zwyciężył.źródło
Powód nr 1 został już podany - ukrywa błędy, których się nie spodziewałeś.
(# 2) - Utrudnia innym czytanie i zrozumienie kodu. Jeśli złapiesz wyjątek FileNotFoundException podczas próby odczytania pliku, to dla innego programisty oczywiste jest, jaką funkcjonalność powinien mieć blok „catch”. Jeśli nie określisz wyjątku, potrzebujesz dodatkowych komentarzy, aby wyjaśnić, co powinien zrobić blok.
(# 3) - Pokazuje leniwe programowanie. Jeśli używasz standardowego try / catch, oznacza to, że albo nie rozumiesz możliwych błędów w czasie wykonywania programu, albo że nie wiesz, jakie wyjątki są możliwe w Pythonie. Złapanie określonego błędu pokazuje, że rozumiesz zarówno swój program, jak i zakres błędów, które generuje Python. Jest to bardziej prawdopodobne, że inni programiści i recenzenci kodu ufają Twojej pracy.
źródło
Jakie wyniki generuje ten kod?
Teraz wyobraź sobie, że
try
-except
blok to setki linii wywołań do złożonej hierarchii obiektów i sam jest wywoływany w środku drzewa wywołań dużego programu. Kiedy program się nie powiedzie, gdzie zaczniesz szukać?źródło
Zasadniczo można sklasyfikować dowolny błąd / wyjątek w jednej z trzech kategorii :
Śmiertelne : to nie twoja wina, nie możesz im zapobiec, nie możesz się od nich odzyskać. Z pewnością nie powinieneś ich ignorować, kontynuować i pozostawić swój program w nieznanym stanie. Pozwól, aby błąd zakończył działanie twojego programu, nic nie możesz zrobić.
Boneheaded : Twoja wina, najprawdopodobniej z powodu niedopatrzenia, błędu lub błędu programowania. Powinieneś naprawić błąd. Ponownie, z całą pewnością nie powinieneś ignorować i kontynuować.
Egzogeniczny : możesz spodziewać się tych błędów w wyjątkowych sytuacjach, takich jak nie znaleziono pliku lub połączenie zostało przerwane . Powinieneś jawnie obsługiwać te błędy i tylko te.
We wszystkich przypadkach
except: pass
program pozostawi tylko nieznany stan, w którym może spowodować więcej szkód.źródło
Mówiąc wprost, jeśli zgłoszony zostanie wyjątek lub błąd, coś jest nie tak. Może nie jest to coś bardzo złego, ale tworzenie, zgłaszanie i wyłapywanie błędów i wyjątków tylko ze względu na używanie instrukcji goto nie jest dobrym pomysłem i rzadko się to robi. W 99% przypadków wystąpił problem.
Problemy muszą być rozwiązywane. Podobnie jak w życiu, w programowaniu, jeśli po prostu zostawiasz problemy w spokoju i próbujesz je zignorować, nie tyle same znikają wiele razy; zamiast tego stają się większe i rozmnażają się. Aby zapobiec narastaniu problemu i ponownemu uderzeniu w dalszą drogę, albo 1) wyeliminuj go i posprzątaj bałagan później, albo 2) powstrzymaj go i posprzątaj bałagan później.
Zignorowanie wyjątków i błędów i pozostawienie ich w ten sposób jest dobrym sposobem na doświadczenie wycieków pamięci, wyjątkowych połączeń z bazą danych, niepotrzebnych blokad uprawnień do plików itp.
W rzadkich przypadkach problem jest tak drobny, trywialny i - oprócz konieczności spróbowania ... złapać blok - samowystarczalny , że tak naprawdę nie ma po prostu bałaganu, który mógłby zostać później oczyszczony. Są to jedyne sytuacje, w których ta najlepsza praktyka niekoniecznie ma zastosowanie. Z mojego doświadczenia wynika, że ogólnie rzecz biorąc oznacza to, że cokolwiek robi kod, jest w zasadzie drobiazgowe i możliwe do uniknięcia, a coś takiego jak próby ponownych prób lub specjalne wiadomości nie są warte ani złożoności, ani podtrzymania wątku.
W mojej firmie regułą jest, aby prawie zawsze robić coś w bloku catch, a jeśli nic nie robisz, musisz zawsze zamieszczać komentarz z bardzo dobrym powodem, dla którego nie. Nigdy nie wolno przechodzić ani wychodzić z pustego bloku catch, gdy jest coś do zrobienia.
źródło
Moim zdaniem błędy mają swój powód, by wydawać się głupie, ale tak już jest. Dobre programowanie powoduje błędy tylko wtedy, gdy trzeba je obsłużyć. Ponadto, jak czytałem jakiś czas temu, „instrukcja pass jest instrukcją, że kod programu zostanie wstawiony później”, więc jeśli chcesz mieć pustą instrukcję wyjątkiem, możesz to zrobić, ale dla dobrego programu będzie być częścią brakuje. ponieważ nie radzisz sobie z rzeczami, które powinieneś mieć. Pojawiające się wyjątki dają szansę na poprawienie danych wejściowych lub zmianę struktury danych, aby wyjątki te nie występowały ponownie (ale w większości przypadków (wyjątki sieciowe, ogólne wyjątki wejściowe) wyjątki wskazują, że kolejne części programu nie będą działać poprawnie. Na przykład wyjątek NetworkException może wskazywać na przerwane połączenie sieciowe, a program nie może wysłać / odebrać danych w kolejnych krokach programu.
Ale użycie bloku pass tylko dla jednego bloku wykonania jest poprawne, ponieważ nadal rozróżniasz typy wyjątków, więc jeśli umieścisz wszystkie bloki wyjątków w jednym, nie jest ono puste:
można w ten sposób przepisać:
Tak więc nawet wiele bloków wyjątków z instrukcjami pass może skutkować kodem, którego struktura obsługuje specjalne typy wyjątków.
źródło
Wszystkie poruszone dotąd komentarze są ważne. Tam, gdzie to możliwe, musisz określić, jaki dokładnie wyjątek chcesz zignorować. Tam, gdzie to możliwe, musisz przeanalizować przyczynę wyjątku i zignorować tylko to, co chciałeś zignorować, a nie resztę. Jeśli wyjątek powoduje, że aplikacja „spektakularnie ulega awarii”, to niech tak będzie, ponieważ ważniejsze jest, aby wiedzieć, że nieoczekiwane zdarzenie miało miejsce, gdy się ono zdarzyło, niż ukrywanie, że problem kiedykolwiek wystąpił.
Biorąc to wszystko pod uwagę, nie bierz żadnej praktyki programowania jako najważniejszej. To jest głupie. Zawsze jest czas i miejsce, aby zrobić blok ignorowania wszystkich wyjątków.
Innym przykładem idiotycznego znaczenia jest użycie
goto
operatora. Kiedy byłem w szkole, nasz profesor nauczył nasgoto
operatora, aby wspominał, że NIGDY nie będziesz go używał. Nie wierz ludziom, którzy mówią ci, że xyz nigdy nie powinien być używany i nie może być scenariusza, gdy jest on użyteczny. Zawsze jest.źródło
Obsługa błędów jest bardzo ważna w programowaniu. Musisz pokazać użytkownikowi, co poszło nie tak. W bardzo niewielu przypadkach można zignorować błędy. Jest to bardzo zła praktyka programowania.
źródło
Ponieważ nie zostało to jeszcze wspomniane, lepiej jest użyć stylu
contextlib.suppress
:Zauważ, że w podanym przykładzie stan programu pozostaje taki sam, niezależnie od tego, czy wystąpi wyjątek. Oznacza to, że
somefile.tmp
zawsze przestaje istnieć.źródło