Zanurkuj w Pythonie/Obsługa wyjątków
W tym rozdziale zajmiemy się wyjątkami, obiektami pliku, pętlami for
oraz modułami os
i sys
. Jeśli używaliśmy wyjątków w innych językach programowania, możemy tylko szybko przyjrzeć się składni Pythona, która odpowiada za obsługę wyjątków, ale powinniśmy zwrócić uwagę na część, która omawia w jaki sposób zarządzać plikami.
Obsługa wyjątków
[edytuj]Jak wiele innych języków programowania, Python obsługuje wyjątki. Przy pomocy bloków try...except
przechwytujemy wyjątki, natomiast raise
rzuca wyjątek.
Python używa słów kluczowych try i except do obsługi wyjątków, natomiast za pomocą słowa raise wyrzuca wyjątki. Java i C++ używają słów try i catch do przechwytywania wyjątków, a słowa throw do ich generacji.
|
Wyjątki są w Pythonie wszędzie. Praktycznie każdy moduł w bibliotece standardowej Pythona ich używa. Sam interpreter Pythona również rzuca wyjątki w różnych sytuacjach. Już wiele razy widzieliśmy je w tej książce:
- Próba użycia nieistniejącego klucza w słowniku rzuci wyjątek
KeyError
- Wyszukiwanie w liście nieistniejącej wartości rzuci wyjątek
ValueError
- Wywołanie nieistniejącej metody obiektu rzuci wyjątek
AttributeError
- Użycie nieistniejącej zmiennej rzuci wyjątek
NameError
- Mieszanie niezgodnych typów danych spowoduje wyjątek
TypeError
W każdym z tych przypadków, gdy używaliśmy IDE Pythona i wystąpił błąd, to został wypisany wyjątek (w zależności od użytego IDE na przykład na czerwono). Jest to tak zwany nieobsłużony wyjątek. Kiedy podczas wykonywania programu został rzucony wyjątek, nie było w nim specjalnego kodu, który by go wykrył i zaznajomił się z nim, dlatego obsługa tego wyjątku zostaje zrzucona na domyślne zachowanie Pythona, które z kolei wypisuje trochę informacji na temat błędu i kończy pracę programu. W przypadku IDE nie jest to wielka przeszkoda, ale wyobraźmy sobie, co by się stało, gdyby podczas wykonywania właściwego programu nastąpiłby taki błąd, a co z kolei spowodowałoby, że program wyłączyłby się.
Jednak efektem wyjątku nie musi być katastrofa programu. Kiedy wyjątki zostaną rzucone, mogą zostać obsłużone. Czasami przyczyną wystąpienia wyjątku jest błąd w kodzie (na przykład próba użycia zmiennej, która nie istnieje), jednak bardzo często wyjątek możemy przewidzieć. Jeśli otwieramy plik, może on nie istnieć. Jeśli łączysz się z bazą danych, może ona być niedostępna lub możemy nie mieć odpowiednich parametrów dostępu np. hasła. Jeśli wiemy, że jakaś linia kodu może wygenerować wyjątek, powinniśmy próbować ją obsłużyć przy pomocy bloku try...except
.
>>> fsock = open("/niemapliku", "r") #(1) Traceback (most recent call last): File "<stdin>", line 1, in ? IOError: [Errno 2] No such file or directory: '/niemapliku' >>> try: ... fsock = open("c:/niemapliku.txt") #(2) ... except IOError: ... print "Plik nie istnieje" ... print "Ta linia zawsze zostanie wypisana" #(3) Plik nie istnieje Ta linia zawsze zostanie wypisana
- Używając wbudowanej funkcji
open
, możemy spróbować otworzyć plik do odczytu (więcej o tej funkcji w następnym podrozdziale). Jednak ten plik nie istnieje i dlatego zostanie rzucony wyjątekIOError
. Ponieważ nie przechwytujemy tego wyjątku Python po prostu wypisuje trochę danych pomocnych przy znajdywaniu błędu, a potem zakańcza działanie programu. - Próbujemy otworzyć ten sam nieistniejący plik, jednak tym razem robimy to we wnętrzu bloku
try...except
. Gdy metodaopen
rzuca wyjątekIOError
, jesteśmy na to przygotowani. Liniaexcept IOError:
przechwytuje ten wyjątek i wykonuje blok kodu, który w tym wypadku wypisuje bardziej przyjazny opis błędu. - Gdy wyjątek zostanie już obsłużony, program wykonuje się dalej w sposób normalny, od pierwszej linii po bloku
try...except
. Zauważmy, że ta linia zawsze wypisze tekst "Ta linia zawsze zostanie wypisana", niezależnie, czy wyjątek wystąpi, czy też nie. Jeśli naprawdę mielibyśmy taki plik na dysku, to i tak ta linia zostałaby wykonana.
Wyjątki mogą wydawać się nieprzyjazne (w końcu, jeśli nie przechwycimy wyjątku, program zostanie przerwany), jednak pomyślmy, z jakiej innej alternatywy moglibyśmy skorzystać. Czy chcielibyśmy dostać bezużyteczny obiekt, który przedstawia nieistniejący plik? I tak musielibyśmy sprawdzić jego poprawność w jakiś sposób, a jeśli byśmy tego nie zrobili, to nasz program wykonałby jakieś dziwne, nieprzewidywalne operacje, których byśmy się nie spodziewali. Nie byłaby to wcale dobra zabawa. Z wyjątkami błędy występują natychmiast i możemy je obsługiwać w standardowy sposób u źródła problemu.
Wykorzystanie wyjątków do innych celów
[edytuj]Jest wiele innych sposobów wykorzystania wyjątków, oprócz do obsługi błędów. Dobrym przykładem jest importowanie modułów Pythona, sprawdzając czy nastąpił wyjątek. Jeśli moduł nie istnieje zostanie rzucony wyjątek ImportError
. Dzięki temu możemy zdefiniować wiele poziomów funkcjonalności, które zależą od modułów dostępnych w czasie wykonania, a dzięki temu możemy wspierać różnorodne platformy (kod zależny od platformy jest podzielony na oddzielne moduły).
Możemy też zdefiniować własne wyjątki, tworząc klasę, która dziedziczy z wbudowanej klasy Exception
, a następnie możemy rzucać wyjątki przy pomocy polecenia raise
. Możemy zajrzeć do części "materiały dodatkowe", aby dowiedzieć się więcej na ten temat.
Następny przykład pokazuje, w jaki sposób wykorzystywać wyjątki, aby obsłużyć funkcjonalność zdefiniowaną jedynie dla konkretnej platformy. Kod pochodzi z modułu getpass
, który jest modułem opakowującym, którym umożliwia pobranie hasła od użytkownika. Pobieranie hasła jest całkowicie różne na platformach UNIX, Windows, czy Mac OS, ale kod ten obsługuje wszystkie te różnice.
# Bind the name getpass to the appropriate function
try:
import termios, TERMIOS #(1)
except ImportError:
try:
import msvcrt #(2)
except ImportError:
try:
from EasyDialogs import AskPassword #(3)
except ImportError:
getpass = default_getpass #(4)
else: #(5)
getpass = AskPassword
else:
getpass = win_getpass
else:
getpass = unix_getpass
termios
jest modułem określonym dla UNIX-a, który dostarcza niskopoziomową kontrolę nad terminalem wejścia. Jeśli moduł ten jest niedostępny (ponieważ, nie ma tego na naszym systemie, ponieważ system tego nie obsługuje), importowanie nawali, a Python rzuci wyjątekImportError
, który przechwycimy.- OK, nie mamy
termios
, więc spróbujmy zmsvcrt
, który jest modułem charakterystycznym dla systemu Windows, a dostarcza on API do wielu przydatnych funkcji dla tego systemu. Jeśli to także nie zadziała, Python rzuci wyjątekImportError
, który także przechwycimy. - Jeśli pierwsze dwa nie zadziałają, próbujemy zaimportować funkcję z
EasyDialogs
, która jest w module określonym dla Mac OS-a, który dostarcza funkcje przeznaczone dla wyskakujących okien dialogowych różnego typu. I ponownie, jeśli moduł nie istnieje, Python rzuci wyjątekImportError
, który też przechwytujemy. - Żaden z tych modułów, które są przeznaczone dla konkretnej platformy, nie są dostępne (jest to możliwe, ponieważ Python został przeportowany na wiele różnych platform), więc musimy powrócić do domyślnej funkcji do pobierania hasła (która jest zdefiniowana gdzieś w module
getpass
). Zauważmy, co robimy: przypisujemy funkcjędefault_getpass
do zmiennejgetpass
. Jeśli czytaliśmy oficjalną dokumentacjęgetpass
, mówi ona, że modułgetpass
definiuje funkcjęgetpass
. Wykonuje to poprzez powiązaniegetpass
z odpowiednią funkcją, która zależy od naszej platformy. Kiedy wywołujemy funkcjęgetpass
, tak naprawdę wywołujemy funkcję określoną dla konkretnej platformy, którą określił za nas powyższy kod. Nie musimy się martwić, na jakiej platformie uruchamiamy nasz kod -- wywołujemy tylkogetpass
, a funkcja ta wykona zawsze odpowiednią czynność. - Blok
try...except
, podobnie jak instrukcjaif
, może posiadać klauzuleelse
. Jeśli żaden wyjątek nie zostanie rzucony podczas wykonywania blokutry
, spowoduje to wywołanie klauzulielse
. W tym przypadku oznacza to, że importfrom EasyDialogs import AskPassword
zadziałał, a więc możemy powiązaćgetpass
z funkcjąAskPassword
. Każdy inny bloktry...except
w przedstawionym kodzie posiada podobną klauzulęelse
, która pozwala, gdy zostanie zaimportowany działający moduł, przypisać dogetpass
odpowiednią funkcję.
Materiały dodatkowe
[edytuj]- Python Tutorial mówi na temat definiowania, rzucania własnych wyjątków i jednoczesnej obsługi wielu wyjątków
- Python Library Reference opisuje wszystkie wbudowane wyjątki
- Python Library Reference dokumentuje moduł
getpass
. - Python Library Reference dokumentuje moduł
traceback
, który zapewnia niskopoziomowy dostęp do atrybutów wyjątków, po tym, jak wyjątek zostanie rzucony. - Python Reference Manual omawia bardziej technicznie blok
try...except
.