Jak na imię ma siostra Elsy?

Posted on 2019-02-03

Wyrażenia logiczne, instrukcje warunkowe - to jedno z postawowych zagadnień, w zasadzie dla każdego programisty.

Na początek odpowiedź - siostra Elsy to Ifla. Taki dowcip, trochę hermetyczny, ale - trzeba się przyzwyczajać...

Często mówiąc o wyrażeniach logicznych mówi się o algebrze Boole'a. Nazwa pochodzi od nazwiska George’a Boole’a, któremu przypisuje się opracowanie stosownej algebry. Tak, podobnie jak wiele innych zagadnień, operacje logiczne mają podstawy matematyczne.

Wyrażenia logiczne

Czym są wyrażenia logiczne? Jest mniej więcej jak w szkole. Zamiast znaczków stosujemy a and b, c or d - żeby wyrazić, że wyrażenie jest prawdą, jeżeli odpowiednio a i b są prawdą, albo c lub d. Ważne jest to, że a, b, c i d mogą być zmienną, albo wywołaniem funkcji, która zwróci wartość logiczną. Tu jeszcze doprecyzuję, ale nie wszystko na raz. Tak to działa

a = True
b = False
a and b

c = True
d = True
c or d

Kolejność działań

A co jeżeli zrobię:

a = True
b = True
c = True
d = False
a and b or c and d

Działa to tak, że na raz wykonuje jedną operację, czyli np. a and b, a potem wynik tego - nazwijmy go x, użyty jest w kolejnej operacji x or c... A może, kolejna operacja to nie będzie x or c? Na szczęście nie jesteśmy skazani na łaskę bezdusznej maszyny. Są nawiasy - można wymusić kolejność działań:

a = True
b = True
c = True
d = False
((a and b) or c) and d

Wynik taki sam, a może inny? Programista powinien wiedzieć. Co więcej - powinien wiedzieć dlaczego.

Operacje logiczne, jak też inne operacje mają priorytet. Tak jak w matematyce: 2+2*2 to 6, a nie 8, bo najpierw wykonujemy mnożenie. A jak to ma Python? - tu jest ściągawka. Czyli jak mam listę działań, tą wyżej, bez nawiasów, to najpierw zostaną wykonane obie operacje and, a ich wyniki zostaną użyte w operacji or.

Do pełni szczęścia potrzebna jeszcze paru rzeczy.

Na początek porównanie. Przypisanie wykonuje operator =. Porównanie to == - jeżeli chcemy sprawdzić, czy to co jest po prawej stronie jest równe temu co jest po lewej. Natomiast != spradza, czy się różnią:

a = 'Ala'
b = 'Kot'
a == b
a != b

Jest jeszcze negacja, którą wykonujemy przy pomocy operatora not:

a = 'Ala'
b = 'Kot'
not a == b

Sprawdzamy ściągawkę - == ma wyższy priorytet niż not - czyli najpierw zostanie wykonane porównanie, a dopiero potem wynik zostanie zanegowany.

Po co to jest?

Skoro takie podstawowe, i takie ważne to należałoby wpomnieć po co to jest. Wyrażenia logiczne używane są przez instrukcje warunkowe. Tytułowe Ifla z Elsą:

a = True
b = True
c = True
d = False
if a and b or c and d:
    print('Your secret files are deleted')
else:
    print('Great job! Secret files are safe!')

To tylko coś wypisuje na ekranie, ale... w życiu może być tak, że coś się skasuje. Dlatego zrozumienie jak to działa jest tak waże.

Wyrażenia logiczne na początku Pythona

Kiedyś dawno temu, za siedmioma firewallami, jeden człowiek nazwiskiem Guido van Rossum wymyślił język programowania - Python. Większość języków ma osobny typ - bool/Boolean. Co dość zaskakujące na początku Python nie miał osobnego typu odpowiadzialnego za przechowywanie True/ False - czy też prawdy/fałszu. Jak sobie radzili? Bardzo prosto - wszystko, co było puste, było fałszem, a coś co nie było puste - było prawdą. Jakie ma to znaczenie praktyczne, skoro dziś mogę napisać a = True? A no do takiego czegoś:

a = ''
b = a || 'test'

Jeżeli zmienna a jest pusta, to b z automatu dostanie wartość test. Inny kod robiący dokładnie to samo:

a = ''
if a == '':
    b = 'test'
else:
    b = a

Czasem jest do napisania funkcja, która na początku sprawdza, czy przekazanie parametry są wypełnione, a jeżeli nie to je wypełnia. Można napisać:

def a(b, c):
    b = b || 'in for b'
    c = c || 5

Optymalizacja w wyrażeniach logicznych

Kolejnym istotnym aspektem jest pomijanie wykonania. To jest rodzaj optymalizacji. Program, jeżeli się zorientuje, że wynik wyrażenia logicznego jest znany, nawet jeżeli nie zostało ono przeprocesowane do końca pomija resztę wyrażenia. Czyli:

False and super_hipper_function()

Spowoduje, że super_hipper_function nie zostanie wykonane. O ile taki przykład jest dość oczywisty, to warto pamiętać, żeby rzeczy ważne wykonywać explicite, a nie przy okazji wyrażeń logicznych.

Pustka

Chyba ostatni element układanki.

Logika ze szkoły zakłada, że wyrażenie logiczne ma dwie możliwe wartości - prawdę i fałsz. W programowaniu często występuje logika trójwartościowa. Coś w stylu: prawda, fałsz, nie wiem. Być może nie jest to doskonała analogia, gdyż dotyczy nie tylko wyrażeń logicznych. Dla przykładu - jest klient banku, któremu dopiero zakładamy konto. Numer konta jest jednym z pól opisujących klienta. Zanim nie utworzymy konta, nie ma jak wpisać tam numeru, a klient jest. Wtedy wpisujemy tam null, czyli brak. Python, do reprezentacji takiego stanu używa None. Dostępny jest osobny operator do sprawdzania, czy coś jest puste:

a = None
a is None

Jest to o tyle istotne, że None to coś pustego, czyli fałsz... Rolą programisty jest zapanowanie nad tym co ma na myśli.