PODSTAWY PYTHONA¶

Pierwsza linia kodu¶

Zacznijmy od prostej instrukcji print(). Za jej pomocą możemy kazać komputerowi wyświetlić coś na ekranie. To „coś” umieszczamy jako argument tej instrukcji między nawiasami.

In [1]:
print('coś')
coś

Zauważ, że napis „coś” w powyższym kodzie jest otoczony z obu stron amerykańskim cudzysłowem pojedynczym '. To informacja dla komputera, że „coś” jest niczym więcej niż zwykłym napisem (ang. string). Właśnie poznaliśmy pierwszy typ danych „napis”.

Zapamiętaj!¶

print() - wyświetla informację na ekranie.

Napisy, zmienne i komentarze¶

Instrukcje Pythona takie jak print() będziemy nazywać funkcjami. Więcej o funkcjach powiemy nieco później.

Teraz zastanówmy się, co by się stało, gdybyśmy przekazali komputerowi następującą instrukcję:

In [2]:
print(coś)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 print(coś)

NameError: name 'coś' is not defined

Python odpowie nam komunikatem typu NameError, o treści „zmienna (ang. variable) o nazwie coś nie została zdefiniowana”.

Co to jest zmienna? Na to pytanie jest tylko jedna odpowiedź: zmienna to nie jest stała. To paradoksalnie tautologiczne ujęcie rzeczy mówi wszystko o tym czym jest zmienna. Stałą jest na przykład poznany wcześniej napis 'coś'. Jest on taki jaki jest i nie może być inny. Zmienna natomiast zmienną jest, czyli może przyjmować różną postać, tj. różne wartości. Weźmy na przykład zmienną current_year i przypiszmy jej wartość 2021, co zapisujemy w Pythonie:

In [3]:
current_year = 2021
print(current_year)
2021

Rok później przypiszemy tej zmiennej inną wartość:

In [4]:
current_year = 2022
print(current_year)
2022

I tak co roku wartość ta będzie się zwiększać o 1. Instrukcja print(current_year) wyświetli co roku inną wartość, najpierw 2021, potem 2022, itd.

Zauważmy, w jaki sposób przypisujemy wartości do zmiennej. Instrukcja przypisania (ang. assignment statement), składa się z nazwy zmiennej (current_year), znaku równości (=), zwanego operatorem przypisania (ang. assignment operator) oraz wartości, która ma zostać przypisana (2021). Tak na marginesie, właśnie poznaliśmy drugi typ danych: liczby całkowite (ang. integer). Zmienna current_year jest właśnie tego typu.

Programując będziemy używać języka angielskiego do nazw zmiennych (a później również funkcji, metod i klas). Język polski może pojawić się w komunikatach zwracanych do użytkownika.

Wiemy już czym są napisy i jak je wyświetlić. Na przykład instrukcja poniżej wyświetli napis „Witaj świecie!”:

In [5]:
print('Witaj świecie!')
Witaj świecie!

Napisy można ze sobą łączyć za pomocą +. Poniższa instrukcja wyświetli napis „Ala ma kota.” (zwróć uwagę na spacje przed i po „ma”):

In [6]:
print('Ala' + ' ma ' + 'kota.')
Ala ma kota.

Podobny efekt uzyskamy pisząc:

In [7]:
print('Ala', 'ma', 'kota.')
Ala ma kota.

Sprawdź samodzielnie jaki będzie wynik wykonania poniższej instrukcji:

In [8]:
print('Ala', 'ma', 'kota', sep='-', end='.')
Ala-ma-kota.

Poniższe dwie linie kodu pokazują, że znak + zachowuje się inaczej gdy jest zastosowany do napisów, a inaczej, gdy stosujemy go do liczb. Zauważ różnice.

In [9]:
print('10' + '5')
105
In [10]:
print(10 + 5)
15

Pokażemy teraz jak można wstawić coś wewnątrz napisu. Przypomnijmy, że wcześniej zdefiniowaliśmy zmienną:

In [11]:
current_year = 2022

Dodamy teraz jeszcze jedną:

In [12]:
my_age = current_year - 1979
print(my_age)
43

Użyjmy ich teraz do w następujących pięciu wywołaniach funkcji print():

In [13]:
print('Mam', my_age, 'lat(a).')
print('Mam %d lat(a).' % my_age)
print('Mam %s lat(a).' % my_age)
print('Mam {} lat(a).'.format(my_age))
print(f'Mam {my_age} lat(a).')
Mam 43 lat(a).
Mam 43 lat(a).
Mam 43 lat(a).
Mam 43 lat(a).
Mam 43 lat(a).

Przeanalizuj również poniższy kod.

In [43]:
hello = 'Cześć %s!'
print(hello % 'Maciek')
Cześć Maciek!

Komentarze¶

Kod Pythona można opatrywać komentarzami. Jest to bardzo porządane, gdyż ułatwia zrozumienie kodu, szczególnie, gdy do niego wracamy po dłuższym czasie niewidzenia. Python umożliwa dwa typy komentarzy:

In [258]:
# komentarz w jednej linii
print('Nie zobaczysz komentarzy!') # komentarz w jednej linii
Nie zobaczysz komentarzy!
In [259]:
"""
    komentarz
    w kilku
    liniach
"""
print('Nie zobaczysz komentarzy!')
Nie zobaczysz komentarzy!

Formatowanie napisów¶

Wewnątrz napisu można umieszczać znaki specjalne (ang. escape characters). Znaki specjalne zawsze zaczynają się od backslasha „\”. Oto przykłady działania znaku tabulacji „\t” i nowej linii „\n”:

In [14]:
print('\tPink Floyd') # tabulacja
	Pink Floyd
In [15]:
print('Pink\nFloyd') # nowa linia
Pink
Floyd

Do znaków specjalnych wrócimy jeszcze później, gdy będziemy mówić o wyrażeniach regularnych.

Teraz poznamy drugą funkcję len(). Jej argumentem, czyli tym co umieścimy między nawiasami „(” i „)”, może być napis, a tym co ona zwraca jest jego długość. Na przykład:

In [16]:
len('Ummagumma') # liczba znaków
Out[16]:
9
In [17]:
len('Ummagumma      ')
Out[17]:
15
In [18]:
len('       Ummagumma ')
Out[18]:
17

Widać więc, że spacje są również liczone gdy sprawdzamy długość napisów.

Python posiada instrukcje do usuwanie spacji z początku lub końca napisu.

In [19]:
print(' Ummagumma      '.rstrip()) # usuwanie wszytkich spacji z prawej strony napisu
print('      Ummagumma '.lstrip()) # usuwanie wszytkich spacji z lewej strony napisu
print('    Ummagumma   '.strip())  # usuwanie wszytkich spacji z obu stron napisu
 Ummagumma
Ummagumma 
Ummagumma
In [20]:
print(len(' Ummagumma      '.rstrip())) 
print(len('      Ummagumma '.lstrip())) 
print(len('    Ummagumma   '.strip()))  
10
10
9

Takie instrukcje, które „doczepiamy” za pomocą kropki . (na przykład do napisów, po to, aby je przekształcić) nazywamy metodami. To, do czego „doczepiamy” metodę nazywa się obiektem. Każdy napis jest obiektem (choć nie każdy obiekt jest napisem, co znaczy, że poznamy jeszcze inne obiekty, które nie będą napisami).

Mówimy, że metoda jest wywoływana na obiekcie (gdy ją do niego „doczepiamy”). W poniższym przykładzie napis ' Ummagumma ' jest obiektem, na którym wywołujemy metodę strip().

In [21]:
' Ummagumma '.strip()
Out[21]:
'Ummagumma'

Oto inne pożyteczne metody, jakie można wywołać na napisach:

In [22]:
print('robert trypuz'.title())
print('RoBert TrypUz'.title())
print('robert trypuz'.upper())
print('RoBert TrypUz'.lower())
print('ala ma kota.'.capitalize())
Robert Trypuz
Robert Trypuz
ROBERT TRYPUZ
robert trypuz
Ala ma kota.

Jeśli chcesz znaleźć fragment napisu w napisie użyj metod find() lub index(). Każdy znak w napisie ma swój numer porządkowy. Zaczynami numerować znaki od 0. Czyli w napisie „kot ”, znak „k” będzie miał indeks 0, „o” indeks 1, „t” indeks 2 i „ ” indeks 3. Metody find() i index() zwrócą indeks pierwszego wystąpienia szukanego fragmentu. Przeanalizuj samodzielnie poniższy przykład.

In [23]:
yoda_sentence = 'Dwóch zawsze ich jest. Nie mniej, nie więcej. Mistrz i uczeń.'
print(yoda_sentence.find('uczeń'))
print(yoda_sentence.index('uczeń'))
print(yoda_sentence.find('Yoda'))
55
55
-1

W przypadku, gdy szukanego fragment nie będzie w napisie, metoda find() zwróci -1, zaś metoda index() zwróci wyjątek („substring not found”).

In [24]:
print(yoda_sentence.index('Yoda'))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [24], in <cell line: 1>()
----> 1 print(yoda_sentence.index('Yoda'))

ValueError: substring not found

Wróćmy jeszcze do tego, że każdy znak w napisie ma swój numer. Za pomocą indeksów możemy „wykroić” z napisu jeden znak albo więcej znaków. Przeanalizuj wyniki poniższego kodu:

In [25]:
# slicing
print('Robert Trypuz'[3])
print('Robert Trypuz'[3:10])
print('Robert Trypuz'[3:10:2])
e
ert Try
etTy
In [26]:
t = 'Thor, syn Odyna, odpowiednik południowogermańskiego Donara, jeden z głównych bogów nordyckich'
m = t[19]+t[12]+t[0:3]+t[8]+t[5]+t[60]+t[23]+t[6]+t[0]+t[5]+t[6]+t[32]+t[19]+t[23]+t[3]
print(m.capitalize(), end='!')
Python jest super!

Metoda count() zwraca liczbę wystąpień fragmentu napisu (znaku, wyrazu lub frazy) w napisie:

In [27]:
print(yoda_sentence.count('uczeń'))
print(yoda_sentence.count('nie'))
1
2

replace() domyślnie zastępuje wszystkie wystąpienia pierwszego argumentu przez drugi.

In [28]:
print(yoda_sentence.replace('uczeń','padawan'))
Dwóch zawsze ich jest. Nie mniej, nie więcej. Mistrz i padawan.

Napiszmy teraz krótki programik, który wykorzysta zdobytą przez nas do tej pory wiedzę. W programie wykorzystamy nową funkcję input() - to już trzecia funkcja jaką poznajesz! Odczytuje ona to co napisze użytkownik i przekształca to na napis.

Po wykonaniu poniższych czterech linii kodu, komputer poprosi cię o podanie twojego imienia, a następnie przywita się z tobą i poda z ilu liter składa się twoje imię:

In [29]:
print('Podaj swoje imię: ')
name = input()
print(f'Witaj {name.title()}!')
print(f'Twoje imię ma {len(name)} znaków.')
Podaj swoje imię: 
Robert
Witaj Robert!
Twoje imię ma 6 znaków.

Wróćmy jeszcze na chwilę do zmiennych i podsumujmy co o nich wiemy. Używanie zmiennych ma wiele zalet. Zmienna umożliwia odnoszenie się do wartości za pomocą nazw oraz do raz wprowadzonej zmiennej można odnosić się kilkakrotnie:

In [30]:
current_year = 2022
my_age = current_year - 1979
print('Mam', my_age, 'lat(a).')
Mam 43 lat(a).

