Skip to content

Latest commit

 

History

History
641 lines (497 loc) · 19.7 KB

03-conditional.mkd

File metadata and controls

641 lines (497 loc) · 19.7 KB

Wykonanie warunkowe

Wyrażenia logiczne

\index{wyrażenie!logiczne} \index{logiczne, wyrażenie} \index{operator!logiczny} \index{logiczny, operator}

Wyrażenie logiczne to wyrażenie, które jest prawdziwe lub fałszywe. Poniższe przykłady używają operatora ==, który porównuje dwa operandy i zwraca True, jeśli są one równe, lub False w przeciwnym wypadku:

>>> 5 == 5
True
>>> 5 == 6
False

True i False są specjalnymi wartościami, które należą do klasy bool; nie są one napisami:

\index{True, wartość specjalna} \index{False, wartość specjalna} \index{wartość specjalna!True} \index{wartość specjalna!False} \index{typ!logiczny} \index{typ!bool}

>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>

Operator == jest jednym z operatorów porównania; pozostałe to:

x != y               # x nie jest równe y
x > y                # x jest większe od y
x < y                # x jest mniejsze od y
x >= y               # x jest większe lub równe y
x <= y               # x jest mniejsze lub równe y
x is y               # x jest tym samym co y
x is not y           # x nie jest tym samym co y

Choć powyższe operacje pewnie już znasz, symbole używane przez Pythona różnią się od symboli matematycznych. Częstym błędem jest użycie pojedynczego znaku równości (=) zamiast znaku podwójnej równości (==). Pamiętaj, że = jest operatorem przypisania, a == jest operatorem porównania. Nie ma znaków takich jak =< lub =>.

\index{porównanie, operator} \index{operator!porównania}

Operatory logiczne

\index{operator!logiczny} \index{logiczny, operator}

Istnieją trzy operatory logiczne: and, or i not. Semantyka (znaczenie) tych operatorów jest podobna do ich znaczenia w języku angielskim. Na przykład wyrażenie

x > 0 and x < 10

jest prawdziwe tylko wtedy, gdy wartość zmiennej x jest większa niż 0 i mniejsza niż 10.

\index{operator!and} \index{operator!or} \index{operator!not}

Wyrażenie n%2 == 0 or n%3 == 0 jest prawdziwe, jeśli którykolwiek z tych warunków jest prawdziwy, tzn. jeśli liczba jest podzielna przez 2 lub 3.

Operator not neguje wyrażenie logiczne, więc not (x > y) jest prawdziwe, jeśli x > y jest fałszywe, tzn. jeśli wartość x jest mniejsza lub równa y.

Ściślej mówiąc, operandy operatorów logicznych powinny być wyrażeniami logicznymi, ale Python nie jest tutaj bardzo restrykcyjny. Każda dodatnia liczba całkowita jest interpretowana jako "prawdziwa".

>>> 17 and True
True

Taka elastyczność może być użyteczna, ale istnieją pewne niuanse, które mogą być mylące. Lepiej ich unikać, dopóki nie będziesz pewien, że wiesz, co robisz.

Instrukcja warunkowa

\index{warunkowa, instrukcja} \index{instrukcja!warunkowa} \index{if, instrukcja} \index{instrukcja!if} \index{if, słowo kluczowe} \index{słowo kluczowe!if} \index{wykonanie warunkowe}

Aby napisać użyteczny program, prawie zawsze potrzebujemy możliwości sprawdzenia pewnych warunków i dostosowania do nich zachowania programu. Instrukcje warunkowe dają nam tę możliwość. Najprostszą formą jest instrukcja if:

if x > 0 :
    print('x jest dodatnia')

Wyrażenie logiczne po instrukcji if jest nazywane warunkiem. Kończymy wyrażenie if znakiem dwukropka (:), a linia lub linie po wyrażeniu if są wcięte.

Logika instrukcji if

Jeśli warunek logiczny jest prawdziwy, to wykonywane jest wyrażenie we wcięciu. Jeśli warunek logiczny jest fałszywy, to wyrażenie we wcięciu jest pomijane.

\index{warunek} \index{złożona, instrukcja} \index{instrukcja!złożona} \index{ciało!instrukcji} \index{instrukcja!ciało}

