PPy3/TematyDodatkowe: Różnice pomiędzy wersjami

Z Brain-wiki
(Utworzono nową stronę "= 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...")
 
 
(Nie pokazano 23 pośrednich wersji utworzonych przez tego samego użytkownika)
Linia 15: Linia 15:
 
*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.
 
*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 ===
+
=== 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''' &mdash; w większości przypadków znajduje się w nich cała informacja konieczna dla naprawienia błędu. Przykład: usiłujemy uruchomić program <tt>błąd.py</tt>, którego treść to
 
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''' &mdash; w większości przypadków znajduje się w nich cała informacja konieczna dla naprawienia błędu. Przykład: usiłujemy uruchomić program <tt>błąd.py</tt>, którego treść to
  
<code lang=python>
+
<source lang=python>
 
x = (2 + 3) 4
 
x = (2 + 3) 4
 
print x
 
print x
</code>
+
</source>
  
 
Wynik pierwszej próby to:
 
Wynik pierwszej próby to:
<code>
+
 
 +
<source lang=bash>
 
$ python3 błąd.py  
 
$ python3 błąd.py  
 
   File "błąd.py", line 1
 
   File "błąd.py", line 1
Linia 31: Linia 32:
 
                 ^
 
                 ^
 
SyntaxError: invalid syntax
 
SyntaxError: invalid syntax
</code>
+
</source>
  
 
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 &mdash; ż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 &mdash; inaczej niż w powszechnie stosowanej notacji matematycznej &mdash; w Pythonie aby uzyskać mnożenie, konieczne jest napisanie jawnie znaku operatora (gwiazdki).
 
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 &mdash; ż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 &mdash; inaczej niż w powszechnie stosowanej notacji matematycznej &mdash; 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:
 
Po naprawieniu tej pomyłki próbujemy jeszcze raz, i wynik to:
<code>
+
<source lang=bash>
 
$ python3 błąd.py  
 
$ python3 błąd.py  
 
   File "błąd.py", line 2
 
   File "błąd.py", line 2
Linia 42: Linia 43:
 
           ^
 
           ^
 
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(x)?
 
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(x)?
</code>
+
</source>
  
 
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ć.
 
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:
 +
 +
<source lang=python>
 +
x, y, z = 3, 4, 5
 +
print(max(x, y, z)
 +
print(X * y * z)
 +
</source>
 +
 +
i wynik jego uruchomienia:
 +
 +
<source lang=bash>
 +
$ python3 błąd.py
 +
  File "błąd.py", line 3
 +
    print(X * y * z)
 +
        ^
 +
SyntaxError: invalid syntax
 +
</source>
 +
 +
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ę <tt>print</tt> &mdash; a nie np. przecinek, albo nawias zamykający &mdash; 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 &mdash; z wynikiem
 +
 +
<source lang=bash>
 +
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
 +
</source>
 +
 +
I tym razem chyba jest wszystko jasne: pojawiła się nazwa <tt>X</tt>, 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.
 +
 +
<blockquote>
 +
Pojawiły się w komunikatach określenia: <tt>SyntaxError</tt>, <tt>NameError</tt>. 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: <tt>TypeError</tt> - związana z przekazaniem wielkości niewłaściwego typu, czy <tt>ValueError</tt> - 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 &mdash; 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).
 +
</blockquote>
 +
 +
=== Asercje ===
 +
 +
Polecenie <tt>assert</tt> ma następującą postać:
 +
 +
<source lang=python>
 +
assert wyrażenie1
 +
# lub:
 +
assert wyrażenie1, wyrażenie2
 +
</source>
 +
 +
i jest to jak widać polecenie proste (nie zawierające bloku poleceń z wcięciem).
 +
 +
Jego działanie jest takie, że: obliczona zostanie wartość <tt>wyrażenie1</tt>; 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 <tt>AssertionError</tt>, co domyślnie przerywa wykonywanie programu i powoduje wypisanie odpowiedniego komunikatu. Jeżeli zastosujemy drugą postać, obliczona zostanie również wartość <tt>wyrażenie2</tt> i wypisana (dokładniej: jej reprezentacja napisowa) jako część komunikatu.
 +
 +
Przyjęło się stosować asercje jako sposób kontroli poprawności działania programu &mdash; 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):
 +
 +
<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 &mdash; 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.''
 +
 +
== Trochę więcej o klasach i obiektach ==
 +
 +
Warto sobie zdawać sprawę, że każda wartość występująca w programie pythonowym jest obiektem &mdash; a więc jest wyposażona w zestaw atrybutów i metod. Dotyczy to nie tylko kolekcji (list, słowników, ...), obiektów takich jak otwarte pliki (wynik wywołania funkcji <tt>open()</tt>), napisów &mdash; ale również np. liczb, czy to całkowitych, czy zmiennoprzecinkowych (i zespolonych, o których nie było tutaj mowy).
 +
 +
