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.

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:

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.

Przykład. Otwieranie nieistniejącego pliku
>>> 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
  1. 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ątek IOError. Ponieważ nie przechwytujemy tego wyjątku Python po prostu wypisuje trochę danych pomocnych przy znajdywaniu błędu, a potem zakańcza działanie programu.
  2. Próbujemy otworzyć ten sam nieistniejący plik, jednak tym razem robimy to we wnętrzu bloku try...except. Gdy metoda open rzuca wyjątek IOError, jesteśmy na to przygotowani. Linia except IOError: przechwytuje ten wyjątek i wykonuje blok kodu, który w tym wypadku wypisuje bardziej przyjazny opis błędu.
  3. 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.

Przykład. Obsługa funkcjonalności zdefiniowanej dla konkretnej platformy
# 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
  1. 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ątek ImportError, który przechwycimy.
  2. OK, nie mamy termios, więc spróbujmy z msvcrt, 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ątek ImportError, który także przechwycimy.
  3. 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ątek ImportError, który też przechwytujemy.
  4. Ż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 zmiennej getpass. Jeśli czytaliśmy oficjalną dokumentację getpass, mówi ona, że moduł getpass definiuje funkcję getpass. Wykonuje to poprzez powiązanie getpass 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 tylko getpass, a funkcja ta wykona zawsze odpowiednią czynność.
  5. Blok try...except, podobnie jak instrukcja if, może posiadać klauzule else. Jeśli żaden wyjątek nie zostanie rzucony podczas wykonywania bloku try, spowoduje to wywołanie klauzuli else. W tym przypadku oznacza to, że import from EasyDialogs import AskPassword zadziałał, a więc możemy powiązać getpass z funkcją AskPassword. Każdy inny blok try...except w przedstawionym kodzie posiada podobną klauzulę else, która pozwala, gdy zostanie zaimportowany działający moduł, przypisać do getpass odpowiednią funkcję.

Materiały dodatkowe

edytuj