Instrukcje if mają taką samą strukturę jak definicje funkcji lub pętle for^[Dowiemy się o funkcjach w rozdziale 4, a o pętlach w rozdziale 5.]. Instrukcja składa się z linii nagłówka, która kończy się znakiem dwukropka (:), po którym następuje wcięty blok (tzw. ciało instrukcji if). Takie instrukcje nazywane są instrukcjami złożonymi, ponieważ rozciągają się na więcej niż jedną linię.

Nie ma górnego ograniczenia co do liczby instrukcji, które mogą pojawić się w ciele instrukcji if, ale musi być co najmniej jedna instrukcja. Czasami warto przygotować ciało if bez instrukcji (zazwyczaj jako miejsce na kod, którego jeszcze nie napisałeś). W takim przypadku możesz użyć pass, który nie robi nic.

\index{pass, instrukcja} \index{instrukcja!pass}

if x < 0 :
    pass          # trzeba obsłużyć wartości ujemne!

Jeśli wpiszesz instrukcję if w interpreterze Pythona, znak zachęty zmieni się z jodełki na trzy kropki, tak aby wskazać, że jesteś w środku bloku instrukcji:

>>> x = 3
>>> if x < 10:
...    print('malutko')
...
malutko
>>>

Kiedy używasz interpretera Pythona, musisz zostawić pustą linię na końcu bloku, bo w przeciwnym razie Python zwróci błąd:

>>> x = 3
>>> if x < 10:
...    print('malutko')
... print('zrobione')
  File "<stdin>", line 3
    print('zrobione')
        ^
SyntaxError: invalid syntax

Pusta linia na końcu bloku instrukcji nie jest konieczna podczas pisania i wykonywania skryptu, ale może poprawić czytelność Twojego kodu.

Wykonanie alternatywnego bloku kodu

\index{wykonanie alternatywne} \index{else, słowo kluczowe} \index{słowo kluczowe!else}

Drugą formą instrukcji if jest wykonanie alternatywne, w którym istnieją dwie możliwości i to warunek określa, która z nich zostanie wykonana. Składnia wygląda następująco:

if x%2 == 0 :
    print('x jest parzysta')
else :
    print('x jest nieparzysta')

Jeśli reszta z dzielenia x przez 2 jest równa 0, to wiemy, że zmienna x jest parzysta, a program wyświetli stosowny komunikat. Jeśli warunek jest fałszywy, to wykonywany jest drugi zestaw poleceń.

Logika if-else

Ponieważ warunek musi być albo prawdziwy, albo fałszywy, dokładnie jedna z alternatyw zostanie wykonana. Te alternatywy nazywane są gałęziami w przepływie wykonania programu.

\index{gałąź}

Warunki powiązane

\index{powiązane, warunki} \index{warunki!powiązane}

Czasami mamy więcej niż dwie możliwości i potrzebujemy więcej niż dwóch gałęzi. Jednym ze sposobów na wyrażenie takiej sytuacji jest warunek powiązany:

if x < y:
    print('x jest mniejsza niż y')
elif x > y:
    print('x jest większa niż y')
else:
    print('x i y są równe')

elif jest skrótem od else if. Tutaj również wykonana zostanie dokładnie jedna gałąź.

Logika if-elif

Nie ma ograniczenia co do liczby instrukcji elif. Jeśli istnieje klauzula else, to musi ona być na końcu (ale samo jej pojawienie się nie jest wymagane).

\index{elif, słowo kluczowe} \index{słowo kluczowe!elif}

if choice == 'a':
    print('Zła odpowiedź')
elif choice == 'b':
    print('Dobra odpowiedź')
elif choice == 'c':
    print('Blisko, ale źle')

Każdy warunek jest kolejno sprawdzany. Jeśli pierwszy jest fałszywy, sprawdzany jest następny itd. Jeśli jeden z nich jest prawdziwy, to wykonywana jest odpowiadająca mu gałąź, a instrukcja jest kończona. Nawet jeśli więcej niż jeden warunek jest prawdziwy, to tylko pierwszy z tych prawdziwych wykonuje swoją gałąź kodu.

Warunki zagnieżdżone

\index{zagnieżdżone, warunki} \index{warunki!zagnieżdżone}

Jeden warunek może być również zagnieżdżony w innym. Mogliśmy zapisać przykład z trzema gałęziami w ten sposób:

if x == y:
    print('x i y są równe')
else:
    if x < y:
        print('x jest mniejsza niż y')
    else:
        print('x jest większa niż y')

