# setup
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
mce = ['styczen', 'luty', 'marzec', 'kwiecien', 'maj', 'czerwiec', 'lipiec', 'sierpien', 'wrzesien', 'pazdziernik', 'listopad', 'grudzien']
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.
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.
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:
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]
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ść.
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.))
dzielenie(2.0,0)
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].
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'
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'
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
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
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.
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.