Historia pytania:
System wyjątków (exceptions) występuje w Pythonie od samego początku i opiera się na klasach. Cała hierarchia wyjątków dziedziczy po klasie bazowej BaseException. W praktyce własne wyjątki są tworzone od Exception. Opracowanie własnych wyjątków jest ważne dla dużych projektów — pozwala na dokładniejsze obsługiwanie i sygnalizowanie specyficznych błędów aplikacji.
Problem:
Nie wszyscy programiści rozumieją różnice między BaseException a Exception, lenią się tworzyć swoje klasy, używają ogólnych bloków catch, co prowadzi do przechwytywania niepożądanych wyjątków (np. SystemExit, KeyboardInterrupt) i trudnodostępnych błędów. Często klasy wyjątków są implementowane niepoprawnie — nie nadają sensownej nazwy i nie dziedziczą z odpowiedniej klasy.
Rozwiązanie:
Twórz swoje wyjątki jako dziedziczące po Exception. Używaj sensownych nazw, aby nie przechwytywać podstawowych błędów ogólnie. Nie dziedzicz od BaseException, a tylko od Exception lub jego pochodnych, i nie przechwytuj wszystkiego przez except: bez wskazywania konkretnej klasy.
Przykład kodu:
class MyAppError(Exception): """Klasa bazowa dla wyjątków aplikacji""" pass class ConfigFileNotFound(MyAppError): pass try: raise ConfigFileNotFound('Plik konfiguracyjny nie został znaleziony!') except ConfigFileNotFound as e: print(f'Błąd: {e}')
Kluczowe cechy:
Czemu niebezpieczne jest przechwytywanie wszystkich wyjątków przez "except:" bez wskazania typu?
Taki blok przechwytuje nawet wyjątki systemowe — KeyboardInterrupt, SystemExit, co uniemożliwia normalne zakończenie programu przy Ctrl+C i prowadzi do "zamrożenia" w krytycznych sytuacjach. Należy pisać "except Exception:" aby pominąć podstawowe zdarzenia systemowe.
Czy można dziedziczyć swój wyjątek od BaseException?
Technicznie można, ale stanowczo nie zaleca się — takie wyjątki są trudne do wykrycia, omijają standardowe mechanizmy obsługi Exception, co często prowadzi do niełapanych błędów w aplikacji.
Czy poprawnie jest używać ValueError lub TypeError zamiast użytkowniczych wyjątków?
W małych skryptach jest to dopuszczalne, ale w dużych projektach lepiej jest tworzyć swoje semantycznie sensowne wyjątki. To przyspiesza diagnozowanie i obsługę błędów na wyższych poziomach aplikacji.
# Niepoprawnie: raise ValueError('Coś specyficznego dla aplikacji') # Dobrze: class MyAppValueError(MyAppError): pass raise MyAppValueError('Opis błędu z kontekstem aplikacji')
W dużym projekcie istniał globalny blok try/except:, który przechwytywał także wyjątki systemowe. Wiele błędów (np. SystemExit) nie było rejestrowanych w logach, aplikacja wchodziła w niespodziewane stany, administratorzy długo szukali symptomów.
Zalety:
Wady:
Zdefiniowano własne klasy błędów, wszędzie używano tylko "except Exception: ..." oraz oddzielnych obsług dla użytkowniczych wyjątków.
Zalety:
Wady: