Ciąg dalszy podstaw

Note

Zanim przejdziesz dalej zapoznaj się z Podstawy programowania w języku Python.

Listy

Lista (list) jest kolejnym typem w Python’ie. Pozwala gromadzić wartości różnych typów w swojego rodzaju kontenerze - jest strukturą danych.

1
>>> members = ["Ewa", "Ala", "Ela", "Ola"]

Do elementów w liście odnosi się po ich indeksach, czyli liczonej od lewej strony pozycji w liście, licząc od zera. Elementy zachowują swoją pozycję w liście.

1
2
3
4
5
6
7
8
>>> members[0]
"Ewa"
>>> members[3]
"Ola"
>>> members[-1]
"Ola"
>>> members[1:3]
["Ala", "Ela"]

Powyższy kod wyciąga pierwszy, czwarty i ostatni element, oraz odcinek listy (slice) od drugiego do czwartego elementu wyłącznie. Taka operacja nazywana jest odnoszeniem się do elementów po indeksach i charakteryzuje się użyciem integer'a w nawiasach kwadratowych [ ].

Lista posiada też metody: append, insert i pop pozwalające manipulować listą.

Następujący kod dopisze string “Karol” do końca listy, następnie doda string “Lena” w trzeciej pozycji, a na koniec wyrzuci ostatni element, który był string'iem “Karol”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> members.append("Karol")
>>> print(members)
['Ewa', 'Ala', 'Ela', 'Ola', 'Karol']
>>> members.insert(2, "Lena")
>>> print(members)
['Ewa', 'Ala', 'Lena', 'Ela', 'Ola', 'Karol']
>>> members.pop(-1)
"Karol"
>>> print(members)
['Ewa', 'Ala', 'Lena', 'Ela', 'Ola']

Krotki (tuple)

