W Pythonie obsługa wyjątków jest realizowana przez konstrukcje try/except/else/finally oraz operator raise.
try definiuje chroniony blok kodu. Jeśli w jego wnętrzu wystąpi wyjątek, sterowanie przekazywane jest do najbliższego pasującego bloku except. Jeśli wyjątku nie ma — wykonywany jest blok else, jeśli taki istnieje. Blok finally jest wykonywany zawsze — niezależnie od tego, czy wystąpił wyjątek, czy nie (na przykład, aby zwolnić zasoby).
Operator raise wywołuje wyjątek jawnie lub powtarza bieżący (w bloku except bez wskazania typu).
Przykład:
try: x = 1 / 0 except ZeroDivisionError as e: print(f"Błąd: {e}") else: print("Nie było błędów") finally: print("Zawsze wykonywane")
Wynik:
Błąd: division by zero
Zawsze wykonywane
Szczegóły:
Pytanie: „Co się stanie, jeśli w bloku try i finally wywołasz raise różne wyjątki?”
Odpowiedź: Wyjątek z finally „przykryje” wyjątek z try: na zewnątrz wyjdzie to, co wystrzeliło w finally.
def foo(): try: raise ValueError("w try") finally: raise IndexError("w finally") try: foo() except Exception as e: print(repr(e)) # Wyświetli: IndexError('w finally')
Historia
W procesie ETL w bloku finally bezwarunkowo zamykano połączenie z bazą, ale zapominali, że przy tym w finally może wystąpić wyjątek (na przykład, jeśli połączenie było już zamknięte). Wynik — „ukryty” wyjątek z finally całkowicie pochłaniał wyjątek z głównego kodu, znacznie utrudniając debugowanie.
Historia
Używano łańcuchów kilku except: od ogólnego except Exception nad szczegółowymi. W efekcie wszystkie specyficzne excepty zostawały „martwe” — wyjątki na niskim poziomie nie były łapane osobno, co utrudniało obsługę specyficznych błędów.
Historia
W serwisie internetowym w bloku except zapominali przekazać wyjątek dalej przez „raise”, logując błąd, ale pozwalali na dalsze wykonywanie. W efekcie rzeczywiste błędy „gubiły się”, a program kontynuował działanie w nieprawidłowym stanie.