8. Błędy i wyjątki¶
Do tej pory wiadomości o błędach były tylko wspomniane, ale jeśli próbowałaś(-łeś) przykładów to pewnie udało ci się na nie natknąć. Występują (przynajmniej) dwa charakterystyczne typy błędów: błędy składni (syntax errors) oraz wyjątki (exceptions).
8.1. Błędy składni¶
Błędy składni, znane również jako błędy parsowania, są prawdopodobnie najczęstszym rodzajem skarg, które pojawiają się podczas nauki Pythona:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
Parser powtarza błędną linię i wyświetla małą „strzałkę” wskazującą najwcześniejszy punkt linii, w którym wykryto błąd. Błąd jest spowodowany (lub przynajmniej wykryty) przez token poprzedzający strzałkę: w przykładzie błąd jest wykryty w funkcji print()
, ponieważ brakuje przed nim dwukropka (':'
). Nazwa pliku i numer linii są drukowane, abyś wiedział(a), gdzie szukać, w przypadku, gdy dane wejściowe pochodzą ze skryptu.
8.2. Wyjątki¶
Nawet jeśli instrukcja lub wyrażenie jest poprawne składniowo, może ona wywołać błąd podczas próby jej wykonania. Błędy zauważone podczas wykonania programu są nazywane wyjątkami (exceptions) i nie zawsze są niedopuszczalne: już niedługo nauczysz w jaki sposób je obsługiwać. Większość wyjątków nie jest jednak obsługiwana przez program przez co wyświetlane są informacje o błędzie jak pokazano poniżej:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
Ostatni wiersz komunikatu o błędzie wskazuje, co się stało. Wyjątki występują w różnych typach, a typ jest drukowany jako część komunikatu: typy w przykładzie to ZeroDivisionError
, NameError
i TypeError
. Ciąg wydrukowany jako typ wyjątku jest nazwą wbudowanego wyjątku, który wystąpił. Jest to prawdą dla wszystkich wbudowanych wyjątków, ale nie musi być prawdą dla wyjątków zdefiniowanych przez użytkownika (choć jest to przydatna konwencja). Standardowe nazwy wyjątków są wbudowanymi identyfikatorami (nie zarezerwowanymi słowami kluczowymi).
Pozostała część linii dostarcza szczegółów na temat typu wyjątku oraz informacji, co go spowodowało.
Wcześniejsza część komunikatu o błędzie pokazuje kontekst, w którym wystąpił wyjątek, w postaci śladu stosu. Ogólnie rzecz biorąc, zawiera on ślad stosu z listą linii źródłowych; jednak nie wyświetli linii odczytanych ze standardowego wejścia.
Built-in Exceptions wymienia wbudowane wyjątki i ich znaczenie.
8.3. Obsługa wyjątków¶
Możliwe jest pisanie programów, które obsługują wybrane wyjątki. Spójrzmy na poniższy przykład, który prosi użytkownika o wprowadzenie danych, dopóki nie zostanie wprowadzona poprawna liczba całkowita, ale pozwala użytkownikowi na przerwanie programu (przy użyciu Control-C lub czegokolwiek innego obsługiwanego przez system operacyjny); zauważ, że przerwanie wygenerowane przez użytkownika jest sygnalizowane przez podniesienie wyjątku KeyboardInterrupt
.
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
...
Instrukcja try
działa następująco.
W pierwszej kolejności wykonywane są instrukcje pod klauzulą try - pomiędzy słowami kluczowymi
try
iexcept
.Jeżeli nie wystąpi żaden wyjątek, klauzula except jest pomijana i zostaje zakończone wykonywanie instrukcji
try
.Jeśli wyjątek wystąpi podczas wykonywania klauzuli
try
, reszta klauzuli jest pomijana. Następnie, jeśli jego typ pasuje do wyjątku nazwanego po słowie kluczowymexcept
, wykonywana jest klauzula except, a następnie wykonanie jest kontynuowane po bloku try/except.Jeśli wystąpi wyjątek, który nie pasuje do wyjątku nazwanego w klauzuli except, jest on przekazywany do zewnętrznych instrukcji
try
; jeśli nie zostanie znaleziona obsługa, jest to nieobsłużony wyjątek i wykonanie zatrzymuje się z komunikatem, jak pokazano powyżej.
Instrukcja try
może mieć więcej niż jedną klauzulę except, aby określić programy obsługi dla różnych wyjątków. Wykonany zostanie co najwyżej jeden handler. Obsługiwane są tylko wyjątki, które występują w odpowiadających im klauzulach try, a nie w kodzie obsługi tej samej instrukcji try
. Klauzula except może określać wiele wyjątków krotką w nawiasach, na przykład:
... except (RuntimeError, TypeError, NameError):
... pass
A class in an except
clause is compatible with an exception if it is
the same class or a base class thereof (but not the other way around — an
except clause listing a derived class is not compatible with a base class).
For example, the following code will print B, C, D in that order:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
Zauważ, że jeśli klauzule except byłyby odwrócone (z except B
na pierwszym miejscu), wypisane zostałoby B, B, B — uruchamiana jest pierwsza pasująca klauzula except.
All exceptions inherit from BaseException
, and so it can be used to serve
as a wildcard. Use this with extreme caution, since it is easy to mask a real
programming error in this way! It can also be used to print an error message and
then re-raise the exception (allowing a caller to handle the exception as well):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except BaseException as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
Alternatively the last except clause may omit the exception name(s), however the exception
value must then be retrieved from sys.exc_info()[1]
.
Instrukcja try
… except
posiada opcjonalną klauzulę else, która, gdy jest obecna, musi następować po wszystkich klauzulach except. Jest to przydatne w przypadku kodu, który musi zostać wykonany, jeśli klauzula try nie rzuci wyjątku. Na przykład:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
Użycie klauzuli else
jest lepsze niż dodanie dodatkowego kodu do klauzuli try
, ponieważ pozwala uniknąć przypadkowego wychwycenia wyjątku, który nie został rzucony przez kod chroniony instrukcją try
… except
.
When an exception occurs, it may have an associated value, also known as the exception’s argument. The presence and type of the argument depend on the exception type.
The except clause may specify a variable after the exception name. The
variable is bound to an exception instance with the arguments stored in
instance.args
. For convenience, the exception instance defines
__str__()
so the arguments can be printed directly without having to
reference .args
. One may also instantiate an exception first before
raising it and add any attributes to it as desired.
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
If an exception has arguments, they are printed as the last part («detail») of the message for unhandled exceptions.
Exception handlers don’t just handle exceptions if they occur immediately in the try clause, but also if they occur inside functions that are called (even indirectly) in the try clause. For example:
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: division by zero
8.4. Rzucanie wyjątków¶
Instrukcja raise
pozwala programiście wymusić wystąpienie żądanego wyjątku. Na przykład:
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere
The sole argument to raise
indicates the exception to be raised.
This must be either an exception instance or an exception class (a class that
derives from Exception
). If an exception class is passed, it will
be implicitly instantiated by calling its constructor with no arguments:
raise ValueError # shorthand for 'raise ValueError()'
Jeśli chcesz rozpoznać, czy wyjątek został rzucony, ale nie zamierzasz go obsługiwać, prostsza forma instrukcji raise
pozwala na ponowne rzucenie wyjątku:
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
8.5. Łańcuch wyjątków¶
Jeśli nieobsłużony wyjątek wystąpi wewnątrz sekcji except
, zostanie do niego dołączony obsługiwany wyjątek i uwzględniony w komunikacie o błędzie:
>>> try:
... open("database.sqlite")
... except OSError:
... raise RuntimeError("unable to handle error")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: unable to handle error
Aby wskazać, że wyjątek jest bezpośrednią konsekwencją innego, instrukcja raise
dopuszcza opcjonalną klauzulę from
:
# exc must be exception instance or None.
raise RuntimeError from exc
Może to być przydatne podczas przekształcania wyjątków. Na przykład:
>>> def func():
... raise ConnectionError
...
>>> try:
... func()
... except ConnectionError as exc:
... raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
ConnectionError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database
Umożliwia również wyłączenie automatycznego łączenia wyjątków przy użyciu idiomu from None
:
>>> try:
... open('database.sqlite')
... except OSError:
... raise RuntimeError from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError
Więcej informacji na temat mechaniki łączenia w łańcuchy można znaleźć w rozdziale Built-in Exceptions.
8.6. Wyjątki zdefiniowane przez użytkownika¶
Programy mogą nazywać własne wyjątki, tworząc nową klasę wyjątków (więcej informacji na temat klas Python można znaleźć w rozdziale Klasy). Wyjątki powinny zazwyczaj dziedziczyć z klasy Exception
, bezpośrednio lub pośrednio.
Można zdefiniować klasy wyjątków, które robią wszystko, co może zrobić każda inna klasa, ale zwykle są one proste, często oferując tylko kilka atrybutów, które pozwalają na wyodrębnienie informacji o błędzie przez kod obsługi wyjątku.
Większość wyjątków ma nazwy kończące się na „Error”, podobnie jak w przypadku standardowych wyjątków.
Many standard modules define their own exceptions to report errors that may occur in functions they define. More information on classes is presented in chapter Klasy.
8.7. Definiowanie działań porządkujących¶
Instrukcja try
ma inną opcjonalną klauzulę, która jest przeznaczona do definiowania działań porządkujących, które muszą być wykonane w każdych okolicznościach. Na przykład:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
Jeśli klauzula finally
jest obecna, klauzula finally
wykona się jako ostatnie zadanie przed zakończeniem instrukcji try
. Klauzula finally
uruchomi się niezależnie od tego, czy instrukcja try
spowoduje wyjątek. Poniższe punkty omawiają bardziej złożone przypadki wystąpienia wyjątku:
Jeśli podczas wykonywania klauzuli
try
wystąpi wyjątek, może on zostać obsłużony przez klauzulęexcept
. Jeśli wyjątek nie zostanie obsłużony przez klauzulęexcept
, zostanie on ponownie rzucony po wykonaniu klauzulifinally
.Wyjątek może wystąpić podczas wykonywania klauzul
except
lubelse
. Ponownie, wyjątek jest ponownie rzucany po wykonaniu klauzulifinally
.Jeśli klauzula
finally
wykonuje instrukcjebreak
,continue
lubreturn
, wyjątki nie są ponownie rzucane.Jeśli instrukcja
try
osiągnie instrukcjębreak
,continue
lubreturn
, klauzulafinally
wykona się tuż przed wykonaniem instrukcjibreak
,continue
lubreturn
.Jeśli klauzula
finally
zawiera instrukcjęreturn
, zwróconą wartością będzie ta z instrukcjireturn
klauzulifinally
, a nie wartość z instrukcjireturn
klauzulitry
.
Dla przykładu:
>>> def bool_return():
... try:
... return True
... finally:
... return False
...
>>> bool_return()
False
Bardziej skomplikowany przykład:
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
Jak widać, klauzula finally
jest wykonywana w każdym przypadku. TypeError
rzucony przez dzielenie dwóch ciągów znaków nie jest obsłużony przez klauzulę except
i dlatego jest ponownie rzucony po wykonaniu klauzuli finally
.
W prawdziwych aplikacjach, klauzula finally
jest przydatna do zwalniania zewnętrznych zasobów (takich jak pliki lub połączenia sieciowe), niezależnie od tego, czy użycie zasobu zakończyło się powodzeniem.
8.8. Predefiniowane akcje porządkujące¶
Niektóre obiekty definiują standardowe akcje porządkujące, które mają zostać podjęte, gdy obiekt nie jest już potrzebny, niezależnie od tego, czy operacja przy użyciu obiektu powiodła się, czy nie. Spójrz na poniższy przykład, który próbuje otworzyć plik i wydrukować jego zawartość na ekranie:
for line in open("myfile.txt"):
print(line, end="")
Problem z tym kodem polega na tym, że pozostawia on plik otwarty przez nieokreślony czas po zakończeniu wykonywania tej części kodu. Nie jest to problemem w prostych skryptach, ale może być problemem dla większych aplikacjach. Instrukcja with
pozwala na używanie obiektów takich jak pliki w sposób, który zapewnia, że są one zawsze czyszczone szybko i poprawnie:
with open("myfile.txt") as f:
for line in f:
print(line, end="")
Po wykonaniu instrukcji, plik f jest zawsze zamykany, nawet jeśli napotkano problem podczas przetwarzania linii. Obiekty, które, podobnie jak pliki, zapewniają predefiniowane akcje czyszczenia, wskażą to w swojej dokumentacji.