Przykładowo:
 +
 +
<source lang=python>
 +
(17).bit_length()
 +
→ 5
 +
(17).bit_count()
 +
→ 2
 +
</source>
 +
 +
Co to właściwie znaczy?
 +
 +
Dla liczby 17 wywołaliśmy najpierw metodę, który mówi nam jaka jest długość w bitach reprezentacji dwójkowej tej liczby (<tt>17 == 0b10001</tt>), a następnie metodę, zwracającą liczbę ''ustawionych'' (tj. równych jeden) bitów w tej reprezentacji. Nawiasy okrągłe są konieczne, ponieważ kropka po cyfrach oznaczałaby, że mamy do czynienia z zapisem liczby zmiennoprzecinkowej. Inaczej mówiąc, byłyby niepotrzebne gdybyśmy mieli do czynienia z nazwą oznaczającą liczbę całkowitą, a nie z jej zapisem dziesiętnym.
 +
 +
Każdy obiekt w Pythonie jest ''instancją'' pewnej ''klasy''. Przykłady klas to: <tt>int</tt> (liczby całkowite), <tt>str</tt> (napisy), <tt>list</tt> (listy), <tt>bool</tt> (wartości logiczne), i wiele innych. Klasa to inaczej typ obiektów; charakteryzuje ona zestaw atrybutów i metod związanych z obiektami danej klasy.
 +
 +
<blockquote><small>
 +
Zazwyczaj różnych obiektów będących instancjami danej klasy może być wiele, potencjalnie nieskończenie wiele. Są jednak wyjątki. Np. klasa <tt>bool</tt> posiada jedynie dwie instancje: (<tt>True</tt> i <tt>False</tt>). A obiekt <tt>None</tt> jest wręcz jedyną instancją swojej klasy.
 +
</small></blockquote>
 +
 +
Niektóre klasy są ,,szczególnymi przypadkami" innych klas &mdash; mówi się, że klasa C ''dziedziczy'' z klasy B, lub że jest ''podklasą'' klasy B. Oznacza to tyle, że instancje klasy C posiadają wszystkie atrybuty i metody jakie posiadają instancje klasy B, ale być może pewne dodatkowe &mdash; a być może niektóre z nich zostały w jakiś sposób zmodyfikowane. Przykładowo, klasa <tt>bool</tt> stanowi podklasę klasy <tt>int</tt>; jej jedyne instancje, <tt>True</tt> i <tt>False</tt>, to ,,tak naprawdę" liczby (odpowiednio) 1 i 0 ,,w przebraniu". Są one więc również instancjami klasy <tt>int</tt> i można do nich stosować te same operacje (np. arytmetyczne) co do liczb całkowitych.
 +
 +
<blockquote><small>
 +
Jeszcze inaczej: jeżeli klasa C jest podklasą klasy B, to każda instancja klasy C jest zarazem instancją klasy B &mdash; można to sprawdzić za pomocą funkcji <tt>isinstance(obiekt, klasa)</tt>.
 +
 +
Same klasy są również obiektami, instancjami klasy o nazwie <tt>type</tt>. Wywołana jako funkcja, <tt>type(obiekt)</tt> zwraca klasę danego obiektu.
 +
</small></blockquote>
 +
 +
Poza tym,że Python posiada całkiem sporo klas, czyli typów obiektów, wbudowanych &mdash; sporą część z nich, chociaż nie wszystkie, omówiliśmy w poprzednich rozdziałach &mdash; to jest wiele dalszych klas zdefiniowanych w bibliotece standardowej, do których można uzyskać dostęp importując odpowiednie moduły; oczywiście jest też bogactwo modułów i bibliotek poza biblioteką standardową. Większość z nich można znaleźć w dedykowanym ,,magazynie", https://pypi.org/.  Pisząc kod w Pythonie można również definiować własne klasy; temat ten został pominięty w tym wstępnym kursie, ale na pewno zasługuje on na pierwsze miejsce w jego dalszym ciągu, kursie dla średnio zaawansowanych.
 +
 +
''CDN''
 +
 +
== O generatorach ==
 +
 +