Używanie zmiennej pozwala na łatwiejsze zrozumienie znaczenia kodu programu (co ma ogromne znaczenie w przypadku dużych projektów nad którymi pracuje kilku programistów). Pamiętaj również, że nazwy zmiennych muszą spełniać określone zasady:

  • Nie mogą zawierać spacji (ani innych „niewidocznych” znaków
  • Mogą zawierać tylko litery, cyfry i znaki podkreślenia _ (- nie jest dozwolony!).
  • Nie mogą zaczynać się od cyfry.
  • Zaleca się używanie liter z alfabetu angielskiego.

Zapamiętaj!¶

capitalize() - metoda zwraca kopię oryginalnego napisu i konwertuje pierwszy jego znak na wielką literę, a pozostałe litery zamienia na małe.

count() - metoda zwraca liczbę wystąpień fragmentu napisu w napisie.

find() - metoda zwraca najniższy indeks fragmentu napisu w danym napisie, o ile zostanie znaleziony. Jeśli nie zostanie znaleziony, zwraca -1.

index() - metoda zwraca najniższy indeks fragmentu napisu w danym napisie, o ile zostanie znaleziony. Jeśli nie zostanie znaleziony, zwraca błąd.

input() - funkcja pobiera dane wejściowe od użytkownika i zwraca je.

len() - funkcja zwraca liczbę znaków w napisie.

lower() - metoda zamienia wszystkie wielkie litery w napisie na małe.

lstrip() - metoda zwraca kopię napisu z usuniętymi spacjami z początku napisu.

replace() - metoda zwraca kopię napisu, w którym wszystkie wystąpienia fragmentu tego napisu są zastępowane innym napisem.

rstrip() - metoda zwraca kopię napisu z usuniętymi spacjami z końca napisu.

strip() - metoda zwraca kopię napisu z usuniętymi spacjami z początku i końca napisu.

title() - metoda zamienia pierwszy znak w napisie na wielką literę, a pozostałe znaki zamienia na małe litery.

upper() - metoda zamienia wszystkie małe litery w napisie na wielkie.

Liczby¶

Oprócz napisów mamy również w Pythonie liczby. Możemy w Pythonie wykonywać arytmetyczne operacje na liczbach. Uruchamiając poniższe przykłady bez trudu zrozumiesz sens tych operacji:

In [31]:
print("6 + 3 =", 6 + 3)
print("6 - 3 =", 6 - 3)
print("6 * 3 =", 6 * 3)
print("6 / 3 =", 6 / 3)
print("6 % 3 =", 6 % 3)
print("6 ** 3 =", 6 ** 3)
print("7 // 3 =", 7 // 3)
6 + 3 = 9
6 - 3 = 3
6 * 3 = 18
6 / 3 = 2.0
6 % 3 = 0
6 ** 3 = 216
7 // 3 = 2

Wyżej mówiliśmy o przypisaniu wartości do stworzonej zmiennej.

In [32]:
x = 3
print(x)
3

Kod w komórce powyżej przypisuje wartość 3 zmiennej x. Gdybyśmy teraz chcieli zwiększyć wartość x o jeden (niezależnie od tego ile ona teraz wynosi) moglibyśmy zrobić tak:

In [33]:
x = x + 1
print(x)
4

W Pythonie możemy łączyć operacja przypisania wartości do zmiennej z wykonywaniem operacji arytmetycznych. Na przykład poniża instrukcja zrobi dokładnie to samo co x = x + 1:

In [34]:
x += 1
print(x)
5

Łączymy + z = i mamy +=, czyli jednoczesne przypisanie z sumą. Podobnie możemy zrobić z innymi operacjami arytmetycznymi. Na przykład:

In [35]:
x = 3
x *= 2
print(x)
6

Przepływ sterowania¶

Powiedzieliśmy na początku, że programowanie, to wydawanie instrukcji komputerowi: zrób to, a potem tamto, itd. Teraz powiemy, że instrukcje mogą być nieco bardziej złożone i niekiedy warunkowe, tj. ich wykonanie będzie zależeć od spełnienia (tj. prawdziwości) zadanego przez nas warunku.

Prawda i fałsz¶

Możemy zapytać komputer używając Pythona, czy dane wyrażenie jest prawdziwe (True) czy fałszywe (False).

In [44]:
2+2 == 4
Out[44]:
True
In [45]:
2+2 != 4
Out[45]:
False
In [46]:
16 > 18
Out[46]:
False
In [47]:
19 >= 18
Out[47]:
True

Użyte w czterech powyższych komórkach operatory są dość oczywiste, ale powiedzmy dla jasności, że ==,!=, >, >= znaczą odpowiednio „jest identyczne”, „jest różne”, „jest większe” oraz „jest większe bądź równe”.

W Pythonie mamy wiele metod, które będą zwracać wartości True lub False. Na przykład: isupper(), islower(), isalpha(), isdigit(), czy endswith(). Jeśli uruchomisz każde z poniższych linii kodu w osobnej komórce notatnika w tym porządku w jakim są poniżej, to otrzymasz jako wynik na zmianę True i False.

In [48]:
'ROBERT TRYPUZ'.isupper()
Out[48]:
True
In [49]:
'KUL'.islower()
Out[49]:
False
In [50]:
'Robert'.isalpha()
Out[50]:
True
In [51]:
'Robert Trypuz'.isalpha()
Out[51]:
False
In [52]:
'123'.isdigit()
Out[52]:
True
In [53]:
'10 000'.isdigit()
Out[53]:
False
In [54]:
'Robert Trypuz'.endswith('Trypuz')
Out[54]:
True

Zauważ, że przedostatnie wyrażenie zwróci wartość False. Dzieje się tak dlatego, że w napisie 10 000 jest spacja. Spacja jest też powodem, dla którego instrukcja 'Robert Trypuz'.isalpha() zwróci wartość False.

Zapamiętaj!¶

endswith() - metoda zwraca True, jeśli napis kończy się określonym sufiksem. Jeśli nie, zwraca False.

isalpha() - metoda zwraca True, jeśli wszystkie znaki w napisie są literami z alfabetu („alpha” to skrót od „alphabet”). Jeśli nie, zwraca False.

isdigit() - metoda zwraca True, jeśli wszystkie znaki w napisie są cyframi. Jeśli nie, zwraca False.

islower() - metoda zwraca True, jeśli wszystkie litery w napisie są małymi literami. Jeśli napis zawiera co najmniej jedną wielką literę, zwraca False.

isupper() - metoda zwraca True, jeśli wszystkie litery w napisie są dużymi literami. Jeśli napis zawiera co najmniej jedną małą literę, zwraca False.

Zbadaj samodzielnie i zapamiętaj!¶

isnumeric() - metoda zwraca True, jeśli wszystkie znaki w napisie są znakami liczbowymi. Jeśli nie, zwraca False.

isspace() - metoda zwraca True, jeśli w napisie znajdują się tylko białe znaki (tj. Nie ma znaków specjalnych jak \n czy \t). Jeśli nie, zwraca False.

startswith() - metoda zwraca True, jeśli napis kończy się określonym prefiksem. Jeśli nie, zwraca False.

Instrukcja warunkowa ,,if''¶

Instrukcja warunkowa to instrukcja, którą komputer wykona pod pewnym warunkiem.

if WARUNEK JEST PRAWDZIWY:
   BLOK INSTRUKCJI DO WYKONANIA

Poniższy kod każe komputerowi wyświetlić napis

Jesteś pełnoletni(a) i możesz głosować! 
Brawo! 
Masz 19 lat(a).

pod warunkiem, że wartość przypisana zmiennej my_age będzie >= 18.

In [56]:
my_age = 19
if my_age >= 18:
    print('Jesteś pełnoletni(a) i możesz głosować!')
    print('Brawo!')
print(f'Masz {my_age} lat(a).')
Jesteś pełnoletni(a) i możesz głosować!
Brawo!
Masz 19 lat(a).

Teraz zmieńmy wartość zmiennej my_age i zobaczmy różnicę w działaniu kodu:

In [57]:
my_age = 16
if my_age >= 18:
    print('Jesteś pełnoletni(a) i możesz głosować!')
    print('Brawo!')
print(f'Masz {my_age} lat(a).')
Masz 16 lat(a).

Zauważmy, że instrukcje

print('Jesteś pełnoletni(a) i możesz głosować!')
print('Brawo!')

w powyższym kodzie zaczynają się „wcięciem” (tabulacją), podczas gdy reszta kodu nie. To nie jest tylko zabieg estetyczny. W ten sposób tworzymy w Pythonie bloki kodu. W tym przypadku chcemy zaznaczyć, że obie instrukcje print() zaczynające się tabulacją należą do tego samego bloku kodu, który wykona się o ile spełniony będzie warunek.

Poniższy kod wykorzystuje wcześniej poznaną metodę count() oraz instrukcję warunkową do wykrywania wulgaryzmów w tekście.

In [59]:
entry = 'Nie zgadzam się z tym stanem rzeczy, kurza twarz!'
vulgarism = 'kurza twarz'
if entry.count(vulgarism) > 0:
  print('Komentarz zawiera wyrażenie uznane za obraźliwe.')
Komentarz zawiera wyrażenie uznane za obraźliwe.

Instrukcja warunkowa może być nieco bardzie złożona i przybrać postać instrukcji if-else:

if WARUNEK JEST PRAWDZIWY:
   BLOK INSTRUKCJI DO WYKONANIA
else:
   BLOK INSTRUKCJI DO WYKONANIA

Blok instrukcji następujący po else wykona się o ile warunek następujący po if nie będzie spełniony (prawdziwy). Uruchom poniższy kod kilkakrotnie i zrozum jego działanie. Program sprawdza, czy znak wprowadzony przez użytkownika jest samogłoską.

In [61]:
my_age = 14
if my_age >= 18:
    print('Jesteś pełnoletni(a) i możesz głosować!')
else:
    print(f'Masz {my_age} lat. Nie możesz głosować!')
Masz 14 lat. Nie możesz głosować!

in w poniższym kodzie to operator przynależności służący do sprawdzenia, czy dany element występuje w sekwencji. W tym kodzie chodzi o sprawdzenie, czy znak jest samogłoską.

Możemy użyć not in, aby się upewnić, że danego elementu w sekwencji nie ma.

In [64]:
print('Podaj znak: ')
sign = input()
vowels = 'aiueoóyąę'
if sign.lower() in vowels:
  print(f'Wprowadzony znak \"{sign}\" jest samogłoską.')
else:
  print(f'Wprowadzony znak \"{sign}\" nie jest samogłoską.')
Podaj znak: 
-
Wprowadzony znak "-" nie jest samogłoską.

Instrukcja warunkowa może być jeszcze bardziej złożona jeśli pomiędzy if i else dodamy jeszcze elif. Otrzymamy wówczas następujący schemat if-elif-else:

if WARUNEK JEST PRAWDZIWY:
   BLOK INSTRUKCJI DO WYKONANIA
elif WARUNEK JEST PRAWDZIWY:
   BLOK INSTRUKCJI DO WYKONANIA
…
elif WARUNEK JEST PRAWDZIWY:
   BLOK INSTRUKCJI DO WYKONANIA
else:
   BLOK INSTRUKCJI DO WYKONANIA

Instrukcja if-elif-else pozwala „zniuansować” warunki. W przypadku if-else wybór był binarny (albo tak albo inaczej). Fragment elif pozwala wprowadzić dodatkowe warunki. Uruchom poniższy program i zrozum jego działanie. To program wyborczy informujący o (nie)możliwości głosowania. Zmień wartość zmiennej my_age, aby uruchomić różne warunki.

In [66]:
my_age = 15
if my_age >= 18:
    print('Jesteś pełnoletni(a) i możesz głosować!')
elif my_age >= 17:
    print(f'Już za rok będziesz głosować!')
elif my_age >= 16:
    print(f'Już za dwa lata będziesz głosować!')
else:
   print(f'Masz {my_age} lat... jeszcze poczekasz!')
Masz 15 lat... jeszcze poczekasz!

Program sprawdzający, czy student zaliczył czy też nie ćwiczenia ze wskazaniem powodu ew. niezaliczenia.

In [68]:
missed_classes = 3
passed_tests = 3 # out of 4
class_participation = True

if missed_classes > 2:
   print('Brak zaliczenia z powodu nieobecności!')
elif passed_tests <= 2:         
   print('Brak zaliczenia; nie zaliczyłeś/aś min 3 testów!')
elif not class_participation: 
   print("Brak zaliczenia z powodu braku aktywności!")   
else:
   print('Zaliczone!', 'Gratulacje!')
Brak zaliczenia z powodu nieobecności!

Gdybyśmy chcieli jedynie poinformować studenta, czy zaliczył ćwiczenia czy nie, moglibyśmy napisać taki oto kod:

In [69]:
missed_classes = 3
passed_tests = 3 # out of 4
class_participation = True

if (missed_classes <= 2) and (passed_tests >= 3) and class_participation:
  print('Zaliczone!', 'Gratulacje!') 
else:
  print('Brak zaliczenia!')
Brak zaliczenia!

W kodzie dotyczącym zaliczenia ćwiczeń użyliśmy trzech funktorów logicznych: negacji (not), koniunkcji (and) i alternatywy (or). Możemy je scharakteryzować za pomocą prawdy (True) i fałszu (False) jak poniżej.

warunek p warunek q and or
True True True True
True False False True
False True False True
False False False False

Pierwszy wiersz tabeli mówi, że jeśli warunki p i q są oba prawdziwe, to ich koniunkcja i alternatywa też jest prawdziwa. Drugi wiersz stwierdza, że jeśli warunek p jest prawdziwy, a q fałszywy, to ich koniunkcja jest fałszywa, zaś alternatywa jest prawdziwa. Itd.

warunek p not p
True False
False True

Negacja warunku prawdziwego jest warunkiem fałszywym, zaś fałszywego prawdziwym.

Poniżej znajduje się program wystawiający ocenę w zależności od liczby zdobytych punktów.

In [71]:
print('Podaj liczbę zdobytych puntków: ')
points = int(input())
if points > 20:
    print("Piątka! Brawo!")
elif points > 18:
    print("Dobry +")
elif points > 16:
    print("Dobry")
elif points > 14:
    print("Dostateczny +")
elif points > 12:
    print("Dostateczny")
else:
    print("Dwója")
Podaj liczbę zdobytych puntków: 
13
Dostateczny

Pętla while¶

Pętla jest instrukcją, która nakazuje komputerowi wykonać inne instrukcje kilkakrotnie. Instrukcje wykonują się w zapętleniu, czyli w kółko. Oczywiście komputer musi dokładnie wiedzieć

  • albo ile razy ma się w takiej pętli kręcić
  • albo pod jakim warunkiem może pętlę zakończyć.

Pętla while należy do tego drugiego typu. Oto przykład takiej pętli:

In [72]:
i = 1
while i <= 4:
    print("Obrót pętli nr", i)
    i = i + 1
Obrót pętli nr 1
Obrót pętli nr 2
Obrót pętli nr 3
Obrót pętli nr 4

Przyjrzyjmy się powyższemu przykładowi. Część while i <= 4: zawiera słowo kluczowe while po którym następuje warunek, który zamyka :. Następnie mamy blok instrukcji do wykonania (oczywiście zaznaczony odpowiednim wcięciem). Schematycznie pętla while mogłaby wyglądać tak:

while WARUNEK JEST PRAWDZIWY:
    BLOK INSTRUKCJI DO WYKONANIA

Czyż nie jest ona co do struktury podobna do instrukcji warunkowej:

if WARUNEK JEST PRAWDZIWY:
   BLOK INSTRUKCJI DO WYKONANIA

Podobieństwo strukturalne pętli while do instrukcji warunkowej niech nas nie zwiedzie. Działanie pętli while jest bowiem zupełnie inne od działania instrukcji warunkowej. Jeżeli warunek pętli jest prawdziwy, następuje wykonanie bloku kodu wewnątrz pętli. Po wykonaniu bloku kodu, komputer powraca do ponownego sprawdzenia warunku. Jeżeli warunek pętli jest prawdziwy, następuje ponowne wykonanie bloku kodu, ale jeżeli warunek jest fałszywy, to blok kodu pętli while zostaje pominięty, co tym samym kończy działanie pętli.

Jeśli teraz chwilę zastanowimy się nad działaniem pętli while, to po chwili dojdziemy to ważnego wniosku: warunek pętli musi się jakoś zmieniać! W przeciwnym razie, jeśli tylko będzie prawdziwy, komputer po „wejściu do pętli” nigdy z niej nie wyjdzie.

Już wiemy co się może zmieniać… zmienna, a dokładnie rzecz ujmując, jej wartość. Popatrzymy jeszcze raz na nieco wcześniej zaprezentowaną pętlę while. Warunek tej pętli zawiera zmienną i - będziemy nazywać ją zmienną regulującą. Natomiast jeśli popatrzymy na blok instrukcji wewnątrz tej pętli, to na jego końcu znajdziemy instrukcję i = i + 1. Zadaniem tej instrukcji jest zmiana wartości i - w tym przypadku jest to inkrementacja o 1, przy każdym obrocie pętli. Aby pętla while działała poprawnie i skutecznie, blok instrukcji wewnątrz pętli musi zawierać inkrementację (lub dekrementację) zmiennej regulujące.

Poniżej znajduje się kod prostej gry, który pomoże zrozumieć Ci sposób działania pętli while (a przy okazji przypomni jak działają wcześniej omawiana przez nas elementy języka Python). Program losowo wybiera liczbę całkowita z przedziału od 1 do 100. Zadaniem użytkownika jest odgadnąć tę liczbę. Program podpowiada, czy poszukiwana liczba jest mniejsza od podanej, czy większa.

In [73]:
import random
number = random.randint(0,100)
print('Komputer właśnie wylosował liczbę z przedziału od 0 do 100.')
print('Zgadnij jaka to liczba!')

guess = int(input('Podaj liczbę od 0 do 100: '))
counter = 1
while guess != number:
    
  if guess > 100:
    print('Podana liczba jest większa niż 100!')
  elif guess < 0:
    print('Podana liczba jest mniejsza od 0!')
  elif guess < number:
    print(f'Podana liczba {guess} jest mniejsza od szukanej.')
  else:
    print(f'Podana liczna {guess} jest większa od szukanej.')
  
  guess = int(input('Podaj liczbę od 0 do 100: '))
  counter += 1      # counter = counter + 1
print(f'Gratulacje! Podana liczba to {number}!')
print(f'Potrzebowałaś/eś {counter} prób!')
Komputer właśnie wylosował liczbę z przedziału od 0 do 100.
Zgadnij jaka to liczba!
Podaj liczbę od 0 do 100: 34
Podana liczba 34 jest mniejsza od szukanej.
Podaj liczbę od 0 do 100: 45
Podana liczba 45 jest mniejsza od szukanej.
Podaj liczbę od 0 do 100: 56
Podana liczna 56 jest większa od szukanej.
Podaj liczbę od 0 do 100: 55
Podana liczna 55 jest większa od szukanej.
Podaj liczbę od 0 do 100: 54
Podana liczna 54 jest większa od szukanej.
Podaj liczbę od 0 do 100: 53
Gratulacje! Podana liczba to 53!
Potrzebowałaś/eś 6 prób!

Metoda random.randint(a, b) zwraca losową liczbę całkowitą z przedziału od a do b, włączając skrajne liczby.

Poniższy kod rysuje uszy kota o dowolnym rozmiarze size:

In [75]:
size = 10
i=1
while i <= size:
  print((i*'@')+(((2*size)-(2*i))*' ')+(i*'@'))
  i+=1
@                  @
@@                @@
@@@              @@@
@@@@            @@@@
@@@@@          @@@@@
@@@@@@        @@@@@@
@@@@@@@      @@@@@@@
@@@@@@@@    @@@@@@@@
@@@@@@@@@  @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@

Zapamiętaj!¶

int() - funkcja konwertuje określoną wartość, na przykład napis '7', na liczbę całkowitą.

import random - komenda import pozwala na dostęp do dodatkowych instrukcji zapisanych w jakimś module; moduł random zawiera metody pozwalające na tworzenie liczb losowych.

random.randint() - to przykład użycia metody randint() z wcześniej zaimportowanego modułu random; random.randint() zwraca losową liczbę całkowitą z zadanego przedziału włączając skrajne liczby.

break i continue¶

Instrukcje break i continue wprowadzają zmianę w działaniu pętli.

Po napotkaniu instrukcji break wewnątrz pętli program opuszcza pętlę. Przeanalizuj poniższy przykład.

In [76]:
i = 1
while i <= 10:
   if i > 5:
       break
   print(i)
   i += 1
1
2
3
4
5

Instrukcji continue używamy w pętli do pominięcia wykonania części kodu wewnątrz pętli, który następuje po tej komendzie. Pętla nadal jest wykonywana zaczynając od początku. Przeanalizuj poniższy kod.

In [77]:
# continue
i = 1
while i <= 10:
   if i >= 4 and i <= 7:
       i += 1
       continue
   print(i)
   i += 1
1
2
3
8
9
10

Instrukcja break zajmuje szczególne zastosowanie w pętli while z warunkiem True. Warunek True jest zawsze spełniony, a zatem pętla while z takim warunkiem będzie wykonywać się bez końca. Jedynym sposobem, aby z takiej pętli wyjść jest użycie instrukcji break.

Poniższy kod „maszyny motywacyjnej” pozwoli zrozumieć ci rolę instrukcji break w pętli while z warunkiem True. Zwróć uwagę, że pętla nie przestanie się wykonywać do momentu, gdy wprowadzisz 0.

In [81]:
menu = '''
1 - motywacja
2 - więcej motywacji
3 - jeszcze więcej motywacji
0 - koniec
'''

while True:
  print(menu)
  letter = input('Wybierz: ')
  if letter == '1':
      print('\n\tJesteś zwycięzcą!')
  elif letter == '2':
      print('\n\tJesteś zwycięzcą! Jesteś zwycięzcą!')
  elif letter == '3':
        print('\n\tJesteś zwycięzcą! Jesteś zwycięzcą! Jesteś zwycięzcą!')
  elif letter == '0':
      print('\n\tDobiegłeś do mety i jesteś diamentem!')
      break
  else:
      print('\n\tMusisz wybrać między 0, 1, 2 i 3!')
1 - motywacja
2 - więcej motywacji
3 - jeszcze więcej motywacji
0 - koniec

Wybierz: 1

	Jesteś zwycięzcą!

1 - motywacja
2 - więcej motywacji
3 - jeszcze więcej motywacji
0 - koniec

Wybierz: 2

	Jesteś zwycięzcą! Jesteś zwycięzcą!

1 - motywacja
2 - więcej motywacji
3 - jeszcze więcej motywacji
0 - koniec

Wybierz: 3

	Jesteś zwycięzcą! Jesteś zwycięzcą! Jesteś zwycięzcą!

1 - motywacja
2 - więcej motywacji
3 - jeszcze więcej motywacji
0 - koniec

Wybierz: 0

	Dobiegłeś do mety i jesteś diamentem!

Pętla ,,for''¶

Dopóki warunek jest prawdziwy, dopóty pętla while jest wykonywana - tego dowiedzieliśmy się w poprzedniej sekcji. Pętla for, którą teraz wprowadzimy, jest wykonywana określoną liczbę razy. Załóżmy, że chcemy wykonać jakiś blok kodu dokładnie 5 razy. Oto przykład jak możemy to zrobić:

In [82]:
for i in range(5):
  print(f'{i}. Hello, world!')
0. Hello, world!
1. Hello, world!
2. Hello, world!
3. Hello, world!
4. Hello, world!

Funkcja range(5) w powyższym kodzie odpowiada za wygenerowanie zakresu liczb od 0 do 4. Zmienna i przyjmie więc kolejno wartości 0, 1, 2, 3 i 4. Czyli w przypadku podania tylko jednego argumentu: argument ten stanowi górne ograniczenie zakresu liczb (nie jest jednak wliczany do wygenerowanego zbioru liczb), a dolną granicę stanowi 0. Zobacz to jeszcze raz:

In [83]:
for i in range(3):
  print(i)
0
1
2

A oto sposób działania tej funkcji w przypadku podania dwóch liczb jako argumentów funkcji range:

In [84]:
for i in range(5, 10):
  print(i)
5
6
7
8
9

Pierwsza z nich stanowi dolny zakres (włączając tę liczbę do zbioru), a drugi argument stanowi górną granicę (nie wliczając jej do tego zbioru).

Oto kolejny przykład użycia funkcji range(); tym razem poza pętlą for:

In [85]:
midlife_crisis = range(35,46)
current_year = 2022
my_age = current_year - 1979
if my_age in midlife_crisis:
  print('Uwaga, może Cię dotknąć kryzys wieku średniego!')
Uwaga, może Cię dotknąć kryzys wieku średniego!

W przypadku podania trzech liczb: dwa pierwsze argumenty zachowują się tak, jak powyżej, natomiast trzeci jest wartością kroku, tzn. określa o ile zmienia się wartość zmiennej w pętli. Przeanalizuj przykłady dwóch pętli poniżej:

In [87]:
for i in range(0,60,10):
  print(i)
0
10
20
30
40
50
In [88]:
for i in range(10,0,-2):
  print(i)
10
8
6
4
2

Jak narysować górę dolarów?

In [90]:
number = 30
for i in range(number):
  if i == (number // 2):
    break
  print((((number // 2) - (i + 1)) * ' ') + ((2 * i) + 1) * '$')
              $
             $$$
            $$$$$
           $$$$$$$
          $$$$$$$$$
         $$$$$$$$$$$
        $$$$$$$$$$$$$
       $$$$$$$$$$$$$$$
      $$$$$$$$$$$$$$$$$
     $$$$$$$$$$$$$$$$$$$
    $$$$$$$$$$$$$$$$$$$$$
   $$$$$$$$$$$$$$$$$$$$$$$
  $$$$$$$$$$$$$$$$$$$$$$$$$
 $$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

Zapamiętaj!¶

range() - funkcja zwraca sekwencję liczb (domyślnie zaczynając od 0) i zwiększa się (domyślnie) o 1 i zatrzymuje się przed określoną liczbą.

Struktury danych¶

Programując będziemy bardzo często przetwarzać dane. Nie jest łatwo zdefiniować czym dane są i nie podejmiemy się tego zadania w tej książce. Wystarczy nam intuicja mówiąca, że danymi są w zasadzie wszelkie informacje zapisane w jakiś sposób. Danymi będą więc teksty prasowe, dane numeryczne w arkuszach kalkulacyjnych, dane osobowe zapisane bazach danych instytucji państwowych, raporty roczne spółek giełdowych, wyniki badań okresowych, nasze wpisy w serwisach społecznościowych, itd. W ostatnim czasie dużo mówi się o ochronie danych i ich wartości biznesowej (Data is money!). W pozostałych książkach z serii AI dla wszystkich pokazujemy jak sieci neuronowe i sieci semantyczne mogą pomóc nam w reprezentacji danych i wydobywaniu z nich niezwykle wartościowych informacji. Teraz jednak przejdźmy do podstawowych struktur danych jakie oferuje Python. Omówimy takie struktury danych jak: listy, słowniki, zbiory oraz krotki.

Do tej pory przechowywaliśmy pojedyncze wartości (czyli dane, takie jak napis lub liczba) w zmiennej. Struktury danych umożliwiają przechowywanie więcej niż jednej wartości w jednej zmiennej. Poza tym struktury danych pozwalają na grupowanie informacji oraz ich organizację.

Listy¶

Pierwszą strukturą danych jaką poznamy są listy. Doświadczenie pokazuje, że są one bardzo poręczne i bardzo często używane. Niekiedy nawet można odnieść wrażenie, że programowanie w języku Python, przynajmniej na początku, to w zasadzie ciągła praca z listami.

Sama nazwa mówi już wiele o tym, z czym mamy tu do czynienia. Lista to uporządkowany zbiór elementów (rzeczy, osób, zwierząt, etc.). Na przykład poniżej mamy listą z trzema elementami:

In [91]:
['element 1', 'element 2', 'element 3']
Out[91]:
['element 1', 'element 2', 'element 3']

Listę definiujemy za pomocą nawiasów kwadratowych: [oraz ]. Elementy listy odzielamy od siebie przecinkami. Możemy tworzyć listy napisów, liczb lub obiektów różnych typów:

In [92]:
['to', 'jest', 'lista', 'napisów']
Out[92]:
['to', 'jest', 'lista', 'napisów']
In [93]:
[1, 2, 3]
Out[93]:
[1, 2, 3]
In [94]:
['napis', 1, True]
Out[94]:
['napis', 1, True]

Listę możemy przypisać do zmiennej. Dzięki temu możemy odwołać się nie tylko do całej listy w sposób poręczny, ale również do poszczególnych jej elementów. Utwórzmy zmienną rainbow (tęcza):

In [100]:
rainbow = []

W ten sposób przypisaliśmy do zmiennej rainbow listę pustą, tzn. taką, która nie ma jeszcze żadnych elementów (metaforycznie można powiedzieć, że przygotowaliśmy czystą kartkę, na której dopiero będziemy tworzyć naszą listę).

Jak wiemy, tęcza składa się z 7 kolorów ułożonych w kolejności: czerwony, pomarańczowy, żółty, zielony, niebieski, indygo i fioletowy.

Do listy możemy dodawać elementy. Do tego służy metoda append(). Przykład użycia:

In [101]:
rainbow.append('czerwony')
print(rainbow)
['czerwony']

Dodajmy następnie kolejny kolor tęczy:

In [102]:
rainbow.append('pomarańczowy')
print(rainbow)
['czerwony', 'pomarańczowy']

Zauważmy, że metoda append() dodaje elementy zawsze na końcu listy.

Jeśli jeszcze raz wydarz Pythonowi komendę rainbow.append('pomarańczowy') i wydrukujesz listę, to zobaczysz, że elementy w liście mogą się powtarzać.

In [103]:
rainbow.append('pomarańczowy')
print(rainbow)
['czerwony', 'pomarańczowy', 'pomarańczowy']

Gdybyśmy chcieli sprawdzić ile razy dany element pojawia się na naszej liście, możemy użyć metody count():

In [104]:
rainbow.count('pomarańczowy')
Out[104]:
2

Oczywiście kolory w tęczy nie powtarzają się, więc musimy usunąć jedno wystąpienie koloru pomarańczowego. W tym przypadku nie ma znaczenia, który usuniemy. Do usuwania elementów z listy służy metoda remove(). Musimy podać wartość, która ma zostać usunięta. Pamiętaj, że remove() usuwa tylko pierwsze wystąpienie wartości na liście. Przykład użycia:

In [105]:
rainbow.remove('pomarańczowy')
print(rainbow)
['czerwony', 'pomarańczowy']

Teraz możemy dalej kontynuować dodawanie kolorów do tęczy.

In [108]:
rainbow.append('zielony')
print(rainbow)
['czerwony', 'pomarańczowy', 'zielony']

Pomyliliśmy się. Po pomarańczowym powinien być żółty. Musimy teraz albo 1) zamienić zielony na żółty i dodać jeszcze raz zielony po żółtym albo 2) wstawić kolor żółty pomiędzy pomarańczowy i zielony. Do obu z tych opcji potrzebujemy wiedzy o indeksach, o których jeszcze nie mówiliśmy w kontekście list, ale… już o nich wspominaliśmy przy okazji mówienia o napisach. Pamiętamy, że za pomocą komendy:

In [109]:
'Robert'[3]
Out[109]:
'e'

mogliśmy wyodrębnić z napisu literę „e”. 3 to indeks tej litery w napisie 'Robert'. Podobnie jest z listami. Każdy element listy ma swój indeks. Pamiętajmy, że wszelkie elementy w Pythonie indeksowane są zawsze od zera. Na przykład komenda

In [110]:
rainbow[1]
Out[110]:
'pomarańczowy'

zwróci drugi element listy (pomarańczowy). Indeksem koloru zielonego jest aktualnie 2, co można sprawdzić za pomocą komendy:

In [111]:
rainbow[2]
Out[111]:
'zielony'

lub

In [112]:
rainbow.index('zielony')
Out[112]:
2

Tak na marginesie dodajmy, że możemy również użyć indeksów ujemnych, by uzyskiwać dostęp do elementów listy od końca:

In [113]:
rainbow[-1]
Out[113]:
'zielony'

Powyższa komenda zwróci ostatni element listy, czyli aktualnie kolor zielony.

Wróćmy jednak do naszego problemu. Mamy aktualnie listę

In [114]:
rainbow
Out[114]:
['czerwony', 'pomarańczowy', 'zielony']

i dwie strategie działania:

  • albo 1) zamienić zielony na żółty i dodać jeszcze raz zielony po żółtym
  • albo 2) wstawić kolor żółty pomiędzy pomarańczowy i zielony.

Zastosowanie pierwszej z nich, czyli zamiana zielonego na żółty, polega na zastąpieniu danego element listy pod danym indeksem (w naszym przypadku koloru zielonego) przez element nowy (w naszym przypadku, kolor żółty). Wyżej wspomnieliśmy, że aby sprawdzić indeks elementu, używamy metody index().

In [115]:
rainbow.index('zielony')
Out[115]:
2

Wynikiem będzie 2. Znając indeks możemy wykonać następującą instrukcję:

In [116]:
rainbow[2] = 'żółty'
print(rainbow)
['czerwony', 'pomarańczowy', 'żółty']

lub tak, wiedząc, że chcemy zastąpić ostatni element:

In [117]:
rainbow[-1] = 'żółty'
print(rainbow)
['czerwony', 'pomarańczowy', 'żółty']

Nasza lista wygląda teraz tak:

['czerwony', 'pomarańczowy', 'żółty']

Wprawdzie kolejność jest prawidłowa, ale straciliśmy kolor zielony. Musimy go dodać jeszcze raz:

In [118]:
rainbow.append('zielony')
print(rainbow)
['czerwony', 'pomarańczowy', 'żółty', 'zielony']

Kontynuujmy dodawanie kolorów:

In [119]:
rainbow.append('indygo')
print(rainbow)
['czerwony', 'pomarańczowy', 'żółty', 'zielony', 'indygo']

Znów pomyliliśmy kolejność. Po zielonym powinien być niebieski. Wiemy już jak podmieniać elementy, ale użyta przez nas wcześniej strategia nie wydaje się najlepsza, ponieważ zastępując element jednocześnie go usuwa. Spróbujmy teraz drugiej strategii, czyli wstawienia nowego elementu pomiędzy dwa elementy listy. Użyjemy do tego metody insert(). Metoda ta wymaga podania indeksu, w miejsce którego chcemy wstawić element oraz nazwę elementu, który chcemy do listy dodać. Chcemy wstawić „niebieski” w miejsce, w którym teraz jest kolor indygo. To pozycja o indeksie 4:

In [120]:
rainbow.insert(4, 'niebieski')
print(rainbow)
['czerwony', 'pomarańczowy', 'żółty', 'zielony', 'niebieski', 'indygo']

Jak widać, metoda insert() nie usunęła indygo, ale przesunęła go o jedno miejsce. Kolor indygo ma teraz indeks 5. Sprawdź to używając metody index().

Pozostało nam dodanie ostatniego koloru do naszej tęczy:

In [121]:
rainbow.append('fioletowy')

Wyświetlmy zatem elementy naszej listy. Możemy użyć do tego pętli for z listą:

In [122]:
for color in rainbow:
  print(color)
czerwony
pomarańczowy
żółty
zielony
niebieski
indygo
fioletowy

Dokładnie przeanalizuj tę pętlę. Zauważ, że pętla for korzysta bezpośrednio z listy przechodząc kolejno po wszystkich jej elementach.

Gdybyśmy chcieli zwrócić kolejno wszystkie elementy tęczy wraz z ich pozycją możemy skorzystać z funkcji enumerate(). Funkcja zwraca elementy listy wraz z ich indeksami. Poniższy kod wyświetli elementy naszej tęczy wraz z numerem wskazującym na jego kolejność:

In [123]:
for index, color in enumerate(rainbow):
  print(index+1,'-',color)
1 - czerwony
2 - pomarańczowy
3 - żółty
4 - zielony
5 - niebieski
6 - indygo
7 - fioletowy

Oto jeszcze kilka metod, o których warto wspomnieć w kontekście list.

Do usuwania elementów z listy służy również del. Należy podać indeks elementu, który ma zostać usunięty. W przypadku podania indeksu elementu, który nie znajduje się na liście, zostaje zgłoszony błąd.

In [124]:
del rainbow[-1]
print(rainbow)
['czerwony', 'pomarańczowy', 'żółty', 'zielony', 'niebieski', 'indygo']

Różnica między użyciem del i wcześniej omówioną metodą remove(), nie licząc innego sposobu zapisywania tych instrukcji, polega na tym, że w del podajemy indeks elementu, który ma zostać usunięty (pozycję na liście), a w przypadku remove() podajemy samą wartość, która ma zostać usunięta.

Za pomocą funkcji len() można sprawdzić wielkość listy, czyli określić, ile elementów znajduje się w liście (pamiętamy, że tę samą funkcję używaliśmy wcześniej do sprawdzenia liczby znaków w napisie). Przykład użycia tej funkcji:

In [125]:
len(rainbow)
Out[125]:
6

Jeżeli chcemy sprawdzić, czy jakiś element znajduje się na liście, możemy użyć instrukcji if z operatorem in:

In [126]:
if 'żółty' in rainbow:
    print("Mamy kolor żółty w tęczy.")
else:
    print("Nie ma koloru żółtego w tęczy.")

if 'biały' in rainbow:
    print("Mamy kolor biały w tęczy.")
else:
    print("Nie ma koloru białego w tęczy.")
Mamy kolor żółty w tęczy.
Nie ma koloru białego w tęczy.

Powyższe instrukcje są wyrażeniami warunkowymi if i obowiązują je przedstawione wcześniej zasady. Operator in sprawdza, czy dany element znajduje się na liście.

Kolejność elementów listy jest zgodna z kolejnością ich dodawania do listy. Gdybyśmy chcieli tę kolejność zmienić, na przykład ustawić elementy listy alfabetycznie, możemy listę posortować. Do sortowania elementów na liście służy metoda sort(). Domyślnie elementy sortowane są od najmniejszego do największego.

Ustawmy kolory naszej tęczy w porządku alfabetycznym. Zacznijmy od stworzenia nowej tęczy o nazwie sorted_raibow. Skopiujmy do niej kolory z tęczy rainbow. Poniższy kod uporządkuje kolory w porządku alfabetycznym:

In [127]:
sorted_rainbow = rainbow.copy() # sorted_rainbow = rainbow[:]
sorted_rainbow.sort()
print(sorted_rainbow)
['czerwony', 'indygo', 'niebieski', 'pomarańczowy', 'zielony', 'żółty']

Aby uporządkować listę w porządku odwrotnym do alfabetycznego użyjemy reverse=True jak pokazano poniżej:

In [128]:
sorted_rainbow = rainbow.copy()
sorted_rainbow.sort(reverse=True)
print(sorted_rainbow)
['żółty', 'zielony', 'pomarańczowy', 'niebieski', 'indygo', 'czerwony']

Gdybyśmy chcieli odwrocić kolejność kolorów tęczy skorzystam z metody reverse():

In [129]:
reversed_rainbow = rainbow.copy()
reversed_rainbow.reverse()
print(reversed_rainbow)
['indygo', 'niebieski', 'zielony', 'żółty', 'pomarańczowy', 'czerwony']

A gdybyśmy życzyli sobie wymieszać kolory tęczy tak, aby ich kolejność była losowa, użyjemy metody random.shuffle():

In [144]:
import random
random.shuffle(rainbow)
print(rainbow)
['niebieski', 'zielony', 'żółty', 'indygo', 'pomarańczowy', 'czerwony', 'fioletowy']

Metoda pop() usuwa element o podanym indeksie jednocześnie zwracając usuwany element. Na przykład poniższa instrukcja usunie z listy rainbow ostatni element i go wyświetli:

In [130]:
print(rainbow.pop(-1))
indygo

Jeśli chcemy usunąć wszystkie elementy listy używamy metody clear():

In [131]:
rainbow.clear()
print(rainbow)
[]

Powyższy kod zwróci listę pustą, czyli [].

Mając dwie listy możemy je połączyć lub innymi słowy, rozszerzyć jedną listę o elementy drugiej listy. Służy do tego metoda extend():

In [132]:
languages = ['Polish', 'English']
languages_to_add = ['Spanish', 'Italian']
languages.extend(languages_to_add)
print(languages)
['Polish', 'English', 'Spanish', 'Italian']

Podobnie jak w przypadku napisów również dla list możemy używać „krajania” (ang. slicing). Na przykład możemy „wykroić” z listy jakąś jej część, jak to pokazano niżej

In [133]:
rainbow = ['czerwony', 'pomarańczowy', 'żółty', 'zielony', 'niebieski', 'indygo', 'fioletowy']
rainbow[:3]
Out[133]:
['czerwony', 'pomarańczowy', 'żółty']

Zbliżając się do końca tego rozdziału omówmy jeszcze dwie ważne metody split() i join().

Metoda split() zamienia napis w listę, której elementami są fragmenty tego napisu. Sposób dzielenia napisu określa separator, którym domyślnie jest spacja. Utwórzmy zmienną paragraph i przypiszmy jej fragment tekstu zaczerpniętego z Wikipedii (https://pl.wikipedia.org/wiki/Tęcza):

In [137]:
paragraph = '''Pomimo faktu, że w tęczy występuje niemal ciągłe widmo kolorów, 
wyszczególnia się z reguły następujące barwy: 
czerwony (na zewnątrz łuku), pomarańczowy, żółty, zielony, 
niebieski, indygo i fioletowy (wewnątrz łuku).'''

paragraph_split = paragraph.split()
print(paragraph_split)
['Pomimo', 'faktu,', 'że', 'w', 'tęczy', 'występuje', 'niemal', 'ciągłe', 'widmo', 'kolorów,', 'wyszczególnia', 'się', 'z', 'reguły', 'następujące', 'barwy:', 'czerwony', '(na', 'zewnątrz', 'łuku),', 'pomarańczowy,', 'żółty,', 'zielony,', 'niebieski,', 'indygo', 'i', 'fioletowy', '(wewnątrz', 'łuku).']
In [138]:
paragraph_split = paragraph.split('\n')
print(paragraph_split)
['Pomimo faktu, że w tęczy występuje niemal ciągłe widmo kolorów, ', 'wyszczególnia się z reguły następujące barwy: ', 'czerwony (na zewnątrz łuku), pomarańczowy, żółty, zielony, ', 'niebieski, indygo i fioletowy (wewnątrz łuku).']

Metoda join() łączy elementy listy w napis. Łączenie wykonujemy za pomocą zadanego przez nas separatora.

In [139]:
' - '.join(rainbow)
Out[139]:
'czerwony - pomarańczowy - żółty - zielony - niebieski - indygo - fioletowy'

Na koniec poznajmy jeszcze funkcję zip(). Pozwala ona na iterowanie po dwóch lub więcej listach jak to pokazano poniżej:

In [141]:
rainbow = ['czerwony', 'pomarańczowy', 'żółty', 'zielony', 'niebieski', 'indygo', 'fioletowy']
friends = ['Jan', 'Kasia', 'Zosia', 'Michał', 'Zuzia', 'Marysia', 'Wojtek']

for color, friend in zip(rainbow, friends):
  print(friend, '-', color)
Jan - czerwony
Kasia - pomarańczowy
Zosia - żółty
Michał - zielony
Zuzia - niebieski
Marysia - indygo
Wojtek - fioletowy

Poniższy kod dzieli tekst na słowa i prezentuje dwie listy słów: przed i po oczyszczeniu:

In [146]:
sentence = 'Ala ma kota, a kot ma mysz; i co z tego? A to z tego, że nic.'
words = sentence.split()

signs_to_remove = '.,;!?()"\'*%$#@'
clean_words = []

for word in words:
  clean_word =''
  for sign in word:
    if sign not in signs_to_remove:
      clean_word = clean_word + sign
  clean_words.append(clean_word)    

for w1, w2 in zip(words, clean_words):
  print(w1,'\t', w2)
Ala 	 Ala
ma 	 ma
kota, 	 kota
a 	 a
kot 	 kot
ma 	 ma
mysz; 	 mysz
i 	 i
co 	 co
z 	 z
tego? 	 tego
A 	 A
to 	 to
z 	 z
tego, 	 tego
że 	 że
nic. 	 nic

Zapamiętaj!¶

append() - metoda dodaje element na koniec listy.

clear() - metoda usuwa wszystkie elementy z listy.

copy() - metoda zwraca kopię listy.

count() - metoda zwraca, ile razy określony element pojawił się na liście. del - służy m.in. do usuwania elementów listy; należy podać indeks elementu, który ma zostać usunięty. W przypadku podania indeksu elementu, który nie znajduje się na liście, zostaje zgłoszony błąd.

enumerate() - funkcja zwraca elementy listy wraz z ich indeksami.

extend() - metoda dodaje do końca listy wszystkie elementy innej listy.

index() - metoda zwraca indeks określonego elementu na liście.

join() - metoda pobiera wszystkie elementy listy i łączy je w jeden ciąg elementów oddzielonych zadanym separatorem (na przykład spacją). len() - funkcja zwraca liczbę elementów listy.

pop() - metoda usuwa element o podanym indeksie z listy i zwraca usunięty element.

random.shuffle() - metoda ustawia kolejność elementów listy w sposób losowy.

remove() - metoda usuwa pierwszy pasujący element z listy i zwraca błąd jeśli ustanego elementu nie ma na liście.

reverse() - metoda odwraca kolejność elementów listy.

sort() - metoda sortuje elementy danej listy w kolejności rosnącej lub malejącej.

split() - metoda dzieli napis za pomocą określonego separatora (domyślnie jest to spacja) i zwraca listę napisów.

zip() - funkcja pobiera elementy z kilku list, agreguje je i zwraca.

Słowniki¶

Słowniki to struktury danych, w których informacje przechowywane są w postaci par klucz:wartość. Słowniki w Pythonie, podobnie zresztą jak analogiczne struktury danych w innych językach programowania, działają na tej zasadzie, że na podstawie klucza wyszukiwana jest wartość.

Do tworzenia słowników służą nawiasy klamrowe: { oraz }. Klucz od wartości oddziela się znakiem :, a poszczególny wpisy w słowniku oddzielane są przecinkami. Najlepiej widać to na przykładzie:

In [148]:
rainbow_dict = {1:'czerwony',
                2:'pomarańczowy',
                3:'żółty',
                4:'zielony',
                5:'niebieski',
                6:'indygo',
                7:'fioletowy'}

Do elementów słownika odwołujemy się za pomocą kluczy. W powyższym przykładzie kluczami są liczby od 1 do 7 (choć kluczami mogą być również napisy). Poniższy kod zwraca napis czerwony.

In [149]:
rainbow_dict[1]
Out[149]:
'czerwony'

Warto nadmienić, że klucze nie są indeksami, a porządek par (klucz, wartość), nie ma znaczenia.

Metoda items() zwróci wszystkie elementy słownika w postaci par (klucz, wartość) w postaci specjalnego widoku:

In [150]:
rainbow_dict.items()
Out[150]:
dict_items([(1, 'czerwony'), (2, 'pomarańczowy'), (3, 'żółty'), (4, 'zielony'), (5, 'niebieski'), (6, 'indygo'), (7, 'fioletowy')])

Widot ten jest iterowalny, tzn. możemy po nim iterować:

In [154]:
for item in rainbow_dict.items():
  print(item)
(1, 'czerwony')
(2, 'pomarańczowy')
(3, 'żółty')
(4, 'zielony')
(5, 'niebieski')
(6, 'indygo')
(7, 'fioletowy')

Metoda keys() zwróci wszystkie klucze danego słownika w postaci specjalnego widoku:

In [155]:
rainbow_dict.keys()
Out[155]:
dict_keys([1, 2, 3, 4, 5, 6, 7])

Metoda values() zwróci wszystkie wartości danego słownika w postaci specjalnego widoku:

In [156]:
rainbow_dict.values()
Out[156]:
dict_values(['czerwony', 'pomarańczowy', 'żółty', 'zielony', 'niebieski', 'indygo', 'fioletowy'])

Podobnie jak w przypadku list, metoda clear() usuwa wszystkie elementy słownika, copy() tworzy jego kopię, pop() usuwa element słownika o zadanym kluczu i go zwraca. Metoda update() — podobnie jak extend() w listach - dodaje do słownika elementy innego słownika.

Samodzielnie napisz kod wykorzystujący te metody.

Metoda get() zwraca wartość dla zadanego klucza. Oprócz klucza można podać jako argument komunikat, który ma zwrócić Python w przypadku braku elementu o zadanym kluczu.

In [157]:
rainbow_dict.get(7)
Out[157]:
'fioletowy'

Poniższy kod zwróci napis o treści „Nie ma tylu kolorów w tęczy!”.

In [158]:
rainbow_dict.get(8, 'Nie ma tylu kolorów w tęczy!')
Out[158]:
'Nie ma tylu kolorów w tęczy!'

Poniżej znajduje się przykład słownika, którego kluczami są napisy:

In [160]:
pl_en_dict = {'mama':'mother',
              'tata':'dad',
              'syn':'son',
              'córka':'daughter',
              'ciocia':'aunt'}

pl_en_dict.get('mama')
Out[160]:
'mother'

Zapamiętaj!¶

clear() - metoda usuwa wszystkie elementy z listy.

copy() - metoda zwraca kopię listy.

dict() - funkcja tworzy słownik.

items() - metoda zwraca obiekt widoku, który wyświetla listę par/krotek (klucz, wartość).

keys() - metoda zwraca obiekt widoku, który wyświetla listę wszystkich kluczy słownika.

values() - metoda zwraca obiekt widoku, który wyświetla listę wszystkich wartości słownika.

get() - metoda zwraca wartość określonego klucza, jeśli klucz znajduje się w słowniku.

pop() - metoda usuwa element o podanym indeksie z listy i zwraca usunięty element.

update() - metoda dodaje do końca listy wszystkie elementy innej listy.

Zbadaj samodzielnie i zapamiętaj!¶

popitem() - metoda usuwa ostatni element dodany do słownika i go zwraca.

fromkeys() - metoda tworzy nowy słownik z podanej sekwencji elementów z wartością podaną przez użytkownika.

Zbiory¶

Zbiory są podobne do list, ale w ich przypadku kolejność elementów nie ma znaczenia. Podobnie jak to miało miejsce z listami, zbiór można utworzyć wymieniając jego elementy. Utwórzmy zbiór muzyków występujących w pierwszym składzie zespołu Hey w roku 1992:

In [161]:
hey_1992 = {'Katarzyna Nosowska', 'Marcin Żabiłowicz', 'Robert Ligiewicz', 'Marcin Macuk', 'Piotr Banach'}

Skład zespołu ulegał kilku zmianom (zob. https://pl.wikipedia.org/wiki/Hey#Muzycy). Utwórzmy więc zmienną hey_band, dzięki której zilustrujemy te zmiany:

In [163]:
hey_band = set()

Metoda update() dodaje do zbioru elementy innego zbioru (podobnie jak to maiło miejsce ze słownikami). Dodajmy więc do aktualnie pustego zbioru hey_band skład wyjściowy z roku 1992:

In [164]:
hey_band.update(hey_1992)
print(hey_band)
{'Marcin Żabiłowicz', 'Piotr Banach', 'Robert Ligiewicz', 'Marcin Macuk', 'Katarzyna Nosowska'}

W roku 1993 z zespołu odszedł basista Marcin Macuk, a dołączył w jego miejsce Jacek Chrzanowski. Użyjemy metody remove(), aby usunąć element ze zbioru i metody add(), aby do zbioru dodać nowy element:

In [165]:
hey_band.remove('Marcin Macuk')
hey_band.add('Jacek Chrzanowski')

Następnie utworzymy kopię tego składu za pomocą metody copy() i przypiszemy ją do zmiennej hey_1993:

In [166]:
hey_1993 = hey_band.copy()
print(hey_1993)
{'Marcin Żabiłowicz', 'Jacek Chrzanowski', 'Piotr Banach', 'Robert Ligiewicz', 'Katarzyna Nosowska'}

Zapamiętaj, że dodawanie elementu, który już jest w zbiorze, nie zmienia tego zbioru (inaczej niż miało to miejsce w przypadku list, gdy elementy mogły się powtarzać - w zbiorze elementy nigdy się nie powtarzają). I jeszcze jedna uwaga. Możesz usuwać elementy ze zbioru również za pomocą metody discard(). Ta metoda nie zwróci błędu jeśli zechcesz usunąć element zbioru, którego tam niema. Metoda remove() w takiej sytuacji zwróci błąd.

Następnie nastąpiły kolejne dwie ważne zmiany w zespole. Najpoważniejszą było odejście Piotra Banacha w roku 1999 roku:

In [167]:
hey_band.remove('Piotr Banach')
hey_1999 = hey_band.copy()
print(hey_1999)
{'Marcin Żabiłowicz', 'Robert Ligiewicz', 'Jacek Chrzanowski', 'Katarzyna Nosowska'}

W roku 2001 do Hey dołączył Paweł Krawczyk:

In [168]:
hey_band.add('Paweł Krawczyk')
hey_2001 = hey_band.copy()
print(hey_2001)
{'Marcin Żabiłowicz', 'Jacek Chrzanowski', 'Robert Ligiewicz', 'Paweł Krawczyk', 'Katarzyna Nosowska'}

A w 2012 do Hey na chwilę również dołączył Marcin Zabrocki:

In [169]:
hey_band.add('Marcin Zabrocki')
hey_2012 = hey_band.copy()
print(hey_2012)
{'Marcin Żabiłowicz', 'Jacek Chrzanowski', 'Robert Ligiewicz', 'Paweł Krawczyk', 'Marcin Zabrocki', 'Katarzyna Nosowska'}

Zespół Hey zawiesił swoją działalność w październiku 2017. Nie było już nim Marcina Zabrockiego, który był członkiem zespołu jedynie przez rok. Rok przed zawieszeniem, do zespołu wrócił Marcin Macuk:

In [170]:
hey_band.remove('Marcin Zabrocki')
hey_band.add('Marcin Macuk')
hey_2017 = hey_band.copy()
print(hey_2017)
{'Marcin Żabiłowicz', 'Jacek Chrzanowski', 'Robert Ligiewicz', 'Paweł Krawczyk', 'Marcin Macuk', 'Katarzyna Nosowska'}

Udało nam się utworzyć kilka zbiorów reprezentujących składy zespołu Hey w ciagu jego trwania. Teraz pokażemy, że na zbiorach możemy wykonywać ciekawe operacje.

Na przykład możemy sumować zbiory za pomocą metody union(). Poniższy kod utworzy zbiór wszystkich muzyków, którzy kiedykolwiek byli członkami zespołu Hey:

In [171]:
set.union(hey_1992, hey_1993, hey_1999, hey_2001, hey_2012, hey_2017)
Out[171]:
{'Jacek Chrzanowski',
 'Katarzyna Nosowska',
 'Marcin Macuk',
 'Marcin Zabrocki',
 'Marcin Żabiłowicz',
 'Paweł Krawczyk',
 'Piotr Banach',
 'Robert Ligiewicz'}

Możemy też stworzyć zbiór złożony z członków pierwszego i ostatniego składu:

In [172]:
hey_1992.union(hey_2017)
Out[172]:
{'Jacek Chrzanowski',
 'Katarzyna Nosowska',
 'Marcin Macuk',
 'Marcin Żabiłowicz',
 'Paweł Krawczyk',
 'Piotr Banach',
 'Robert Ligiewicz'}

Gdybyśmy chcieli sprawdzić, którzy muzycy byli we wszystkich składach zespołu, to powinniśmy poszukać części wspólnej wszystkich składów. Do tego służy metoda intersection():

In [173]:
set.intersection(hey_1992, hey_1993, hey_1999, hey_2001, hey_2012, hey_2017)
Out[173]:
{'Katarzyna Nosowska', 'Marcin Żabiłowicz', 'Robert Ligiewicz'}

Może nas również interesować, jacy muzycy byli w pierwszym składzie zespołu Hey, a których nie było w składzie finalnym z 2017 roku. Do tego służy metoda difference():

In [176]:
hey_1992.difference(hey_2017)
Out[176]:
{'Piotr Banach'}

Muzycy, którzy zmienili się między pierwszym i ostatnim składem:

In [177]:
hey_1992.symmetric_difference(hey_2017) # (X u Y) - (X n Y)
Out[177]:
{'Jacek Chrzanowski', 'Paweł Krawczyk', 'Piotr Banach'}

Gdbyśmy uznali, że ponieważ Hey zawiesił swoją działalność, to w zasadzie nie ma w nim już żądnych muzyków, wówczas możemy wyczyścić zbiór ze wszystkich elementów za pomocą metody clear():

In [178]:
hey_band.clear()

isdisjoint() zwraca wartość True, jeśli zbiory są rozłączne, tj. nie mają elementów wspólnych. Czy pierwszy skład był całkowicie różny od ostatniego? Sprawdźmy:

In [179]:
hey_1992.isdisjoint(hey_2017)
Out[179]:
False

issubset() zwraca wartość True, jeśli zbiór zawiera się w drugim, tj. wszystkie elementy jednego zbioru są również elementami drugiego (choć nie musi być odwrotnie):

In [180]:
hey_2001.issubset(hey_2017)
Out[180]:
True
In [181]:
hey_2017.issubset(hey_2001)
Out[181]:
False

Zapamiętaj!¶

clear() - metoda usuwa wszystkie elementy ze zbioru.

copy() - metoda zwraca kopię zbioru. difference() - metoda zwraca zbiór będący różnicą dwóch zbiorów.

discard() - metoda usuwa określony element ze zbioru (jeśli jest obecny); metoda nie zwróci błędu jeśli zechcesz usunąć element zbioru, którego niema. intersection() - metoda zwraca nowy zbiór zawierający elementy wspólne dla dwóch lub więcej zbiorów.

isdisjoint() - metoda zwraca True, jeśli dwa zbiory są rozłączne. Jeśli nie, zwraca False.

issubset() - metoda zwraca True, jeśli wszystkie elementy zbioru są obecne w innym zbiorze (przekazywanym jako argument). Jeśli nie, zwraca False.

remove() - metoda usuwa określony element ze zbioru; metoda zwróci błąd jeśli zechcesz usunąć element zbioru, którego tam niema.

union() - metoda zwraca nowy zbiór z elementami ze wszystkimi zbiorów będących jej argumentami.

update() - metoda dodaje do końca zbioru wszystkie elementy innego zbioru.

Zbadaj samodzielnie i zapamiętaj!¶

issuperset() - metoda zwraca wartość True, jeśli zbiór jest nadzbiorem drugiego

pop() - metoda usuwa dowolny element ze zbioru i zwraca ten element.

frozenset() - funkcja tworzy zbiór niezmienny, tzn. taki, którego elementów nie można zmienić (dodać lub usunąć).

Krotki¶

Krotki (ang. tuple) podobnie jak listy służą do przechowywania informacji o kilku elementach jednocześnie. W krotkach podobnie jak w listach kolejność elementów ma znaczenie. To co różni krótki od list to nawiasy, w których zapisujemy elementy: ( i ). Kolejną różnicą jest fakt, że nie można zmieniać rozmiaru i zawartości raz utworzonej krotki (co wyrażamy angielskim terminem immutable).

Za pomocą krotek możemy na przykład przechowywać informacje of kulturystach:

In [182]:
jan_kaszanka = ('Jan', 'Kaszanka', 180, 113, 54, 150)
jan_kaszanka
Out[182]:
('Jan', 'Kaszanka', 180, 113, 54, 150)

W powyższej krotce na kolejnych miejscach mamy informację dotyczące odpowiednio: imienia, nazwiska, wzrostu, wagi, obwodu bicepsa oraz obwodu klatki piersiowej. Gdyby rozmiary bicepsa lub klatki piersiowej Jana Kaszanki zmieniły się musielibyśmy zmiennej jan_kaszanka przypisać nową krotkę (update nie jest możliwy!):

In [183]:
jan_kaszanka = ('Jan', 'Kaszanka', 180, 113, 55, 150)

Dla krotek Pyton oferuje dwie metody: count() oraz index(). count() która zwraca ile razy dany element występuje w danej krotce:

In [184]:
jan_kaszanka.count(180)
Out[184]:
1

Metodę index() zwraca indeks zadanego elementu. Używając metody index() możemy podać trzy argumenty: element, którego szukamy, indeks od którego zaczniemy wyszukiwanie oraz indeks na którym zakończymy wyszukiwanie:

In [185]:
jan_kaszanka.index(180, 2, 3)
Out[185]:
2

W powyższym przykładzie szukamy w krotce wartości 180 zaczynając od indeksu 2 i kończąc na indeksie 3.

Krotki są iterowalne:

In [186]:
for i in jan_kaszanka:
  print(i)
Jan
Kaszanka
180
113
55
150

Kolekcje Pythona¶

Listy, słowniki, zbiory oraz krotki są kolekcjami (ang. collections). Cześć metod, o których pisaliśmy w tym rozdziale ma szerszy zakres, tj. nie stosuje się tylko do list lub tylko do słowników, ale do wszystkich kolekcji. Poniżej pokażemy przykładowo, że argumentem metody extend() dla list może być dowolna kolekcja.

In [188]:
l = [1,2]                 # lista
s = {3,4}                 # zbiór
t = (5,6)                 # krotka
d = {7:"buh", 8:"buha"}   # słownik

l.extend(s)
print(l)

l.extend(t)
print(l)

l.extend(d)
print(l)
[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7, 8]

Podobnie argumentem metody sorted() dla list może być dowolna kolekcja:

In [189]:
sorted({2, 1, 4, 3})
Out[189]:
[1, 2, 3, 4]

Trzy poniższe linie kodu mają ten sam wynik, zbiór {1, 2, 3, 4}:

In [190]:
{1,2}.union([3,4])
{1,2}.union((3,4))
{1,2}.union({3:"buh", 4:"buha"})
Out[190]:
{1, 2, 3, 4}

Podobnie możemy użyć kolekcji z metodami stosowanymi do zbiorów: intersection(), difference(), symmetric_difference(), issubset() oraz issuperset(). Możemy też utworzyć niezmienny zbiór za pomocą funkcji frozenset(), z dowolnej kolekcji. Podobnie funkcja enumerate(), którą używaliśmy w pętli for może przyjąć jako argument dowolną kolekcję, np. zbiór.

Listy, słowniki i zbiory składane¶

Mechanizm składania (ang. comprehension) ułatwia tworzenie list, słowników oraz zbiorów i należy do tych aspektów języka Python, które łatwiej jest pokazać niż wytłumaczyć.

Listy składane¶

Listę składaną (ang. list comprehension) możemy utworzyć z dowolnej kolekcji. Załóżmy, że mamy listę cen trzech produktów:

In [191]:
prices = [100, 150, 200]

Gdbyśmy chcieli utworzyć nową listę z cenami zwiększonymi o 20%, o nazwie prices_increased, to korzystając z dotychczas poznanych technik moglibyśmy zrobić tak:

In [192]:
prices_increased = []
for price in prices:
  prices_increased.append(price * 1.20)

prices_increased  
Out[192]:
[120.0, 180.0, 240.0]

Używając list składanych moglibyśmy to samo zrobić za pomocą jednej linii kodu w następujący sposób:

In [193]:
[price * 1.20 for price in prices]
Out[193]:
[120.0, 180.0, 240.0]

Słowniki składane¶

Słowniki składane (ang. dictionary comprehension) tworzymy podobnie jak listy składane. Załóżmy, że mamy dwie listy, listę owoców i ich cen:

In [194]:
fruits = ['mandarynki', 'jabłka', 'kiwi']
prices = [15, 10, 20]

Słownik, którego kluczami są nazwy owoców, a wartościami ich ceny tworzymy następująco:

In [195]:
fruits_prices_dict = {fruit:price for fruit, price in zip(fruits, prices)}
fruits_prices_dict
Out[195]:
{'mandarynki': 15, 'jabłka': 10, 'kiwi': 20}

Słowniki składane są poręczne, gdy chcemy zamienić w już istniejącym słowniku klucze na wartości. Wówczas robimy tak:

In [196]:
{value:key for key, value in fruits_prices_dict.items()}
Out[196]:
{15: 'mandarynki', 10: 'jabłka', 20: 'kiwi'}

Zbiory składane¶

Zbiory składane (ang. set comprehension) tworzymy w dokładnie taki sam sposób. Zadaniem poniższego kodu jest stworzenie zbioru słów znajdujących się we fragmecie wiersza ,,Szybko'' Danuty Wawiłow.

In [202]:
strophe = '''
Szybko, zbudź się, szybko, wstawaj!
Szybko, szybko, stygnie kawa! 
Szybko, zęby myj i ręce! 
Szybko, światło gaś w łazience! 
Szybko, tata na nas czeka! 
Szybko, tramwaj nam ucieka! 
Szybko, szybko, bez hałasu! 
Szybko, szybko, nie ma czasu! 

Na nic nigdy nie ma czasu? 
'''

bag_of_words = {word for word in strophe.lower().replace('.', '').replace(',', '').replace('!', '').split()}
bag_of_words
Out[202]:
{'bez',
 'czasu',
 'czasu?',
 'czeka',
 'gaś',
 'hałasu',
 'i',
 'kawa',
 'ma',
 'myj',
 'na',
 'nam',
 'nas',
 'nic',
 'nie',
 'nigdy',
 'ręce',
 'się',
 'stygnie',
 'szybko',
 'tata',
 'tramwaj',
 'ucieka',
 'w',
 'wstawaj',
 'zbudź',
 'zęby',
 'łazience',
 'światło'}

Pliki¶

W poprzednim rozdziale mówiliśmy danych i o strukturach (takich jak listy, słowniki, zbiory oraz krotki) służących do organizowania danych. Powiedzieliśmy, że danymi są wszelkie informacje zapisane w jakiś sposób. Teraz dodamy, że dane są najczęściej zapisywane w plikach. Dokumenty, które tworzymy w takich programach jak Word, czy arkusze kalkulacyjne, to tylko niektóre przykłady typów plików, z którymi spotykamy się niemalże na co dzień.

Pliki możemy podzielić roboczo na dwie grupy, te które narzucają danym pewną strukturę, dzięki której nota bene możemy na tych danych dokonywać pewnych operacji (tu mamy na przykład arkusze kalkulacyjne) oraz te, które nie posiadają struktury dla danych które przechowują (tu mamy zwykłe pliki tekstowe).

Z punktu widzenia Pythona, na pliku możemy wykonać jedną z trzech operacji:

  • Otworzyć plik do odczytu (ang. read). Tę operację oznaczymy małą literą r.
  • Otworzyć plik w celu dodania do niego nowej linii (ang. append). Tę operację oznaczymy małą literą a. Operacja ta tworzy plik, jeśli plik do którego chcemy coś dodać nie istnieje.
  • Otworzyć plik w celu zapisania w nim nowej treści (ang. write). Tę operację oznaczymy małą literą w. Operacja ta tworzy plik, jeśli plik w którym chcemy coś zapisać nie istnieje oraz nadpisuje istniejącą treść o ile plik istnieje.

TXT¶

Tworzymy listę zakupów i dodajemy do niej produkty:

In [203]:
with open('lista_zakupów.txt', mode='w') as file:
  file.write('Lista zakupów:\n')
  while True:
    product = input('Dodaj produkt: ')
    if product:
      file.write(f'- {product}\n')
    else:
      break
      
Dodaj produkt: masło
Dodaj produkt: chleb
Dodaj produkt: 

Odczytujemy listę zakupów:

In [204]:
with open('lista_zakupów.txt', 'r') as file:
  print(file.read())
Lista zakupów:
- masło
- chleb

In [205]:
with open('lista_zakupów.txt', 'r') as file:
  for line in file:
    print(line)
Lista zakupów:

- masło

- chleb

Praca na liniach pliku poza blokiem with:

In [206]:
with open('lista_zakupów.txt', 'r') as file:
  lines = file.readlines()

for line in lines:
  print(line)
Lista zakupów:

- masło

- chleb

CSV¶

Plik csv (ang. comma separated values) jest plikiem tekstowym, w którym informacje są uporządkowane w formie tabularycznej. Możemy więc traktować plik csv jako tabelę, w której mamy nazwy kolumn w pierwszym wierszu i to one nadają sens danym. Dane są zwykle oddzielone przecinkami, choć możliwe jest ustalenie innego separatora (ang. delimiter).

Pliki csv można czytać jak normalne pliki tekstowe. Są również biblioteki nieco ułatwiające czytanie takich plików i zapisywanie w nich informacji.

In [207]:
import csv

with open('students.csv', 'w') as file:
  csv_writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)

  csv_writer.writerow(['Jan', 'Kiełbasa', '12.12.2000', 'Białystok'])
  csv_writer.writerow(['Monika', 'Szacka', '16.04.2000', 'Lublin'])

Największy problem z plikami csv dotyczy danych (np. napisów lub liczb), które w swojej strukturze zawierają separator. Na przykład liczba 11,235 czy napis Cześć, co u ciebie? zapisane do pliku csv bez cudzysłowu będą stanowiły realny problem.

Stąd używając writera określamy, czy i jak chcemy używać cudzysłowu. Definicja naszego writera wygląda tak, że quoting=csv.QUOTE_MINIMAL, co oznacza, że cudzysłów zostanie użyty w sytuacji, gdy w danych będziemy mieli separator (czyli przecinek w naszym przypadku) lub znak przypisany do parametru quotechar. csv.QUOTE_ALL zamieści wszystkie dane w cudzysłowie.

Odczytując dane musimy również ustalić atrybuty parametrów delimiter i quotechar.

In [208]:
with open('students.csv', newline='') as file:
  csv_reader = csv.reader(file, delimiter=',', quotechar='"')
  for row in csv_reader:
    print(f'Imię: {row[0]}\nNazwisko: {row[1]}\nData urodzenia: {row[2]}\nMiejsce urodzenia: {row[3]}\n')
Imię: Jan
Nazwisko: Kiełbasa
Data urodzenia: 12.12.2000
Miejsce urodzenia: Białystok

Imię: Monika
Nazwisko: Szacka
Data urodzenia: 16.04.2000
Miejsce urodzenia: Lublin

JSON¶

JSON (skrót od JavaScript Object Notation) to bardzo popularny i poręczny format wymiany danych między systemami. Struktura informacji w JSONie jest bardzo podobna do słowników Pythona. Oto przykład danych zapisanych w formacie JSON:

{
 "name":"Jan", 
 "surname":"Kiełbasa", 
 "data_of_birth":"12.12.2000", 
 "place_of_birth":"Białystok"
}

Python ma wbudowany pakiet json, który ułatwia czytania i zapisywanie danych zapisanych w formacie JSON.

Możemy przekształcić słownik Pythona w strukturę formatu JSON za pomocą metody json.dump() i zapisać ją do pliku:

In [217]:
dict_example = {
 "name":"Jan", 
 "surname":"Kiełbasa", 
 "data_of_birth":"12.12.2000", 
 "place_of_birth":"Białystok"
}

with open("file.json", "w") as file:
    json.dump(dict_example, file)

Poniżej pokazujemy przykład użycia metody json.load() do wczytania danych w formacie JSON. Zauważcie, że wynikiem jest słownik.

In [219]:
import json

with open("file.json", "r") as file:
    data = json.load(file)
    
print(data)
type(data)
{'name': 'Jan', 'surname': 'Kiełbasa', 'data_of_birth': '12.12.2000', 'place_of_birth': 'Białystok'}
Out[219]:
dict

Definiujemy własne funkcje¶

Python zawiera wiele funkcji wbudowanych. Do tej pory poznaliśmy już kilka z nich: str(), print(), input(), czy range().

W Pythonie możemy pisać własne funkcje. Dzięki funkcjom możemy „zamknąć” część kodu programu w zwartej strukturze. Zobaczymy dalej, analizując kilka przykładów, że używanie własnych funkcji ma wiele korzyści i tak naprawdę trudno sobie wyobrazić programowanie Pythonie bez częstego korzystanie z własnych funkcji.

Każda dobrze zdefiniowana funkcja wygląda tak:

def NAZWA FUNKCJI(LISTA PARAMETRÓW):
  """
    opis działania funkcji
  """   
  BLOK INSTRUKCJI

Definicja funkcji zaczyna się od słowa kluczowego def po którym następuje NAZWA FUNKCJI, po której znów mamy w nawiasach zwykłych LISTĘ PARAMETRÓW funkcji oraz :.

W kolejnych liniach, poprzedzony odpowiednią tabulacją, pojawia się komentarz zawierający opis działania funkcji, jej parametrów i ew. co funkcja zwraca oraz BLOK INSTRUKCJI, który zostaje wykonany po wywołaniu funkcji. Komentarz może być pominięty, jeśli funkcja jest prosta, a jej działanie oczywiste.

Rozważmy poniższe dwa przykłady definicji funkcji:

In [262]:
def print_welcome_message(name, surname):
    """
    print_welcome_message displays the welcome message.

    :param name: name string
    :param surname: surname string
    
    """ 
    print(f'Witaj człowieku o imieniu {name} i nazwisku {surname}!')

Funkcja nazywa się print_welcome_message i ma dwa parametry name i surname. Blok instrukcji tej funkcji składa się z jednej instrukcji print().

Wywołujemy funkcję podając jej nazwę oraz przekazując jej argumenty, które powinny pojawić się w miejscu odpowiednich parametrów:

In [261]:
print_welcome_message(name='Robert', surname='Trypuz')
Witaj człowieku o imieniu Robert i nazwisku Trypuz!

Jedną z zalet funkcji jest możliwość wielokornego ich wywoływania z różami argumentami jak to pokazujemy poniżej:

In [222]:
print_welcome_message('Maria','Zawada')
Witaj człowieku o imieniu Maria i nazwisku Zawada!

Gdy raz napiszemy dobrze funkcję, możemy ją w zasadzie traktować jako black box mający swoje wejście i wyjście. Sposób działania nie zawsze musi nam zaprzątać głowę.

Warto jeszcze dodać, że funkcje nie muszą mieć parametrów. Na przykład funkcja poniżej ma jedynie wyświetlić obrazek filiżanki z pachnącą kawą (przepisując ten kod możesz ten obrazek uprościć):

In [263]:
def espresso_please():
    """
    espresso_please displays the cup picture.

    """     
    espresso = r'''
            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    '''
    print(espresso)

Zauważ r przed napisem w powyższym kodzie. Nieco upraszczając można powiedzieć, że nakazuje on Pythonowi potraktować napis dokładnie tak jak my go widzimy.

In [264]:
espresso_please()
            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    
In [265]:
def more_espresso_please(number):
    """
    more_espresso_please displays the cup picture number times.

    :param number: number of times to display the cup picture
    
    """ 
    for i in range(number):
        espresso_please()
In [266]:
more_espresso_please(2)
            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    

            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    

Funkcje i zmienne¶

Pamiętamy, że zmienne w pętli nie były „widoczne” poza pętlą. Podobnie zmienne w pewnym bloku kodu nie były widoczne poza nim. Tak też będzie z funkcjami. Zmienne i argumenty wewnątrz funkcji mają zasięg lokalny (ang. local scope), dlatego nazywane są zmiennymi lokalnymi. Zmienne będące na zewnątrz wszystkich funkcji mają zasięg globalny (ang. global scope), stąd nazywane są zmiennymi globalnymi.

Istnieje tylko jeden zasięg globalny i jest on utworzony wraz z rozpoczęciem programu. Zasięg lokalny jest tworzony za każdym razem, gdy dowolna funkcja jest wywoływana. Zmienne z zasięgu globalnego są dostępne w zasięgu lokalnym, ale nie odwrotnie.

Argumenty pozycyjne¶

Wywołując funkcję możemy pominąć nazwy parametrów i od razu podać argumenty w nawiasie:

In [267]:
print_welcome_message('Robert', 'Trypuz')
Witaj człowieku o imieniu Robert i nazwisku Trypuz!

Są to tzw. argumenty pozycyjne i musimy pamiętać, że kolejność ich zapisania ma znaczenie. Porównaj:

In [268]:
print_welcome_message('Trypuz', 'Robert')
Witaj człowieku o imieniu Trypuz i nazwisku Robert!
In [269]:
print_welcome_message(surname='Trypuz', name='Robert')
Witaj człowieku o imieniu Robert i nazwisku Trypuz!

Wartości domyślne¶

Python pozwala na przypisanie parametrom funkcji domyślnych wartości. Korzyść z tego jest taka, że wywołując tę funkcję możemy pominąć przekazanie jej niektórych argumentów, oczywiście o ile jesteśmy zadowoleni z ich wartości domyślnej. Poniżej definiujemy funkcję n_espresso_please, która wyświetli obrazek filiżanki kawy number razy, przy czym domyślnie 1.

In [273]:
def more_espresso_please(number=1):
    """
    n_espresso_please displays the cup picture number times.

    :param number: number of times to display the cup picture
    
    """     
    for i in range(number):
        espresso_please()
In [275]:
more_espresso_please()
            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    
In [276]:
more_espresso_please(number=2)
            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    

            (  )  
            )  (   
          (   )    
         _______
        <_______> ___
        |       |/ _ \
        |       | | | |
        |         |_| |
     ___|       |\___/
    /   \_______/    \
    \________________/

    

Podobnie funkcja poniżej wyświetla nazwę produkt oraz kraj jego pochodzenia. Domyślnym krajem jest Polska.

In [277]:
def product_info(product_name, country_of_orgin='Polska'):
    """
    product_info displays info about product, its name and country of orgin.

    :param product_name: name of product
    :param country_of_orgin: country of orgin string; 'Polska' by default
    
    """     
    print(f'Nazwa produktu: {product_name}, kraj pochodzenia: {country_of_orgin}')

product_info('kiełbasa')
product_info('kawa', 'Nigeria')
Nazwa produktu: kiełbasa, kraj pochodzenia: Polska
Nazwa produktu: kawa, kraj pochodzenia: Nigeria

Wartości opcjonalne¶

Wartości opcjonalne parametrów, czyli „mogą być, ale też może ich nie być”, to sposób na poszerzenie zakresu działania funkcji. Krótko mówiąc, dzięki wartościom opcjonalnym funkcje mogą więcej, ponieważ dostosowują się do różnych sytuacji. Jedna funkcja z wartościami opcjonalnymi może zastąpić kilka funkcji z wartościami wymaganymi. Poniższa funkcja „poszerza” zakres działania wcześniej zdefiniowanej funkcji o tej samej nazwie:

In [278]:
def print_welcome_message(name=None, surname=None):
    """
    print_welcome_message displays the welcome message. 
    If name and surname are provided, the funciton refers to them.

    :param name: name string, optional
    :param surname: surname string, optional
    
    """      
    if name is None and surname is None:
        print('Witaj!')
    else:
        print(f'Witaj człowieku o imieniu {name} i nazwisku {surname}!')
In [279]:
print_welcome_message()
Witaj!
In [280]:
print_welcome_message(name='Robert', surname='Trypuz')
Witaj człowieku o imieniu Robert i nazwisku Trypuz!

Wartość zwrotna¶

Funkcja nie musi nic wyświetlać. Zadaniem niektórych funkcji będzie zwracania wartości. Funkcja poniżej przyjmuje dwa parametry, a następnie zwraca wynik operacji +.

In [281]:
def add(a, b):
    """
    add function adds two numbers or concatenates two strings. 
    If name and surname are provided, the funciton refers to them.

    :param a: number or string
    :param b: number or string
    :return: sum of two numbers or concatenation of two strings
    
    """     
    result = a + b
    return result
In [282]:
add(2,3)
Out[282]:
5
In [283]:
add(2,3) * add(3,4)
Out[283]:
35
In [284]:
add('Robert', 'Trypuz')
Out[284]:
'RobertTrypuz'

Funkcje i struktury danych¶

Parametrem funkcji mogą być różne struktury danych. Poniższa funkcja zakłada, że parametrem będzie lista osób:

In [285]:
def print_awart_winners(list_of_winners):
  print('Oto nasi zwycięzcy:')
  for winner in list_of_winners:
    print('-', winner)

print_awart_winners(['Jan z Warszawy', 'Maria z Grudziądza', 'Janina z Sokółki'])
Oto nasi zwycięzcy:
- Jan z Warszawy
- Maria z Grudziądza
- Janina z Sokółki

Funkcje mogą również zwracać różne struktury danych. Poniższa funkcja zwraca słownik utworzony z argumentów funkcji.

In [286]:
def create_student_dict(name, surname, date_of_birth, place_of_birth):
  student_dict = {'name': name,
                  'surname': surname,
                  'date_of_birth': date_of_birth,
                  'place_of_birth': place_of_birth}
  return student_dict
In [287]:
create_student_dict('Robert', 'Trypuz', '12.07.1956', 'Lublin')
Out[287]:
{'name': 'Robert',
 'surname': 'Trypuz',
 'date_of_birth': '12.07.1956',
 'place_of_birth': 'Lublin'}
In [288]:
students_list = []
students_list.append(create_student_dict('Jan', 'Kiełbasa', '12.12.2000', 'Białystok'))
students_list.append(create_student_dict('Monika', 'Szacka', '16.04.2000', 'Lublin'))
print(students_list)
[{'name': 'Jan', 'surname': 'Kiełbasa', 'date_of_birth': '12.12.2000', 'place_of_birth': 'Białystok'}, {'name': 'Monika', 'surname': 'Szacka', 'date_of_birth': '16.04.2000', 'place_of_birth': 'Lublin'}]

Przekazywanie dowolnej liczby argumentów¶

Użyj:

  • * - dla krotki
  • ** - dla słownika

*languages poniżej to krotka z dowolną liczbą argumentów.

In [289]:
def add_foreign_languages(*languages):
  if len(languages) > 1:
    print('Kandydat zna języki obce:')
    for language in languages:
      print(f'- {language}')
  elif len(languages) == 1:
    print(f'Kandydat zna język {languages[0]}.')
  else:
    print('Z języków obcych to kandyda ogarnia tylko te w swoich w butach.')

add_foreign_languages('angielski')
Kandydat zna język angielski.
In [290]:
add_foreign_languages('angielski', 'hiszpański')
Kandydat zna języki obce:
- angielski
- hiszpański

**languages to słownik z dowolną liczbą elementów.

In [291]:
def add_foreign_languages(**languages):
  if len(languages) > 1:
    print('Kandydat zna języki obce:')
    for language, level in languages.items():
      print(f'- {language}: {level}')
  elif len(languages) == 1:
    print(f'Kandydat zna język {list(languages.keys())[0]} {languages[list(languages.keys())[0]]}.')
  else:
    print('Z języków obcych to kandyda ogarnia tylko te w swoich w butach.')

add_foreign_languages(angielski='biegle', włoski='początkująco')
Kandydat zna języki obce:
- angielski: biegle
- włoski: początkująco

Styl i gracja¶

Na koniec podsumujmy zalety używania funkcji oraz powiedzmy kilka słów o estetyce programowania funkcji.

Zalet używania funkcji:

  • Nie powielamy kodu, co pozwala zmniejszyć rozmiar programu, zmniejsza ryzyko popełnienia błędów oraz ułatwia wprowadzanie zmian.
  • Wyodrębnienie fragmentu kodu pozwala lepiej go zrozumieć oraz wprowadzać odpowiednie poprawki, jeśli pojawi się taka potrzeba lub konieczność.
  • Kod zamknięty w funkcji jest łatwiejszy do czytania i zrozumienia. Nazwa funkcji nazywa szereg instrukcji w niej zawartych i w opisuje ich sens.

Oto kilka wskazówek, które pozwolą ci poprawić styl twojego kodu:

  • Nazwa funkcji powinna jasno wskazywać co dana funkcja robi.
  • Nazwa funkcji powinna składać się z małych liter i jeśli składa się z kilku słów należy połączyć się znakiem _
  • W większych projektach i bardziej złożonych funkcjach zaleca się dodawania komentarza zaraz po definicji funkcji opisującego co funkcje robi, jakie przyjmuje parametry i co zwraca.
  • Podając wartość domyślną dla parametru nie stosuj spacji ani przed ani” po znaku =. Jeśli definiujesz kilka funkcji oddziel je dokładnie dwoma wierszami/liniami.

Więcej informacji odnośnie stylu pisania kodu znajdziesz w PEP 8 – Style Guide for Python Code.

Klasy i obiekty¶

Programowanie obiektowe to paradygmat programowania, w którym używa się obiektów, zdefiniowanych za pomocą stanu, zapisanego w atrybutach, oraz zachowania (realizowane za pomocą metod). Często programy napisane w Pythonie są zbiorem takich obiektów.

Obiekty są tworzone na podstawie definicji zwanej klasą. Klasa zawiera informacje, jakie właściwości powinien posiadać każdy egzemplarz tej klasy. Na podstawie jednej klasy można stworzyć wiele obiektów.

Żeby zdefiniować klasę należy posłużyć się słowem kluczowym class, po którym musi zostać podana nazwa klasy. Standardowo nazwy klas rozpoczyna się dużą literą. Jak wspomnieliśmy wcześniej, obiekty mają stan i zachowanie. Do definiowania zachowania służą metody. Metody to funkcje dostępne w ramach klasy. Definiuje się je za pomocą słowa kluczowego def, a jako pierwszy parametr zawsze trzeba podać self, co pozwala na używanie referencji do obiektu w ramach danej metody. Bardziej szczegółowy opis użycia parametru self zostanie przedstawiony później.

Pierwsza klasa i jej obiekty (egzemplarze)¶

W celu uzyskanie większej kontroli nad procesem tworzenia nowego obiektu, należy użyć konstruktora. Konstruktory to specjalne metody, które zostają wykonane zaraz po utworzeniu obiektu. Służą do nadania obiektom początkowego stanu. Żeby utworzyć konstruktor należy użyć specjalnej nazwy metody __init__. Poniżej znajduje się klasa Student z definicją konstruktora.

In [317]:
class Student():
  def __init__(self, name, surname, id):
    self.name = name
    self.surname = surname
    self.id = id

Oto egzemplarz klasy Student:

In [318]:
student_1 = Student(name='Jan', surname='Kiełbasa', id=123456)

Atrybuty:

In [319]:
print(student_1.name)
print(student_1.surname)
print(student_1.id)
Jan
Kiełbasa
123456

Dodajmy do klasy Student dwie metody, które posłużą nam do zmiany wartości atrybutów:

In [320]:
class Student():
  def __init__(self, name, surname, id):
    self.name = name
    self.surname = surname
    self.id = id

  def change_name(self, new_name):
    self.name = new_name

  def change_surname(self, new_surname):
    self.surname = new_surname    
In [321]:
student_1 = Student(name='Jan', surname='Kiełbasa', id=123456)
student_1.change_name('Robert')
print(student_1.name)
Robert

Następnei dodamy dwie kolejne metody get:

In [322]:
class Student():
  def __init__(self, name, surname, id):
    self.name = name
    self.surname = surname
    self.id = id

  def change_name(self, new_name):
    self.name = new_name

  def change_surname(self, new_surname):
    self.surname = new_surname    

  def get_name(self):
    print(f'Student(ka) ma na imię {self.name}.')    

  def get_surname(self):
    print(f'Student(ka) ma na nazwisko {self.surname}.')      
In [323]:
student_1 = Student(name='Jan', surname='Kiełbasa', id=123456)
student_2 = Student(name='Mariola', surname='Marchewka', id=789012)
In [324]:
student_1.get_name()
student_2.get_name()
student_2.get_surname()
student_2.change_surname('Pietrucha')
student_2.get_surname()
Student(ka) ma na imię Jan.
Student(ka) ma na imię Mariola.
Student(ka) ma na nazwisko Marchewka.
Student(ka) ma na nazwisko Pietrucha.

Metoda statyczna¶

Czasami istnieje potrzeba zdefiniowania jednej metody albo jednej wartości dla wszystkich obiektów danej klasy (a nie dla poszczególnych obiektów). Tego typu elementy nazywa się statycznymi. Możemy więc mówić o metodach statycznym oraz o polach statycznych. Żeby zdefiniować metodę statyczną, należy użyć dekoratora @staticmethod. Statyczne atrybuty klasy definiuje się w treści klasy.

Poniżej znajduje się fragment nowej wersji definicji klasy Student z wykorzystaniem metody statycznej status oraz z atrybutem statycznym total, które służą do zliczania liczby stworzonych obiektów klasy Student.

In [337]:
class Student():
  total = 0
  
  def __init__(self, name, surname, id):
    self.name = name
    self.surname = surname
    self.id = id
    Student.total += 1

  def change_name(self, new_name):
    self.name = new_name

  def change_surname(self, new_surname):
    self.surname = new_surname    

  def get_name(self):
    print(f'Student(ka) ma na imię {self.name}.')    

  def get_surname(self):
    print(f'Student(ka) ma na nazwisko {self.surname}.') 
    
  @staticmethod
  def status():
    print(f'Liczba utworzonych obiektów klasy Student: {Student.total}.')    
In [338]:
student_1 = Student(name='Jan', surname='Kiełbasa', id=123456)
student_2 = Student(name='Mariola', surname='Marchewka', id=789012)
In [339]:
Student.status()
Liczba utworzonych obiektów klasy Student: 2.

Dziedziczenie¶

Dziedziczenie pozwala odwołać się do metod innej klas, uważanej za bardziej ogólną. To odwołanie nazywa się dziedziczeniem (ang. inheritance). Poniżej zdefiniujemy klasę StudentExternal, która będzie dziedziczyć metody klasy Student.

In [353]:
class StudentExternal(Student):
    pass
In [354]:
student_external1 = StudentExternal('Nina', 'Kowalska', 4567896)
In [355]:
student_external1.get_name()
Student(ka) ma na imię Nina.

Dodamy do StudentExternal jeden nowy parametr study_field.

In [357]:
class StudentExternal(Student):
  def __init__(self, name, surname, id, study_field):
    super().__init__(name, surname, id)
    self.study_field = study_field
In [358]:
student_external1 = StudentExternal('Nina', 'Kowalska', 4567896, 'AI')
In [359]:
student_external1.get_surname()
Student(ka) ma na nazwisko Kowalska.
In [360]:
student_external1.study_field
Out[360]:
'AI'

Przeanalizuj poniższy kod. Klasa trójkątów równobocznych EquilateralTriangle dziedziczy po klasie Triangle.

In [366]:
class Triangle:
    def __init__(self, edge1, edge2, edge3):
        self.edge1 = edge1
        self.edge2 = edge2
        self.edge3 = edge3

    def perimeter(self):
        return self.edge1 + self.edge2 + self.edge3
    
class EquilateralTriangle(Triangle):
    def __init__(self, edge):
        super().__init__(edge, edge, edge)
            

triangle = Triangle(3, 4, 5)
print(f'Obwód trójkąta nierównobocznego: {triangle.perimeter()}.')
            
equilateral_triangle = EquilateralTriangle(3)
equilateral_triangle.perimeter()
print(f'Obwód trójkąta równobocznego: {equilateral_triangle.perimeter()}.')
Obwód trójkąta nierównobocznego: 12.
Obwód trójkąta równobocznego: 9.

Egzemplarze i atrybuty¶

Atrybutem klasy może być obiekt, jak to demostruje poniższy przykład.

In [367]:
class StudyField:
  def __init__(self, field_name, dean):
      self.field_name = field_name
      self.dean = dean
In [368]:
cog_science = StudyField('Cognitive Science', 'Marek Lechniak')
In [369]:
class StudentExternal(Student):
  def __init__(self, name, surname, id, field_name, dean):
    super().__init__(name, surname, id)
    self.study_field = StudyField(field_name, dean)
In [370]:
student_external1 = StudentExternal('Nina', 'Kowalska', 4567896, 'Cognitive Science', 'Marek Lechniak')
In [371]:
student_external1.study_field.dean
Out[371]:
'Marek Lechniak'

Styl i gracja¶

Nazwy klas powinny być w stylu PascalCase, tj. każde słowo w nazwie klasy powinno być zaczynać się dużą literą, a między słowami nie powinno być spacji.

Nazwy obiektów (aka egzemplarzy) klas powinny stosować się do tych samych zasad co nazwy funkcji (czyli powinny składać się z małych liter, a słowa powinny być połączone znakiem _).

Obiekty iterowalne i generatory¶

Obiekty iterowalne to obiekty, po których można iterować na przykład za pomocą pętli for. W Pythonie obiektami iterowalnymi są: napisy, listy, słowniki, zbiory, krotki oraz pliki. Liczby nie są. Zbadaj poniższy kod.

In [372]:
for letter in 'Ala ma kota':
  print(letter)
A
l
a
 
m
a
 
k
o
t
a
In [373]:
for number in 234:
  print(number)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [373], in <cell line: 1>()
----> 1 for number in 234:
      2   print(number)

TypeError: 'int' object is not iterable

Druga z pętli for zwróci błąd 'int' object is not iterable.

Iterator¶

Aby obiekt był iterowalny musi mieć zaimplementowany specjalny protokół, na który składają się:

  • metoda __iter__, która zwraca iterator i informuje Pythona, że obiekt jest iterowalny
  • metoda __next__, która musi być zdefiniowana na iteratorze; jej zadaniem jest przechwytywanie i zwracanie kolejnych wartości albo zakończenie iterowania za pomogą wyjątku StopIteration.

Poniższa klasa MyIterator wyjaśnia dziłanie iteratora.

In [385]:
class MyIterator():
  def __init__(self, data):
    print('[Konstruktor __init__ przyjmuje dane i inicjuje indeks.]')
    self.data = data
    self.index = 0
  
  def __iter__(self):
    print('[Metoda __iter__ informuje Pythona, że obiekt jest iterowalny.]')
    return self
  
  def __next__(self):
    data_len = len(self.data)
    if self.index >= data_len:
      print(f'[Metoda __next__: StopIteration; nie ma indeksu {self.index}]')
      raise StopIteration
    
    value = self.data[self.index]
    self.index += 1
    print(f'[Metoda __next__: przechwytuje aktualną wartość "{value}" z indeksu {self.index-1}/{data_len-1} i przechodzi do indeksu {self.index}.]')
    return value

my_iterator = MyIterator(['A', 'B', 'C'])

for item in my_iterator:
    print(f'Zwracany wynik: {item}')
[Konstruktor __init__ przyjmuje dane i inicjuje indeks.]
[Metoda __iter__ informuje Pythona, że obiekt jest iterowalny.]
[Metoda __next__: przechwytuje aktualną wartość "A" z indeksu 0/2 i przechodzi do indeksu 1.]
Zwracany wynik: A
[Metoda __next__: przechwytuje aktualną wartość "B" z indeksu 1/2 i przechodzi do indeksu 2.]
Zwracany wynik: B
[Metoda __next__: przechwytuje aktualną wartość "C" z indeksu 2/2 i przechodzi do indeksu 3.]
Zwracany wynik: C
[Metoda __next__: StopIteration; nie ma indeksu 3]

Generator¶

Generator jest funkcją, która zwraca iterator. Generator wygląda jak zwyczaja funkcja i musi zawierać przynajmniej jedno wyrażenie yield, którego zadaniem jest przechowywanie stanu w jakim się funkcja znajduje po wywołaniu i przy kolejnym wywołaniu kontynuowanie od tego właśnie miejsca.

Generatory automatycznie implementują __iter__, __next__ i StopIteration, więc nie musimy się tym sami zajmować.

Wywołanie generatora nie sprawia, że zaczyna się od razu wykonywać/iterować. Iterowanie odbywa się za pomocą next(). Po jednej iteracji generatu pauzuje i czeka na kolejne wywołanie (oczywiście zapamiętując stan w jakim się znajduje).

In [396]:
def my_generator(my_list):
    for i in range(len(my_list)):
        yield my_list[i]

for item in my_generator(['A', 'B', 'C']):
    print(f'Zwracany wynik: {item}')  
Zwracany wynik: A
Zwracany wynik: B
Zwracany wynik: C
In [399]:
my_gen = my_generator(['A', 'B', 'C'])

print(next(my_gen))
print(next(my_gen))
print(next(my_gen))
print(next(my_gen))
A
B
C
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [399], in <cell line: 6>()
      4 print(next(my_gen))
      5 print(next(my_gen))
----> 6 print(next(my_gen))

StopIteration: 

Generatory są bardzo pożyteczne, gdy chcemy uniknąć zaśmiecania pamięci komputera na przykład listami elementów, szczególnie, gdy te listy są bardzo duże. Poniższy kod pokazuje różnicę między listą my_list, która tworzy są w całości i zajmuje miejsce w pamięci i generatorem my_generator, który nie generuje od razu calej listy liczb od 0 do 4.

In [402]:
my_list = [x for x in range(5)]
my_generator = (x for x in range(5))

for i in my_generator:
    print(i)
0
1
2
3
4