Warunek zewnętrzny zawiera dwie gałęzie. Pierwsza gałąź zawiera prostą instrukcję. Druga gałąź zawiera kolejną instrukcję if, która ma dwie własne gałęzie. Te dwie gałęzie to proste instrukcje, chociaż mogłyby być również instrukcjami warunkowymi.

Zagnieżdżone instrukcje if

Chociaż wcięcie tych instrukcji sprawia, że ich struktura jest widoczna, to jednak zagnieżdżone instrukcje warunkowe bardzo szybko stają się trudne do odczytania. Ogólnie rzecz biorąc, jeśli możesz, to ich unikaj.

Operatory logiczne często są sposobem na uproszczenie zagnieżdżonych instrukcji warunkowych. Na przykład możemy przepisać następujący kod za pomocą jednego warunku:

if 0 < x:
    if x < 10:
        print('x jest jednocyfrową dodatnią liczbą.')

Funkcja print() jest wywoływana tylko wtedy, gdy spełnimy oba warunki, więc możemy uzyskać ten sam efekt z operatorem and:

if 0 < x and x < 10:
    print('x jest jednocyfrową dodatnią liczbą.')

Łapanie wyjątków przy użyciu try i except

Wcześniej widzieliśmy fragment kodu, w którym używaliśmy funkcji input() i int() do odczytania i przetworzenia liczby całkowitej wprowadzonej przez użytkownika. Widzieliśmy też, jak takie podejście może być zdradzieckie:

>>> prompt = "Jaka jest prędkość lotu jaskółki bez obciążenia?\n"
>>> speed = input(prompt)
Jaka jest prędkość lotu jaskółki bez obciążenia?
Jakiej jaskółki? Afrykańskiej czy europejskiej?
>>> int(speed)
ValueError: invalid literal for int() with base 10:
>>>

Gdy wykonamy te polecenia w interpreterze Pythona, otrzymujemy od niego prośbę o wprowadzenie danych, interpreter pomyśli "ups!" i przejdzie do naszej następnej instrukcji.

Jeżeli jednak umieścisz ten kod w skrypcie Pythona i wystąpi błąd, to Twój skrypt natychmiast się zatrzyma w miejscu pojawienia się błędu, wyświetlając przy tym informacje z mechanizmu traceback, pozwalającego zobaczyć kolejne wywołania funkcji, które ostatecznie doprowadziły do wystąpienia błędu. Po wystąpieniu błędu skrypt nie wykona kolejnych instrukcji.

\index{traceback}

Oto przykładowy program do konwersji temperatury Fahrenheita na temperaturę Celsjusza:

\index{fahrenheit} \index{celsjusz} \index{konwersja temperatury} \index{temperatura, konwersja}

\VerbatimInput{../code3/fahren.py}

Jeśli uruchomimy ten kod i podamy mu błędne dane wejściowe, to program po prostu zakończy się niepowodzeniem z niemiłym komunikatem o błędzie:

Podaj temperaturę w skali Fahrenheita: 72
22.22222222222222
Podaj temperaturę w skali Fahrenheita: fred
Traceback (most recent call last):
  File "fahren.py", line 2, in <module>
    fahr = float(inp)
ValueError: could not convert string to float: 'fred'

W Pythonie jest wbudowana warunkowa struktura wykonania poleceń, która obsługuje tego typu oczekiwane i nieoczekiwane błędy, zwana "try / except". Idea try i except polega na tym, że wiesz, iż pewna sekwencja instrukcji może sprawić problem i chcesz dodać kilka poleceń do wykonania w przypadku wystąpienia błędu. Te dodatkowe polecenia (blok "except") są ignorowane, gdy nie ma błędu.

Możesz myśleć o try i except w Pythonie jako o "polisie ubezpieczeniowej" na sekwencję poleceń.

Możemy przepisać nasz konwerter temperatury w następujący sposób:

\VerbatimInput{../code3/fahren2.py}

Python zaczyna od wykonania sekwencji poleceń w bloku try. Jeśli wszystko pójdzie dobrze, pomija blok except i przechodzi dalej. Jeśli wystąpi wyjątek w bloku try, Python wyskakuje z bloku try i wykonuje sekwencję instrukcji w bloku except.

Podaj temperaturę w skali Fahrenheita: 72
22.22222222222222
Podaj temperaturę w skali Fahrenheita: fred
Musisz wprowadzić liczbę

Obsługa wyjątku z instrukcją try nazywana jest łapaniem wyjątku. W tym przykładzie klauzula except wypisuje komunikat o błędzie. Ogólnie rzecz biorąc, złapanie wyjątku daje Ci szansę naprawienia problemu, spróbowania ponownie albo przynajmniej zgrabnego zakończenia programu.

Minimalna ewaluacja wyrażeń logicznych

\index{minimalna ewaluacja} \index{ewaluacja, minimalna}

Gdy Python przetwarza wyrażenie logiczne takie jak x >= 2 and (x/y) > 2, ewaluuje je od lewej do prawej strony. Ze względu na definicję and, jeśli x jest mniejsze niż 2, wyrażenie x >= 2 zostanie ewaluowane do False, a więc całe wyrażenie zostanie ewaluowane do False, niezależnie od tego czy (x/y) > 2 zostanie ewaluowane do True czy False.

Gdy Python wykryje, że nic już nie zyska dzięki ewaluacji pozostałej część wyrażenia logicznego, przerywa jego ewaluację i nie wykonuje dalszych obliczeń. Zatrzymanie oceny wyrażenia logicznego, ponieważ ogólna wartość jest już znana, nazywa się minimalną ewaluacją.

\index{strażnik, wzorzec} \index{wzorzec!strażnik}

Konsekwencją specyfiki działania minimalnej ewaluacji jest sprytna technika zwana wzorcem strażnika. Przeanalizujmy następującą sekwencję kodu w interpreterze Pythona:

>>> x = 6
>>> y = 2
>>> x >= 2 and (x/y) > 2
True
>>> x = 1
>>> y = 0
>>> x >= 2 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and (x/y) > 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>

Trzecie obliczenie nie powiodło się, ponieważ Python ewaluował (x/y) i y było zerem, co spowodowało błąd wykonania. Ale pierwszy i drugi przykład nie zakończyły się błędami, ponieważ pierwsza część tych wyrażeń x >= 2 została ewaluowana do False, więc warunek (x/y) nigdy nie został sprawdzony właśnie z powodu reguły minimalnej ewaluacji, przez co nie było tutaj żadnego błędu.

Możemy w następujący sposób skonstruować takie wyrażenie logiczne, aby strategicznie umieścić strażnika tuż przed ewaluacją potencjalnie powodującą błąd:

>>> x = 1
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x >= 2 and (x/y) > 2 and y != 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>

W pierwszym wyrażeniu logicznym, x >= 2 wynosi False, więc ewaluacja kończy się na and. W drugim wyrażeniu logicznym, x >= 2 wynosi True, ale y != 0 wynosi False, więc nigdy nie dochodzimy do próby ewaluacji (x/y).

W trzecim wyrażeniu logicznym, y != 0 jest po obliczeniu (x/y) , więc wyrażenie kończy się błędem.

W drugim wyrażeniu mówimy, że y != 0 działa jak strażnik, tak by zapewnić, że wykonamy (x/y) tylko wtedy, gdy y nie jest zerem.

Debugowanie

\index{debugowanie} \index{traceback}

Mechanizm traceback w Pythonie pojawia się w przypadku wystąpienia błędu; zawiera on wiele informacji, ale czasem może być przytłaczający. Najbardziej użyteczne są zazwyczaj informacje o tym:

  • jaki to był błąd,
  • gdzie ten błąd wystąpił.

Błędy składniowe są zazwyczaj łatwe do znalezienia, ale jest kilka podstępnych przypadków. Błędy związane z tzw. białymi znakami^[Należą do nich spacje, znaki tabulacji, znaki końca linii oraz dowolne inne znaki niemające kształtu na ekranie.] mogą być dla nas trudne do wychwycenia, ponieważ np. spacje i tabulacje są mało widoczne i jesteśmy przyzwyczajeni do ich ignorowania.

\index{białe znaki}

>>> x = 5
>>>  y = 6
  File "<stdin>", line 1
    y = 6
    ^
IndentationError: unexpected indent

W powyższym przykładzie problem polega na tym, że druga linia jest wcięta przez jedną spację. Ale komunikat o błędzie wskazuje na y, co jest mylące. Ogólnie rzecz biorąc, komunikaty o błędach wskazują, gdzie problem został wykryty, ale rzeczywisty błąd może być w kodzie gdzieś wcześniej, czasami w poprzedniej linii.

Ogólnie komunikaty o błędach informują o tym, gdzie wykryto problem, ale często nie tam, gdzie został on popełniony.

Słowniczek

ciało instrukcji : Sekwencja instrukcji zawarta w złożonej instrukcji.\index{ciało!instrukcji}\index{instrukcja!ciało}

gałąź : Jedna z alternatywnych sekwencji instrukcji występujących w instrukcji warunkowej.\index{gałąź}

instrukcja warunkowa : Instrukcja, która kontroluje przepływ wykonywania programu w zależności od pewnego warunku.\index{warunkowa, instrukcja}\index{instrukcja!warunkowa}

instrukcja złożona : Instrukcja składająca się z nagłówka i ciała. Nagłówek kończy się dwukropkiem (:). Ciało jest wcięte w stosunku do nagłówka.\index{złożona, instrukcja}\index{instrukcja!złożona}

minimalna ewaluacja : Kiedy Python jest w trakcie ewaluacji złożonego wyrażenia logicznego i przerywa dalszą ewaluację, ponieważ Python zna końcową wartość wyrażenia bez potrzeby ewaluacji reszty wyrażenia.\index{minimalna ewaluacja}\index{ewaluacja, minimalna}

operator logiczny : Jeden z operatorów, który łączy wyrażenia logiczne: and, or lub not.\index{operator!logiczny}\index{logiczny, operator}

operator porównania : Jeden z następujących operatorów, który porównuje swoje operandy: ==, !=, >, <, >= i <=.\index{porównanie, operator}\index{operator!porównania}

traceback : Lista funkcji, które są wykonywane; pojawia się, gdy wystąpi wyjątek.\index{traceback}

warunek : Wyrażenie logiczne w instrukcji warunkowej określające która gałąź jest wykonywana.\index{warunek}

warunki powiązane : Wyrażenie warunkowe z serią alternatywnych gałęzi.\index{powiązane, warunki}\index{warunki!powiązane}

warunki zagnieżdżone : Instrukcja warunkowa, która pojawia się w jednej z gałęzi innej instrukcji warunkowej.\index{zagnieżdżone, warunki}\index{warunki!zagnieżdżone}

wyrażenie logiczne : Wyrażenie o wartości True lub False.\index{wyrażenie!logiczne}\index{logiczne, wyrażenie}

wzorzec strażnika : Miejsce, w którym konstruujemy wyrażenie logiczne z dodatkowymi porównaniami, tak aby skorzystać z działania minimalnej ewaluacji.\index{strażnik, wzorzec}\index{wzorzec!strażnik}

Ćwiczenia

Ćwiczenie 1

Przepisz ponownie swój program obliczający wynagrodzenie, tak aby dać pracownikowi 1,5 raza większą stawkę godzinową za czas przepracowany powyżej 40 godzin.

Podaj liczbę godzin: 45
Podaj stawkę godzinową: 10
Wynagrodzenie: 475.0

Ćwiczenie 2

Przepisz ponownie swój program płacowy, używając try i except, tak aby elegancko obsługiwał on nienumeryczne dane wejściowe, wyświetlając w takim przypadku wiadomość i kończąc swoje działanie. Poniżej znajdują się wyniki dwóch uruchomień programu:

Podaj liczbę godzin: 20
Podaj stawkę godzinową: dziewięć
Błąd, podaj wartość numeryczną
Podaj liczbę godzin: czterdzieści
Błąd, podaj wartość numeryczną

Ćwiczenie 3

Napisz program, który poprosi użytkownika o wartość pomiędzy 0.0 a 1.0. Jeśli wartość jest poza zakresem, wypisz komunikat o błędzie. Jeśli wartość jest między 0.0 a 1.0, wypisz ocenę, korzystając z poniższej tabeli:

Wartość   Ocena
>= 0.9     5,0
>= 0.8     4,5
>= 0.7     4,0
>= 0.6     3,5
>= 0.5     3,0
 < 0.5     2,0
Podaj wartość: 0.95
5,0
Podaj wartość: doskonała
Niepoprawna wartość
Podaj wartość: 10.0
Niepoprawna wartość
Podaj wartość: 0.75
4,0
Podaj wartość: 0.5
3,0
Podaj wartość: 0.46
2,0

Uruchom program wielokrotnie, jak pokazano powyżej, tak aby przetestować różne wartości.