In [4]:
# setup
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
In [5]:
mce = ['styczen', 'luty', 'marzec', 'kwiecien', 'maj', 'czerwiec', 'lipiec', 'sierpien', 'wrzesien', 'pazdziernik', 'listopad', 'grudzien'] 

Błędy, obsługa wyjątków i szukanie pomocy

Pisanie bezbłędnego kodu jest praktycznie niemożliwe. Poza błędami popełnionymi przez programistę, które z reguły można wykryć testując program, istnieje grupa problemów, których albo nie można uniknąć albo wynikają z niedoskonałości przyszłych danych, których nie znamy na etapie pisania kodu. Takie sytuacje nazywamy wyjątkiam (ang. exceptions). W trakcie pisania kodu doświadczony programista powinien umieć przewidzieć takie sytuacje i odpowiednio przygotować kod do ich obsługi. Są też sytuacje. Służą do tego procedury przechwytywania wyjątków (ang. catch), procedury ręcznego wywoływania wyjątków (ang raise) i procedury upewniania sie (ang. assert) czy zmienne lub argumenty mają prawidłowe wartości.

Błędy

W języku programowania Python możemy mieć do czynienia z 3 typami błędów:

  • Syntax errors – błędy składniowe, oznacza, że naruszyliśmy zasady “gramatyki” języka Python. Takie błędy popełnia się zwłaszcza w początkowej pracy z językiem Python. Są one najłatwiejsze do naprawienia. Python stara się wskazać nam, w którym miejscu znalazł błąd. Czasami jednak błąd może pojawić się już nieco wcześniej, a miejsce na które wskazuje Python w komunikacie o błędzie to punkt, w którym interpreter zorientował się, że “coś jest nie tak”. To dla nas punkt wyjścia do sprawdzenia gdzie może być błąd.

  • Logic errors - błędy logiczne, występują, gdy składnia jest prawidłowa, ale nastąpił błąd w kolejności wykonania instrukcji lub błąd w tym jak instrukcje zależą od siebie. Dla przykładu: Otwórz butelkę i napij się wody, włóż butelkę do pleacka, idź do szkoły, zakręć butelkę. (I plecak jest mokry).

  • Semantic errors – błędy tematyczne, z tego typu błędem mamy do czynienia w sytuacji kiedy program działa, tylko nie robi tego co chcieliśmy, żeby robił. Na przykład źle zaprogramowaliśmy procedurę obliczeniową, która technicznie jest poprawna ale zwraca błęde wyniki, na przykład: pole_kola = 3 * math.pi**2, ta linia kodu zadziała zawsze prawidłowo, ale wynik nie jest polem koła. Takie błędy są najtrudniejsze do wykrycia.

Wyjątki

Niestety, pomimo najlepiej napisanego kodu, zdażają się sytuacje, że błąd jest nie do uniknięcia. Jest to najczęściej związane z danymi lub parametrami wprowadzanymi do programu z zewnątrz. Przykładem może być wartość zero w mianowniku, wartość nieokreślona, nieprawidłowy typ danych, błędna nazwa parametru, przekroczenie zasięgu listy itp. Takie sytuacje są nie do uniknięcia bo nie wiemy co z napisanym przez nas kodem będzie się działo. Sytuacje, w których program musi wykonać operacje która nie jest możliwa do wykonania, prowadzi najczęściej do zakończenia działania skryptu i wypisania komunikatu o błędzie. W ten sposób język Python informuje nas, że “coś poszło nie tak”, lub też po prostu wpisaliśmy coś w sposób nie zrozumiały dla języka Python.

Najważniejsze grupy błędów:

  • Syntax error - zła składnia, interpreter kończy działanie
  • Type error - nie można wykonać operacji w ramach danego typu, np. dodać wartości do napisu
  • Index error - przekroczenie zasięgu listy lub krotki
  • Value error - nie obsługiwana wartość lub typ danych
  • Zero division error - błąd dzielenia przez zero

Poniżej przedstawiamy najczęstrze przypadki, gdy do takich błędów dochodzi:

>>> Dodaj 2 i 5 
  File "<stdin>", line 1 
    Dodaj 2 i 5 
          ^  
SyntaxError: invalid syntax
>>> int('a') 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
ValueError: invalid literal for int() with base 10: 'a'
>>> print('a' * 'b') 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: can't multiply sequence by non-int of type 'str'
>>> print(5 + 'b') 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> tydzien[2] = 'SRODA' 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: 'tuple' object does not support item assignment
>>> mce[12] 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
IndexError: list index out of range
>>> 3/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Więcej na temat możliwych typów komunikatów:

[http://www.tutorialspoint.com/python/python_exceptions.htm]

[https://docs.python.org/3/library/exceptions.html]

Obsługa wyjątków

Dobrze napisany kod powinien posiadać obsługę wyjątków. Doświadczony programista potrafi przewidzieć, że w danym miejscu kodu może dojść do wyjątku i jeśli wiemy, że jakaś fragment kodu może taki wygenerować wyjątek, powinniśmy próbować ją obsłużyć przy pomocy bloku try...except lub try...except... finally. Przykładem niech będzie obsługa dzielenia przez 0. Jeżeli spodziewamy się, że argument B funkcji może przyjąć wartość 0, powinniśmy taki przygotować się na taką ewentualność.

In [4]:
def dzielenie(A,B):
    try:
        return A/B
    except:
        print("WARNING: dzielenie przez zero nie jest dozwolone")
        return float("inf")

print(dzielenie(2.0,4.))
0.5
In [5]:
dzielenie(2.0,0)
WARNING: dzielenie przez zero nie jest dozwolone
Out[5]:
inf

Jak działa obsługa wyjątku? Każda podejrzana instrukcja powinna znajdować się w bloku try. Jeżeli blok try zwróci wyjątek, to kod zostanie przekierowany do bloku except: i zostanie wyświetlony komunikat oraz zwrócona wartość nieskończona, ale kod dalej będzie działał.

Klauzula finally jest stosowana przy zaawansowanej obsłudze błędów, wykonywana jest zawsze, gdy rozpączęła się instrukcja try i ma na celu "posprzątanie" po złożonym kodzie przechytującym kilka możliwych błędów lub wyjątków. Więcej na ten temat można znaleść w opisie języka [https://docs.python.org/3/tutorial/errors.html].

Raise - ręczne wywoływanie wyjątków

Klauzula raise służy do samodzielnego wywołania błędu jeżeli zajdzie jakaś sytuacja w kodzie, która zajść nie powinna a o której chcemy ponformować użytkownika. Jest raczej przeznaczona dla zaawansowanego użytkownika (innego programisty), który będzie korzystał z naszych funkcji a nie dla użytkownika końcowego, dla którego komunikat powinien być podany w bardziej przyjaznej formie.

Poniższy przykład wyjaśni stosowanie klauzuli raise. Jeżeli konstruujemy funkcję, do której można przekazać argument z ograniczonej listy argumentów i błędny argument może powodować poważne perturbacje gdzieś w dalszej częśc kodu. Możemy sprawdzić, czy nazwa jest na liście, jeśli nie, możemy spowodować błąd własnego typu - tu nazwany NameError, który poinformuje programistę że zaszedł krytyczny błąd

def select_name(name):
    names = ('Jan', 'John', 'Jon', 'Hans', 'Wania')
    try:
        if name not in names:
            raise NameError('Name <' + name +  '> is not on the list')
    except:
        raise

select_name('Kazik')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in select_name
NameError: 'Name <Kazik> is not on the list'

Assert - upewnianie się

Inną, prostrzą formą upewniania się że dane lub argumenty przekazane do funkcji są prawidłowe jest upewnianie się (ang. assertion). Służy do tego klauzula assert za którą znajduje się test logiczny oraz ewentualny komunikat o błędzie. Funkcja z powyższego przykładu może wyglądać następująco:

def select_name(name):
    names = ('Jan', 'John', 'Jon', 'Hans', 'Wania')
    assert name in names, 'Name <' + name +  '> is not on the list'

select_name('Kazik')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in select_name
AssertionError:' Name <Kazik> is not on the list'

Note: Gdy nie możemy rozwiązać problemu...

  1. Szukamy rozwiązania poprzez system pomocy. Funkcja help nazwa_funkcji pozwala dowiedzieć się jakiej użyć składni, które parametry są opcjonalne, jakie wartości powinny przyjmować poszczególne argumenty

  2. Jeżeli nadal nie działa: [Dokumentacja języka Python: https://docs.python.org/] Pozwala znaleść pełen opis funkcji, z wszystkimi możliwymi argumentami oraz proste przykłady zastosowania

  3. Jeżeli nadal nie działa: StackOverflow – serwis społecznościowy na którym programiści mogą zadawać pytania dotyczące szeroko pojętego wytwarzania oprogramowania. Najprostrzy sposób to zadanie pytania poprzedzonego słowem kluczowym python w wyszukiwarce google, która przekieruje nas na odpowiednie wątki Stackoverflow.

  4. Jeżeli nie znaleźliśmy odpowidzi - zadać pytanie na Stackoverlfow lub na jednej z grup dyskusyjnych. Należy dobrze przygotować pytanie, upewnić się że problem nie został zgłoszony, wyjaśniony.

Ćwiczenie

  1. Dla funkcji z poprzedniego ćwiczenia dodać sprawdzanie, czy:
    • stan morza jest pomiędzy 450 a 600
    • wartość krytyczna sztormu jest większa niż średnia wartość wprowadzonych danych
  2. Rozwiązać dany problem: jak poprosić użytkownika o podanie wartości w czasie działania kodu