Tuple to angielska nazwa na kolejny typ jakim są krotki. Tuple to uproszczone listy, które raz zadeklarowane nie mogą zostać zmienione. Używamy ich np. wtedy kiedy chcemy mieć pewność, że kod podczas swojego działania nigdy nie zmieni wartości takiej tupli. Są immutable (eng: niezmienne).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> example_tuple = (1, 2, 3)  # Nie powinniśmy nazywać naszej zmiennej "tuple", bo to nazwa zastrzeżona w Pythonie.
>>> print(example_tuple[2])    # Podobnie ma się to z "list" i innymi typami, której dalej omawiamy
3
>>> example_tuple.append()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'
>>> example_tuple[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Powyższy AttributeError to błąd wynikający z próby użycia metody, która nie jest obecna na typie tuple. TypeError to inny rodzaj błędu wyniesiony gdy dojdzie do bardzo dosłownej próby modyfikacji zmiennej.

Słowniki (dict)

Słownik to typ który przechowuje wartości pod kluczami - to oznacza, że każdy wpis w słowniku składa się z dwóch “elementów” - klucza i wartości.

1
2
3
>>> family = {"mama": "Halina", "tata": "Stefan"}
>>> family["mama"]
"Halina"

W powyższym przykładzie zadeklarowany został słownik z dwiema wartościami: "Halina" pod kluczem "mama" i "Stefan" pod kluczem "tata". Do wartości przetrzymywanych pod kluczem w słowniku należy się odnieść poprzez klucz (string) w nawiasach kwadratowych.

Edycja słownika odbywa się następująco:

1
2
3
4
5
6
7
>>> family["brat"] = "Piotrek"
>>> family["test1"] = "test1"
>>> print(family)
{'mama': 'Halina', 'tata': 'Stefan', 'brat': 'Piotrek', 'test1': 'test1'}
>>> del family["test1"]
>>> family["brat"] = ["Piotrek", "Marek"]
>>> print(family)

W ten sposób nastąpiło nadpisanie wartości pod kluczami, usunięcie wpisu, a na koniec nadpisanie jednej z wartości listą. Wartościami słownika mogą być różne typy (podobnie jak z listach i krotkach).

Iteracja - pętle

while

Skrypty mogą być bardzo liniowe w swoim działaniu - po uruchomieniu wykonać jedno zadanie i zakończyć działanie. Inne programy mogą zawierać pętlę główną w której mogą kręcić się w nieskończoność czekając np. na naciśnięcie klawisza przez użytkownia - nie zakończą swojego zadania dopóki nie zostaną wyłączone w określony sposób.

Główna pętla czy nie w języku Python można zadeklarować pętle choćby tylko z potrzeby powtórzenia tych samych operacji wielokrotnie.

Prawdopodobnie najbardziej podstawową pętlą będzie while, czyli instrukcja która przyjmuje jedno wyrażenie, które zostanie rozwiązane na typ bool - prawdę lub fałsz i tak długo jak wyrażenie jest prawdziwe kod zawarty w bloku while będzie powtarzany. while jest instrukcją blokową więc należy pamiętać o wcięciach.

1
2
3
4
>>> i = 5
>>> while i > 0:
...     print(i)
...     i -= 1

W powyższym przykładzie zadeklarowana została zmienna i, pętla została uruchmiona tak, żeby powtarzać kod w jej bloku dopóki i jest większe od 0. Zatem, tak powinien wyglądać przebieg tej pętli:

  1. i = 5 więc jest większe od 0, wyświetl 5 i odejmij 1 od i (użyty został operator, który jest skrótem na i = i - 1,

  2. i = 4 więc jest większe od 0, wyświetl 4 i odejmij 1 od i,

  3. i = 3 więc jest większe od 0, wyświetl 3 i odejmij 1 od i,

  4. i = 2 więc jest większe od 0, wyświetl 2 i odejmij 1 od i,

  5. i = 1 więc jest większe od 0, wyświetl 1 i odejmij 1 od i,

for

Instrukcja for jest trochę bardziej skomplikowana ale generalnie robi to samo co while. To co je odróżnia to argumenty, które przyjmują: while przyjmuje wyrażenie, sprawdzane pod kątem “prawdziwości”, for z kolei przyjmuje obiekt będący iteratorem.

Iterator to typ, po którego instancji można iterować, tak jak w przykładach poniżej:

String jest iteratorem, bo można iterować po jego indywidualnych znakach:

1
2
>>> for letter in "Python":
...     print(letter)

Lista jest iteratorem, bo można iterować po jej elementach (tak samo jak tupla):

1
2
3
>>> members = ["Ewa", "Ala", "Ela", "Ola"]
>>> for member in members:
...     print(member)

Słownik jest iteratorem, bo można iterować po jego kluczach:

1
2
3
>>> family = {"mama": "Halina", "tata": "Stefan"}
>>> for family_member in family:
...     print(family)

w przypadku słownika jest metoda items, która pozwala iterować po kluczach i wartościach jednocześnie - wtedy do każdego przebiegu pętli trafiają dwie wartości i zapis wygląda tak:

1
2
3
>>> family = {"mama": "Halina", "tata": "Stefan"}
>>> for family_member, name in family.items():
...     print(name + " to: " + family_member)

Powyższe przykłady pokazują jak działa for - przeskakuje po elementach iteratora, dopóki nie wykorzysta wszystkich elementów.

Funkcje

Funkcje są blokami kodu, który ma być wykorzystywany wielokrotnie. Idealnie, jedna funkcja powinna mieć tylko jedno zadanie i robić je dobrze. Deklaracja funkcji odbywa się przez słowo def, nazwą funkcji i agrumentami.

1
2
def add(value_a, value_b):
    return value_a + value_b

W tym momencie w pamięci programu znajduje się funkcja dodaj i można ją dowolnie wykonywać. Zmienne w funkcji dostępne są tylko w tej funkcji.

1
2
3
4
5
6
7
8
9
print(add(2, 2))
value_c = add(2, 2)
print(add(value_c, 5))
print(
    add(
        add(2, 2),
        add(5, 5)
    )
)

Funkcja dodaj wywołana z dwoma argumentami 2 i 2 zwróciła (return) wartość, która natychmiast została wyświetlona. Wynik funkcji może też być przypisany do zmiennej jak wartosc_c, w tym przadku żeby wykorzystać ten wynik w następnej linii. Ostatecznie zagnieżdzonych zostało kilka funkcji, pokazuje to w jakiej kolejności kod jest uruchamiany: Najpierw wykona się dodaj(2, 2) dając 4, potem dodaj(5, 5) dając 10, potem dodawane są wyniki czyli dodaj(4, 10), wszystko to trafia do print() z wartością 14.

Dekoratory

Ciekawą właściwością funkcji jest to, że są one obiektami pod pewnymi względami podobnymi do ciągów znaków czy liczb. Spójrzmy na poniższy przykład:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def greet():
    return "Hello!"


print(greet)
print(greet())

x = greet # zwróć uwagę na brak nawiasów

print(x())

Podanie nazwy funkcji bez “nawiasów” powoduje, że nie wywołujemy funkcji, a jedynie wskazujemy na definicje danej funkcji, co widać w wyniku uruchomienia powyższego przykładu:

1
2
3
<function greet at 0x7fc859f9ae60>    <-- to właśnie obiekt funkcji
Hello!                                <-- wynik print(greet())
Hello!                                <-- wynik x()

Ta funkcjonalność daje sporo możliwości, z których jedną z najważniejszych są dekoratory. Szczegółowa znajomość dekoratorów nie jest wymagana na początkowym etapie nauki, ale warto wiedzieć czym są i jak z nich korzystać. Dekorator to w uproszczeniu funkcja, która jako swój argument przyjmuje inną funkcję, np. by rozszerzyć działanie istniejącej funkcji:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
DEBUG = True

def check_debug(decorated_function):

    def wrapper():
        if DEBUG:
            print("lets print!")
        decorated_function()
    return wrapper

@check_debug
def our_function():
    print("Hi!")

@check_debug
def our_other_function():
    print("Hello!")

our_function()
our_other_function()

W powyższym przykładzie nasz dekorator pozwala na dodanie dodatkowego tekstu, jeśli zmienna DEBUG ma wartość True. Może to być przydatne kiedy szukamy błędów w naszym kodzie. Widzimy też deklaracje @check_debug, która powoduje, że nasza funkcja check_debug opakowuje wskazaną funkcję. Nie ma tutaj żadnej magii - powyższy kod moglibyśmy napisać bez @, chociaż byłby wtedy zdecydowanie mniej czytelny:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
DEBUG = True

def check_debug(decorated_function):

    def wrapper():
        if DEBUG:
            print("lets print!")
        decorated_function()
    return wrapper

def our_function():
    print("Hi!")

def our_other_function():
    print("Hello!")

our_decorated_function = check_debug(our_function)
our_decorated_other_function= check_debug(our_other_function)

our_decorated_function()
our_decorated_other_function()

zwrot @dekorator to tak zwany “syntactic sugar” - cukier składniowy, czyli element języka programowania który sam z siebie nie dodaje do niego nowych funkcjonalności, ale sprawia że kod jest czytelniejszy i łatwiejszy w obróbce. W trakcie nauki Pythona napotkasz wiele podobnych struktur. Dekoratory zawierają w sobie wiele niuansów i powyższy przykład jest bardzo prymitywny - dociekliwym polecam doczytać o dekoratorze @wraps znajdującym się w bibliotece functools, by poprawić zachowanie naszego dekoratra. Dekorować można również klasy, ale wykracza to poza zakres naszego kursu.

Klasy

Klasy to część programowania obiektowego, które samo w sobie jest materiałem na roczny kurs - poniższy tekst to próba przybliżenia podstaw obchodzenia się z klasami. Dziedziczenie jest bardzo istotnym zagadnieniem, którego nie zdołamy tu zbadać.

Klasa deklarowana jest słowem class, jej nazwą i innych klas podanych w nawiasie - klas z których ma się odbyć dziedziczenie.

Co może pomóc w zobrazowaniu sobie czym są klasy to to, że klasa może reprezentować coś ze świata, osobę, przedmiot, zamówienie w sklepie internetowym, itp. Wymienione “rzeczy” posiadają swoje cechy (atrybuty) i mogą oddziaływać na siebie i innych swoistymi akcjami (metodami).

Zobrazowaniem uproszczonej wersji aplikacji typu blog - opisanie autora (Author) i wpisu bloga (Post) mogłoby być:

1
2
3
4
5
6
7
8
class Author:

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __str__(self):
        return self.first_name + " " + self.last_name

Zdefiniowana klasa Author ma dwie metody (metoda do funkcja klasy). Obie metody są tak zwanymi “magicznymi metodami” i charakteryzują się tym, że mają znaki __ (podwójny podkreślnik) przed i po swojej nazwie i używane są do zadań specjanych przez język.

__init__ to kod wykonywany przy inicjalizacji instancji klasy

__str__ wykona się gdy instancę klasy zostanie “rzucona” na typ String (funkcją str())

Klasa a instancja

Klasę można rozumieć jako matrycę do wyciskania, a instancję jako konkretny odcisk, np: Artur jest instancją człowieka, tak samo jak Łukasz - jako ludzie posiadają ten sam zestaw funkcji, jak oddychanie. Jednak mimo tego, że są ludźmi są zupełnie osobnymi bytami.

1
2
3
jan = Author("Jan", "Brzechwa")
pani_od_pottera = Author("J.K.", "Rowling")
piotr = Author("Piotr", "Blaszczyk")

Wszystkie trzy zmienne zawierają instancje klasy Author, wszystkie mają te same atrybuty (imię i nazwisko) ale każda z nich zawiera inne dane.

1
2
3
4
5
6
7
print(jan.first_name)
print(pani_od_pottera.first_name)
print(pani_od_pottera.last_name)
print(str(piotr))

piotr.last_name = "Blaszczyk III Wielki"
print(str(piotr))

Można operować i manipulować danymi instancji nie zmieniając danych zawartych w innych instancjach.

Idąc dalej - zadeklarowana zostaje kolejna klasa - Post:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Author:

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __str__(self):
        return self.first_name + " " + self.last_name


class Post:

    def __init__(self, title, author, content):
        self.title = title
        self.author = author
        self.content = content

    def __str__(self):
        return self.title + " by " + str(self.author)


jan = Author("Jan", "Brzechwa")
pani_od_pottera = Author("J.K.", "Rowling")
piotr = Author("Piotr", "Blaszczyk")

pytalski = Post("Pan Pytalski", jan, "...")
harry = Post("Harry Potter", pani_od_pottera, "...")
pypila = Post("PyPila", piotr, "...")

Koniec? Co dalej?

  1. Poćwicz to co dziś przerobiliśmy.

  2. Napisz program obsługujący światła drogowe, wykorzystaj klasy do utrzymywania stanu światła z trzema lampami dla pojazdów i dwiema lampami dla pieszych.