PPy3/TematyDodatkowe: Różnice pomiędzy wersjami
m (→Asercje) |
|||
Linia 105: | Linia 105: | ||
=== Wyjątki w trakcie pracy programu === | === Wyjątki w trakcie pracy programu === | ||
+ | |||
+ | Wyjątki występujące w trakcie pracy programu można przechwycić i obsłużyć za pomocą następującej konstrukcji (polecenia złożonego): | ||
+ | |||
+ | <source lang=python> | ||
+ | try: | ||
+ | # operacje mogące spowodować wyjątek | ||
+ | except (KlasaWyjątku1, KlasaWyjątku2, ...): | ||
+ | # co zrobić jeśli wyjątek jednej z tych klas wystąpił | ||
+ | else: | ||
+ | # co zrobić jeśli nie było wyjątku | ||
+ | finally: | ||
+ | # co zrobić w KAŻDYM przypadku | ||
+ | </source> | ||
+ | |||
+ | *bloki <tt>else:</tt> i <tt>finally:</tt> są nieobowiązkowe, tj. możemy je pominąć (wraz z odp. linijką wprowadzającą) | ||
+ | *w bloku <tt>except:</tt> można podać jedną klasę wyjątku (wtedy zbędne są nawiasy), lub żadnej — wtedy przechwycimy ''każdy'' wyjątek; jeśli jednak chcemy, jak w przykładzie, wymienić więcej niż jedną klasę, to nawiasy są konieczne | ||
+ | *skuteczne przechwycenie wyjątku sprawia, że wykonanie programu nie ulegnie przerwaniu w punkcie jego wystąpienia, tyko będzie kontynuowane, również po opuszczeniu bloku <tt>try:</tt> | ||
+ | *można jednak spowodować, aby po wykonaniu czynności obsługi wyjątku, wyjątek został niejako ''wznowiony''; wystarczy w bloku <tt>except:</tt> wywołać instrukcję <tt>raise</tt>. Dla większości klas wyjątków skutkuje to zakończeniem programu | ||
+ | *instrukcję <tt>raise</tt> można wywołać również w dowolnym innym punkcie programu, powinna wówczas w niej wystąpić nazwa klasy wyjątku, jaki chcemy wywołać. | ||
+ | |||
+ | Przykłady: | ||
+ | |||
+ | ''CDN.'' | ||
== Klasy własnej produkcji == | == Klasy własnej produkcji == |
Wersja z 13:44, 2 lip 2018
Spis treści
Tematy dodatkowe
Błędy i wyjątki
Każdy programista popełnia błędy — jest to nieunikniona część tej działalności.
- Są błędy, które można nazwać banalnymi: literówka w nazwie lub słowie kluczowym, zapomniany dwukropek, niedomknięte nawiasy, itp.
- Są bardziej podstępne błędy logiczne: gdy algorytm, którego zapisem ma być tworzony kod, po prostu nie robi dokładnie tego, co nam się wydaje.
- I w końcu są sytuacje wyjątkowe: gdy w toku pracy programu zachodzą nieoczekiwane okoliczności — użytkownik podał błędną nazwę pliku z danymi, zawartość tego pliku nie jest zgodna z oczekiwaniami programu, połączenie sieciowe ulega przerwaniu w trakcie pobierania lub przesyłania danych, itp.
Wszystkie te sytuacje nazywa się wyjątkami, i tym samym słowem określa się mechanizm, jakiego wiele współczesnych języków programowania (w tym Python) dostarcza programiście, aby mu pomóc określić działania jakie program miałby podjąć w sytuacjach wyjątkowych. Wyjątkowość takich sytuacji generalnie polega na tym, że w przypadku ich wystąpienia program po prostu nie może kontynuować przewidzianego toku działania:
- błąd w składni programu oznacza, że kod nie daje się zinterpretować w sposób jednoznaczny; najlepsze co można zrobić, to w momencie wykrycia błędu przerwać działanie i spróbować dostarczyć programiście (w formie komunikatu) informację ułatwiającą określenie i naprawienie błędu;
- błędne działanie algorytmu można np. spróbować wykrywać za pomocą tzw. asercji (polecenie assert) badających, czy pewne warunki, jakie powinny być spełnione przez dane przetwarzane w programie są istotnie spełnione;
- wyjątki powstałe na skutek okoliczności zachodzących w trakcie działania programu, uniemożliwiających jego normalną kontynuację, można spróbować przewidzieć i przygotować program do odpowiedniego zareagowania, czyli obsługi wyjątku. Na przykład, można spróbować ponowić jeszcze raz (lub kilka razy) próbę łączności sieciowej, i poddać się dopiero po określonej liczbie niepowodzeń; natomiast w sytuacji błędu wynikającego z podania przez użytkownika programu niewłaściwych argumentów uruchomienia, warto postarać się o przekazanie użytkownikowi komunikatu informującego go w sposób dla niego zrozumiały, o właściwym sposobie uruchamiania programu.
Błędy składniowe i podobne
Jak wspomniano powyżej, w przypadku napotkania błędu składniowego w kodzie programu interpreter natychmiast przerwie wykonywanie programu i wypisze komunikat. Komunikaty te warto czytać uważnie — w większości przypadków znajduje się w nich cała informacja konieczna dla naprawienia błędu. Przykład: usiłujemy uruchomić program błąd.py, którego treść to
x = (2 + 3) 4
print x
Wynik pierwszej próby to:
$ python3 błąd.py
File "błąd.py", line 1
x = (2 + 3) 4
^
SyntaxError: invalid syntax
Z komunikatu całkiem jasno wynika, że napotkany został błąd składni (SyntaxError), że wykryto ten błąd w linijce 1, a nawet dokładnie — że miejscem, w którym interpreter stwierdził, że ma do czynienia z nieprawidłowy kodem, było wystąpienie cyfry 4 po zamykającym nawiasie. Po prostu zapomnieliśmy, że — inaczej niż w powszechnie stosowanej notacji matematycznej — w Pythonie aby uzyskać mnożenie, konieczne jest napisanie jawnie znaku operatora (gwiazdki).
Po naprawieniu tej pomyłki próbujemy jeszcze raz, i wynik to:
$ python3 błąd.py
File "błąd.py", line 2
print x
^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(x)?
Tutaj już jaśniej być nie może; interpreter nie tylko wskazał nam dokładne miejsce wystąpienia błędu, ale nawet dokładnie opisał, jak go naprawić.
Oczywiście nie zawsze interpreter będzie w stanie wskazać nam błąd aż tak precyzyjnie. Jako przykład weźmy następujący plik:
x, y, z = 3, 4, 5
print(max(x, y, z)
print(X * y * z)
i wynik jego uruchomienia:
$ python3 błąd.py
File "błąd.py", line 3
print(X * y * z)
^
SyntaxError: invalid syntax
Dlaczego został wytknięty błąd składniowy w linijce 3? Przecież to w linijce 2 zapomnieliśmy zamknąć jeden z nawiasów.
Otóż jeżeli z końcem linii Python stwierdza, że nie wszystkie dotąd otwarte nawiasy zostały zamknięte, to traktuje to jako oznaczające, że kolejną linijkę należy potraktować jako ciąg dalszy bieżącej. I w tym przypadku dopiero napotykając w kolejnej linijce nazwę print — a nie np. przecinek, albo nawias zamykający — stwierdza, że coś musi być nie tak. Miejsce wykrycia błędu niestety nie zawsze się będzie pokrywało z miejscem jego faktycznego wystąpienia.
Po poprawce, czyli zamknięciu nawiasu na linii 2, próbujemy jeszcze raz — z wynikiem
python3 błąd.py
5
Traceback (most recent call last):
File "błąd.py", line 3, in <module>
print(X * y * z)
NameError: name 'X' is not defined
I tym razem chyba jest wszystko jasne: pojawiła się nazwa X, z którą wcześniej nie związano żadnej wartości. Po prostu przez pomyłkę napisaliśmy X wielką literą zamiast małą.
Te przykłady pokazują, że przy wykorzystaniu treści wypisywanych komunikatów proces usunięcia z programu błędów składniowych i formalnych może być dość szybki i bezbolesny.
Pojawiły się w komunikatach określenia: SyntaxError, NameError. Są to przykłady nazw tzw. klas wyjątków, czyli ogólnie rozumianych kategorii błędów, jakie są sygnalizowane. Wkrótce spotkamy dalsze klasy wyjątków, takie jak: TypeError - związana z przekazaniem wielkości niewłaściwego typu, czy ValueError - gdy nie typ. ale wartość wielkości, na której usiłuje się operować, jest niewłaściwa dla danej operacji. Dowiemy się również, dlaczego klasy wyjątków są przydatne — pomimo tego, iż dla ludzkich oczu zwykle bardziej przydatna jest słowna informacja uszczegółowiająca (to co pojawia się po nazwie klasy wyjątku i dwukropku).
Asercje
Polecenie assert ma następującą postać:
assert wyrażenie1
# lub:
assert wyrażenie1, wyrażenie2
i jest to jak widać polecenie proste (nie zawierające bloku poleceń z wcięciem).
Jego działanie jest takie, że: obliczona zostanie wartość wyrażenie1; jeżeli jest ona prawdziwa, nie dzieje się nic (następuje natychmiastowe przejście do kolejnej linijki kodu). Jeżeli okaże się fałszywa, zostanie zasygnalizowany wyjątek, klasy AssertionError, co domyślnie przerywa wykonywanie programu i powoduje wypisanie odpowiedniego komunikatu. Jeżeli zastosujemy drugą postać, obliczona zostanie również wartość wyrażenie2 i wypisana (dokładniej: jej reprezentacja napisowa) jako część komunikatu.
Przyjęło się stosować asercje jako sposób kontroli poprawności działania programu — w tym sensie, aby wykluczyć sytuacje „niemożliwe", tj. powstałe w wyniku błędu programisty. Ze względu na prostotę składni warto też z nich korzystać w kodzie służącym do testowania poprawności innego kodu (czyli np. w funkcjach testujących, czy inne, bardziej złożone funkcje dają poprawne wyniki w prostych sytuacjach próbnych).
Wyjątki w trakcie pracy programu
Wyjątki występujące w trakcie pracy programu można przechwycić i obsłużyć za pomocą następującej konstrukcji (polecenia złożonego):
try:
# operacje mogące spowodować wyjątek
except (KlasaWyjątku1, KlasaWyjątku2, ...):
# co zrobić jeśli wyjątek jednej z tych klas wystąpił
else:
# co zrobić jeśli nie było wyjątku
finally:
# co zrobić w KAŻDYM przypadku
- bloki else: i finally: są nieobowiązkowe, tj. możemy je pominąć (wraz z odp. linijką wprowadzającą)
- w bloku except: można podać jedną klasę wyjątku (wtedy zbędne są nawiasy), lub żadnej — wtedy przechwycimy każdy wyjątek; jeśli jednak chcemy, jak w przykładzie, wymienić więcej niż jedną klasę, to nawiasy są konieczne
- skuteczne przechwycenie wyjątku sprawia, że wykonanie programu nie ulegnie przerwaniu w punkcie jego wystąpienia, tyko będzie kontynuowane, również po opuszczeniu bloku try:
- można jednak spowodować, aby po wykonaniu czynności obsługi wyjątku, wyjątek został niejako wznowiony; wystarczy w bloku except: wywołać instrukcję raise. Dla większości klas wyjątków skutkuje to zakończeniem programu
- instrukcję raise można wywołać również w dowolnym innym punkcie programu, powinna wówczas w niej wystąpić nazwa klasy wyjątku, jaki chcemy wywołać.
Przykłady:
CDN.