W jednym z poprzednich rozdziałów [[PPy3/Kolekcje#Przyk.C5.82ad:_suma_cyfr|poznaliśmy wyrażenia generatorowe]]. Przypomnijmy, że stanowią one uproszczony zapis iteracji, pozwalający na uniknięcie w wielu prostych przypadkach pętli <tt>for</tt> i tworzenia zmiennych pomocniczych. Przykładowo:
 +
 +
<source lang=python>
 +
kwadraty = (x**2 for x in range(100))
 +
</source>
 +
 +
tworzy generator, ''produkujący'' w iteracji kwadraty kolejnych liczb naturalnych (od 0 do 99).
 +
 +
Poważnym ograniczeniem wyrażeń generatorowych jest to, że można ich używać do przekształcenia jednego obiektu iterowalnego w drugi &mdash; w tym przypadku, ciągu liczb naturalnych w ciąg ich kwadratów, natomiast nie ma jak za pomocą wyrażenia generatorowego stworzyć obiektu iterowalnego ,,od zera". Można to uzyskać, stosując ogólniejszą konstrukcję:
 +
 +
<source lang=python>
 +
def gen_kwadraty(start=0):
 +
    k = start
 +
    while True:
 +
        yield k**2
 +
        k += 1
 +
 +
kwadraty = gen_kwadraty()
 +
</source>
 +
 +
To, co napisałem powyżej przypomina bardzo definicję funkcji; jedyną różnicą na poziomie składni jest wystąpienie w definicji słowa <tt>yield</tt> (i brak wystąpienia słowa <tt>return</tt>). W istocie <tt>gen_kwadraty</tt> można uważać za funkcję &mdash; której wywołanie zwraca generator: obiekt, którego iteracja dostarczy kwadratów kolejnych liczb naturalnych, począwszy od liczby <tt>start</tt>, argumentu wywołania, o domyślnej wartości zero. Zauważmy, że w budowie definicji nie użyliśmy obiektu <tt>range</tt>; użyliśmy za to jawnej pętli <tt>while</tt>, niejako opakowując ją w generator. Co więc w ten sposób zyskujemy?
 +
 +
Przede wszystkim wielką elastyczność. W pokazanym przykładzie zysk jest niewielki, ale w definicji generatora możemy użyć jakiejkolwiek bądź reguły tworzenia kolejnych elementów iteracji. Nie potrzebujemy utworzyć ich wszystkich zanim zaczniemy z nich korzystać &mdash; tak jak byłoby w przypadku, gdybyśmy <tt>gen_kwadraty</tt> zdefiniowali jako funkcję zwracającą listę kwadratów kolejnych liczb. Co więcej, iteracja zapewniona przez <tt>gen_kwadraty</tt> może nam dostarczyć dowolnie wielu elementów (oczywiście w realnym komputerze po dostatecznie długim czasie osiągnięto by liczby, których wielkość przekroczyłaby pojemność dostępnej pamięci operacyjnej). Decyzja o tym, kiedy przerwać iterację pozostawiona jest ,,użytkownikowi", tzn. kodowi odwołującemu się do obiektu <tt>kwadraty</tt>. 
 +
 +
 +
----
 +
 +
[[PPy3/Matplotlib|poprzednie]] | [["Programowanie z Pythonem3"|strona główna]] | [[PPy3/DobrePraktyki|dalej]]
 +
 +
--[[Użytkownik:RobertJB|RobertJB]] ([[Dyskusja użytkownika:RobertJB|dyskusja]]) 14:26, 27 mar 2018 (CEST)

Aktualna wersja na dzień 11:32, 13 lip 2023

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.

Trochę więcej o klasach i obiektach

Warto sobie zdawać sprawę, że każda wartość występująca w programie pythonowym jest obiektem — a więc jest wyposażona w zestaw atrybutów i metod. Dotyczy to nie tylko kolekcji (list, słowników, ...), obiektów takich jak otwarte pliki (wynik wywołania funkcji open()), napisów — ale również np. liczb, czy to całkowitych, czy zmiennoprzecinkowych (i zespolonych, o których nie było tutaj mowy).

Przykładowo:

(17).bit_length()
 5
(17).bit_count()
 2

Co to właściwie znaczy?

Dla liczby 17 wywołaliśmy najpierw metodę, który mówi nam jaka jest długość w bitach reprezentacji dwójkowej tej liczby (17 == 0b10001), a następnie metodę, zwracającą liczbę ustawionych (tj. równych jeden) bitów w tej reprezentacji. Nawiasy okrągłe są konieczne, ponieważ kropka po cyfrach oznaczałaby, że mamy do czynienia z zapisem liczby zmiennoprzecinkowej. Inaczej mówiąc, byłyby niepotrzebne gdybyśmy mieli do czynienia z nazwą oznaczającą liczbę całkowitą, a nie z jej zapisem dziesiętnym.

Każdy obiekt w Pythonie jest instancją pewnej klasy. Przykłady klas to: int (liczby całkowite), str (napisy), list (listy), bool (wartości logiczne), i wiele innych. Klasa to inaczej typ obiektów; charakteryzuje ona zestaw atrybutów i metod związanych z obiektami danej klasy.

Zazwyczaj różnych obiektów będących instancjami danej klasy może być wiele, potencjalnie nieskończenie wiele. Są jednak wyjątki. Np. klasa bool posiada jedynie dwie instancje: (True i False). A obiekt None jest wręcz jedyną instancją swojej klasy.

Niektóre klasy są ,,szczególnymi przypadkami" innych klas — mówi się, że klasa C dziedziczy z klasy B, lub że jest podklasą klasy B. Oznacza to tyle, że instancje klasy C posiadają wszystkie atrybuty i metody jakie posiadają instancje klasy B, ale być może pewne dodatkowe — a być może niektóre z nich zostały w jakiś sposób zmodyfikowane. Przykładowo, klasa bool stanowi podklasę klasy int; jej jedyne instancje, True i False, to ,,tak naprawdę" liczby (odpowiednio) 1 i 0 ,,w przebraniu". Są one więc również instancjami klasy int i można do nich stosować te same operacje (np. arytmetyczne) co do liczb całkowitych.

Jeszcze inaczej: jeżeli klasa C jest podklasą klasy B, to każda instancja klasy C jest zarazem instancją klasy B — można to sprawdzić za pomocą funkcji isinstance(obiekt, klasa).

Same klasy są również obiektami, instancjami klasy o nazwie type. Wywołana jako funkcja, type(obiekt) zwraca klasę danego obiektu.

Poza tym,że Python posiada całkiem sporo klas, czyli typów obiektów, wbudowanych — sporą część z nich, chociaż nie wszystkie, omówiliśmy w poprzednich rozdziałach — to jest wiele dalszych klas zdefiniowanych w bibliotece standardowej, do których można uzyskać dostęp importując odpowiednie moduły; oczywiście jest też bogactwo modułów i bibliotek poza biblioteką standardową. Większość z nich można znaleźć w dedykowanym ,,magazynie", https://pypi.org/. Pisząc kod w Pythonie można również definiować własne klasy; temat ten został pominięty w tym wstępnym kursie, ale na pewno zasługuje on na pierwsze miejsce w jego dalszym ciągu, kursie dla średnio zaawansowanych.

CDN

O generatorach

W jednym z poprzednich rozdziałów poznaliśmy wyrażenia generatorowe. Przypomnijmy, że stanowią one uproszczony zapis iteracji, pozwalający na uniknięcie w wielu prostych przypadkach pętli for i tworzenia zmiennych pomocniczych. Przykładowo:

 kwadraty = (x**2 for x in range(100))

tworzy generator, produkujący w iteracji kwadraty kolejnych liczb naturalnych (od 0 do 99).

Poważnym ograniczeniem wyrażeń generatorowych jest to, że można ich używać do przekształcenia jednego obiektu iterowalnego w drugi — w tym przypadku, ciągu liczb naturalnych w ciąg ich kwadratów, natomiast nie ma jak za pomocą wyrażenia generatorowego stworzyć obiektu iterowalnego ,,od zera". Można to uzyskać, stosując ogólniejszą konstrukcję:

 def gen_kwadraty(start=0):
     k = start
     while True:
         yield k**2
         k += 1

 kwadraty = gen_kwadraty()

To, co napisałem powyżej przypomina bardzo definicję funkcji; jedyną różnicą na poziomie składni jest wystąpienie w definicji słowa yield (i brak wystąpienia słowa return). W istocie gen_kwadraty można uważać za funkcję — której wywołanie zwraca generator: obiekt, którego iteracja dostarczy kwadratów kolejnych liczb naturalnych, począwszy od liczby start, argumentu wywołania, o domyślnej wartości zero. Zauważmy, że w budowie definicji nie użyliśmy obiektu range; użyliśmy za to jawnej pętli while, niejako opakowując ją w generator. Co więc w ten sposób zyskujemy?

Przede wszystkim wielką elastyczność. W pokazanym przykładzie zysk jest niewielki, ale w definicji generatora możemy użyć jakiejkolwiek bądź reguły tworzenia kolejnych elementów iteracji. Nie potrzebujemy utworzyć ich wszystkich zanim zaczniemy z nich korzystać — tak jak byłoby w przypadku, gdybyśmy gen_kwadraty zdefiniowali jako funkcję zwracającą listę kwadratów kolejnych liczb. Co więcej, iteracja zapewniona przez gen_kwadraty może nam dostarczyć dowolnie wielu elementów (oczywiście w realnym komputerze po dostatecznie długim czasie osiągnięto by liczby, których wielkość przekroczyłaby pojemność dostępnej pamięci operacyjnej). Decyzja o tym, kiedy przerwać iterację pozostawiona jest ,,użytkownikowi", tzn. kodowi odwołującemu się do obiektu kwadraty.



poprzednie | strona główna | dalej

--RobertJB (dyskusja) 14:26, 27 mar 2018 (CEST)