01 - Podstawy języka Python

Podstawy przetwarzania danych

Politechnika Poznańska, Instytut Robotyki i Inteligencji Maszynowej

Ćwiczenie laboratoryjne 1: operacje wejścia/wyjścia w języku Python; typy danych i podstawowe operacje matematyczne

Powrót do spisu treści ćwiczeń laboratoryjnych

O języku

Python to język programowania wysokiego poziomu ogólnego przeznaczenia, o rozbudowanym pakiecie bibliotek standardowych, którego ideą przewodnią jest czytelność i klarowność kodu źródłowego. Jego składnia cechuje się przejrzystością i zwięzłością.

Python wspiera różne paradygmaty programowania: obiektowy, imperatywny oraz w mniejszym stopniu funkcyjny. Posiada w pełni dynamiczny system typów i automatyczne zarządzanie pamięcią, będąc w tym podobnym do języków Perl, Ruby, Scheme czy Tcl. Podobnie jak inne języki dynamiczne jest często używany jako język skryptowy. Interpretery Pythona są dostępne na wiele systemów operacyjnych.

Python rozwijany jest jako projekt Open Source zarządzany przez Python Software Foundation, która jest organizacją non-profit. Standardową implementacją języka jest CPython (napisany w C), ale istnieją też inne (źródło: Wikipedia).

Cechą szczególną języka jest interpretacja znaków białych. Inaczej niż w przypadku większości pozostałych języków programowania, mają one ogromne znaczenie i odpowiadają m.in. za przynależność pojedynczej linii lub bloku kodu do danej klasy, czy instrukcji sterującej.

Funkcjonalność języka może zostać rozszerzona poprzez użycie darmowych bibliotek. Ich repozytorium jest dostępne pod adresem https://pypi.org, a instalację umożliwia narzędzie konsolowe pip domyślnie instalowane wraz z interpreterem.

Dostępna jest darmowa dokumentacja języka.

Zmienne

Definiowanie zmiennych

Język Python, podobnie jak na przykład język MATLAB, charakteryzuje się dynamicznym typowaniem zmiennych. Oznacza to, że po przypisaniu wartości do zmiennej, Python sam określi jaki typ będzie dla niej odpowiedni.

Do zmiennej musi być przypisana wartość w momencie tworzenia - nie możemy zadeklarować samej nazwy “na później”. Linijka:

new_variable

skutkuje następującym błędem (tzw. wyjątkiem):

NameError: name 'new_variable' is not defined

Mimo że zmienne nie mają jawnie zadeklarowanych typów, wartości w nich przechowywane już tak, i posiadają one swoje właściwości czy funkcje. Obowiązuje tak zwane silne typowanie. Dla przykładu: typy liczbowe nie zostaną automatycznie zamienione na tekst i na odwrót. Możliwe jest tego typu działanie:

variable_int_1 = 10
variable_int_2 = 20
variable_int_1 + variable_int_2

natomiast poniższy kod nie wykona się ze względu na niezgodność typów:

variable_int_1 = 10
variable_string_1 = "abc"
variable_int_1 + variable_string_1

Typ zmiennej możemy zawsze sprawdzić przy pomocy wbudowanej funkcji type():

print(type(42))
print(type("abc"))

Definiując zmienne należy pamiętać o kilku zasadach dotyczących ich nazw:


💥 Zadanie 1 💥

Uruchom konsolę języka Python. Jest ona dostępna na przykład w środowisku PyCharm po utworzeniu projektu (ikona węży w panelu w lewym dolnym narożniku).

Wykonaj poniższe komendy w konsoli Pythona. Sprawdź jakie typy zmiennych zostały do nich przypisane.

 a = 10
 b = "Tekst"  # cudzysłów pojedynczy (') oraz podwójny (") działają tak samo

Konwersje typów

Programista ma możliwość skorzystania z mechanizmu konwersji typów zmiennych. Działa to podobnie jak w przypadku rzutowania znanego z języków C i C++. Operacja ta realizowana jest jednak odmiennie, bo za pośrednictwem dedykowanych funkcji str(), float() oraz int().


💥 Zadanie 2 💥

Wpisz w konsoli kod c = str(a). Jakiego typu jest zmienna c?


Stałe

Wiele języków programowania pozwala na definiowanie stałych, czyli wartości lub struktur, które w ogólności nie ulegają, a przynajmniej nie powinny ulegać zmianie w trakcie działania programu.

Przykładem stałej może być liczba \(Pi\). Python nie pozwala na tworzenie stałych, znanych np. z języka C, czy C++. Przyjęta została jednak konwencja, że nazwy wszystkich niezmiennych wartości zapisywane są wersalikami (wielkimi literami). Nie zmienia ona faktu, że wartości te mogą zostać zmodyfikowane, ale jednocześnie jasno sugeruje, żeby tego nie robić.

 PI = 3.14159265359
 MOUNT_EVEREST = 8849

Zasady tworzenia nazw dla stałych są identyczne jak w przypadku zmiennych.

Operacje na liczbach

Python pozwala operować na danych liczbowych, podobnie jak ma to miejsce w innych językach wysokiego poziomu. Do dyspozycji programisty pozostawiono zbiór operatorów, przedstawiony w tabeli poniżej. Za ich pomocą można wykonywać operacje zarówno na liczbach całkowitych jak i zmiennoprzecinkowych.

Operator Działanie
+ Dodawanie
- Odejmowanie
* Mnożenie
/ Dzielenie
% Reszta z dzielenia
// Dzielenie całkowite
** Potęgowanie

Szczególną uwagę należy zwrócić na operatory dzielenia. O ile typ zmiennej, do której zwracany będzie wynik, zwyczajowo jest zależny od operandów i wyniku samej operacji, o tyle w przypadku dzielenia przy użyciu / wynik zawsze będzie zmienną zmiennoprzecinkową. Efekt ten nie zawsze jest pożądany, stąd twórcy dodali drugi operator dzielenia - //. Wartość zwracana za jego pośrednictwem jest zawsze całkowita. Cześć liczby znajdująca się po przecinku jest pomijana, niezależnie od jej wartości.


💥 Zadanie 3 💥

Przetestuj działanie każdego z operatorów zawartych w tabeli wykorzystując zmienne zdefiniowane poniżej. Porównaj wyniki operacji x = e / f oraz y = e // f.

d = 77
e = 10
f = 0.10001

Operacje na ciągach znaków

Ciągi znakowe w języku Python muszą zostać umieszczone w ramach odpowiednich znaczników. Do dyspozycji są trzy różne kombinacje:

Długość tekstu zwraca funkcja len(). Ma ona zastosowanie zarówno w stosunku do zmiennych tekstowych jak i do samych ciągów:

my_text = 'Test' 
len(my_text)
len('Test')

Ciągi znakowe można ze sobą łączyć, tworząc dłuższe wyrażenia. Służy do tego operator +. W ciągach tekstowych można także umieszczać liczby. Niezbędna jest wtedy stosowna konwersja typów:

my_text_with_number = my_text + ' numer ' + str(e)

Możliwe jest także odwołanie się do pojedynczego znaku przy użyciu operatora []. Należy pamiętać, że ciąg znaków jest, podobnie jak w większości języków programowania, numerowany od 0.

my_text = 'Test'
print(my_text[0] + my_text[1] + my_text[2] + my_text[3])

Do podziału ciągu na podciągi służy metoda split(). Jako argument przyjmuje ona separator, czyli frazę, względem której ma nastąpić podział:

j = 'Test_1;Test_2'
k = j.split(';')
print(k)

💥 Zadanie 4 💥

Przeanalizuj wynik wywołania dla powyższego wyrażenia.


Obsługa wejścia/wyjścia

Konsola interpretera Python pozwala na interakcję z użytkownikiem. Możliwe jest wczytanie danych podanych przez użytkownika oraz wypisanie danych na ekranie. Służą do tego odpowiednio funkcje input() oraz print().

Funkcja input() przyjmuje jako argument tekst, który zostanie wyświetlony na ekranie, jeszcze przed pobraniem wejścia od użytkownika. Zwracana jest zmienna typu tekstowego. Przed wykonaniem operacji arytmetycznych należy pamiętać, aby dokonać konwersji na właściwy typ.

first_number = input('Podaj pierwszą liczbę: ')
second_number = int(input('Podaj drugą liczbę: '))

Funkcja print() wypisuje na ekranie zadany tekst. Nie ma konieczności stosowania ciągów formatujących - poszczególne części tekstu są łączone operatorem +.

first_number = int(first_number)
print('Suma liczb: ' + str(first_number + second_number))

💥 Zadanie 5 💥

Przetestuj działanie powyższego kodu. Zmodyfikuj go tak, aby wczytane zostały dwa ciągi tekstowe. Następnie połącz je w jedno wyrażenie i wyświetl.


Skrypty

Wykonywanie poleceń z poziomu konsoli interpretera ogranicza się w praktyce do pojedynczych komend. Dużo lepszym rozwiązaniem jest użycie skryptów. W przypadku Pythona, są to pliki z rozszerzeniem *.py. Wraz z pierwszym projektem, utworzony został plik skryptowy main.py.

Usuń zawartość pliku main.py jeśli nie jest on pusty. Nie będzie ona przydatna na tych i kolejnych zajęciach.


💥 Zadanie 6 💥

Przetestuj działanie kodu z podrozdziału Obsługa wejścia/wyjścia.


Debugger

Często, szczególnie w przypadku większych projektów, zachodzi potrzeba znalezienia błędów, które nie powodują błędu krytycznego programu, ale wpływają na wynik obliczeń. Do tego celu idealnie nadaje się debugger wbudowany w narzędzie PyCharm. Klikając pomiędzy numerem linii a obszarem roboczym, użytkownik może dodać punkt zatrzymania (tzn. break point). Gdy interpreter dotrze do wykonywania danej linii kodu, zostanie on zatrzymany, a na ekranie pojawią się dodatkowe informacje diagnostyczne, takie jak na przykład obecna wartość zmiennych przechowywanych w pamięci. Aby uruchomić tryb debugowania, można użyć skrótu klawiszowego SHIFT+F9 lub kliknąć ikonę z robakiem. Tryb ten będziemy używać na zajęciach tylko okazjonalnie, ale warto mieć świadomość jego istnienia.


💥 Zadanie 7 💥

Napisz program, który wczyta od użytkowników dwie liczby (podstawę oraz wykładnik potęgi), wykona operację potęgowania i wypisze wynik na ekranie. Przetestuj działanie debuggera.

💥 Zadanie 8 💥

Napisz program, który wczyta od użytkownika dwie liczby, podzieli je i wypisze na ekranie wartość całkowitą oraz resztę z dzielenia.


Komentarze

Przygotowując skrypt, nierzadko konieczne jest opisanie funkcjonalności poszczególnych fragmentów lub bloków kodu. Jest to czynność szczególnie istotna, gdy do kodu powraca się po dłuższej przerwie lub udostępnia się go innym osobom. Komentowanie treści, przydaje się także w trakcie poszukiwania błędów w kodzie, dzieląc kod na mniejsze fragmenty. Komentarze powinny używać języka angielskiego.

Należy przy tym pamiętać, że priorytetem jest odpowiednie nazewnictwo zmiennych oraz funkcji. Dzięki nazwom, które dobrze oddają to, co dana zmienna przechowuje lub dana funkcja robi, kod w dużej mierze komentuje się sam. Dodatkowe komentarze wymagane są wtedy głównie w wyjątkowo skomplikowanych lub nietypowych miejscach.

W języku Python dostępne są dwa podstawowe typu komentarzy – jednoliniowe oraz wieloliniowe. Pierwsze z nich, jak sama nazwa wskazuje, służą do komentowania pojedynczej linii kodu lub do dopisania komentarza za kodem właściwym. Komentarz taki tworzy się za pomocą znacznika #.

# the value of 5 will be assigned to the xyz variable below
xyz = 5  # the assignment happens here

Druga możliwość to dodanie komentarza blokowego, który obejmuje jedną lub wiele linii kodu. Służą do tego znaczniki """ i muszą one zostać użyte dwukrotnie, aby rozpocząć blok oraz go zamknąć.

"""
Kod opracował:
Jan Kowalski
100100
"""
xyz = 5

W ramach pojedynczego skryptu można używać obu typów komentarzy.

Podstawowe struktury danych

Twórcy języka Python przewidzieli cztery podstawowe struktury danych. Są nimi listy, krotki, zbiory oraz słowniki. Każda ze struktur posiada swoją specyfikę oraz przeznaczenie.

Listy służą do przechowywania dużej liczby pojedynczych wartości. Kolejność elementów na liście może być zmieniana po jej utworzeniu. Dopuszczalne są także duplikaty wartości. Dostęp do pojedynczego elementu uzyskuje się za pośrednictwem nawiasów kwadratowych. Elementy są numerowane od zera. Na potrzeby dodawania oraz usuwania elementów, zdefiniowane zostały odpowiednio metody append() oraz remove(). Długość listy można uzyskać za pośrednictwem funkcji len().

my_list = ['A', 'B', 'C']
my_list[2] = 'X'
my_list.append('D')
my_list.remove('A')
print(my_list[0])
print(len(my_list))

Krotki, podobnie jak listy, służą do przechowywania zbioru pojedynczych wartości. Podstawową różnicą jest fakt, że ich bezpośrednia zawartość nie może zostać zmieniona po ich utworzeniu. W praktyce oznacza to, że niemożliwe jest użycie operatora przypisania (poskutkuje błędem). Dostęp do pojedynczego elementu uzyskuje się za pośrednictwem nawiasów kwadratowych. Elementy są numerowane od 0. Możliwe jest pobranie wartości wskazującej na długość krotki, za pośrednictwem funkcji len().

my_tuple = ('A', 'B', 'C')
print(my_tuple[0])
print(len(my_tuple))

Zbiory stanowią kolekcje, służące do przechowywania unikalnych wartości. Wartości te nie posiadają zdefiniowanej kolejności wystąpienia, a tym samym nie ma możliwości pobrania konkretnej wartości. Co więcej, kilkukrotne wypisanie zawartości zbioru, może skutkować inną kolejnością elementów przy każdym wywołaniu. Dodawanie oraz usuwanie elementów odbywa się przy pomocy metod add() oraz remove(). Rozmiar zbioru można pobrać przy użyciu funkcji len().

my_set = {'A', 'B', 'C'}
my_set.add('D')
my_set.remove('A')
print(my_set)
print(len(my_set))

Dostęp do elementów zbioru odbywa się za pośrednictwem pętli for.

Ostatnią strukturą są słowniki. Podobnie jak zbiory, służą one do przechowywania unikalnych elementów, przy czym są to pary postaci klucz:wartość, gdzie tylko klucz musi być unikalny w ramach danego słownika. Dostęp odbywa się za pośrednictwem klucza podanego w nawiasach kwadratowych. Możliwe jest dodawanie i usuwanie elementów oraz zmiana ich wartości. Bardzo często kluczem jest ciąg tekstowy, ale obsługiwane są również inne typy.

my_dict = {'el_A': 'A', 'el_B': 'B', 'el_C': 'C'}
my_dict['el_D'] = 'D'
del my_dict['el_A']
print(my_dict)
print(my_dict['el_B'])
print(len(my_dict))

💥 Zadanie 9 💥

Przetestuj każdą z omówionych struktur.


Instrukcje sterujące

Instrukcja warunkowa if

Instrukcje warunkowe pozwalają na kontrolę procesu wykonywania kodu źródłowego. Umożliwiają one wskazanie, które fragmenty kodu powinny zostać wykonane w zależności od spełnienia (lub nie) warunków wejściowych. Każda instrukcja warunkowa rozpoczyna się słowem kluczowym if. Za słowem if zawsze musi pojawić się warunek do sprawdzenia oraz znak :. Można tu wykorzystać operatory porównania oraz - w celu budowy bardziej złożonych warunków - operatory logiczne.

Operatory porównania
Operator Działanie
== Równy
!= Różny
> Większy
< Mniejszy
>= Większy lub równy
<= Mniejszy lub równy
Operatory logiczne
Operator Działanie
and Iloczyn logiczny
or Suma logiczna
not Negacja

Instrukcja warunkowa może być bez alternatywy i składać się tylko ze słowa kluczowego if. Należy pamiętać, że głębokość wcięcia tekstu wskazuje przynależność danego wyrażenia lub bloku tekstu do danej klauzuli (białe znaki mają tu znaczenie). Instrukcja if opcjonalnie może zawierać alternatywę, definiowaną przy użyciu słowa elif oraz else. Poniższy przykład ilustruje zasadę działania instrukcji warunkowej dla przypadku dzielenia przez 0.

# poprawny kod
a = 5
b = 0
if b == 0:
    print('Division by 0 error!')
else:
    c = a / b
    print(c)
# niepoprawny kod
a = 5
b = 0
if b == 0:
print('Division by 0 error!')
else:
c = a / b
print(c)

💥 Zadanie 10 💥

Przygotuj kod instrukcji warunkowej, która sprawdzi czy wartość zmiennej a jest większa od 100, mniejsza od 100, czy równa 100. Zwrócona powinna zostać informacja w formie tekstowej (np. 'Liczba jest mniejsza niż 100.').


Pętla while

Przetwarzanie dużej ilości danych wymusza konieczność użycia dedykowanych instrukcji danego języka programowania. Pętle pozwalają na wykonanie danego bloku kodu wielokrotnie, bez konieczności jego zwielokrotniania w pliku źródłowym.

Pętla while to pętla, która jest wykonywana dopóki dany warunek jest spełniony (logiczna prawda). Stosuje się ją, gdy nie jest znana liczba iteracji (powtórzeń) do wykonania. Takimi przypadkami są m.in. wczytywanie liczb od użytkownika, do czasu podania konkretnej wartości, czy losowanie liczb, do czasu uzyskania wartości oczekiwanej.

Pętlę tę definiuje się za pomocą słowa kluczowego while. Następnie należy podać warunek, który jest sprawdzany przy każdym obiegu pętli. Warunek musi zostać zakończony znakiem :.

Poniższy kod ukazuje przykładowe zastosowanie pętli while.

value = 1
while value > 0:
    value = int(input('Enter number: '))
    print('Received number ' + str(value))

💥 Zadanie 11 💥

Napisz program, który będzie wczytywał kolejne liczby podawane przez użytkownika dopóki ich suma będzie mniejsza niż 1000. Program powinien na bieżąco informować o ostatnim wyniku sumowania.


Pętla for

Pętlę for definiuje się przy użyciu słów kluczowych for oraz in. Za słowem for należy umieścić nazwę iterowalnej zmiennej, kolejno słowo in i strukturę, która ma zostać poddana iteracji. Należy też pamiętać o znaku :. Do zmiennych iterowalnych można zaliczyć wspomniane wcześniej struktury: listę, krotkę, zbiór oraz słownik. Możliwe jest także użycie funkcji range(). Przyjmuje ona następujące argumenty: wartość początkową, wartość końcową, krok. Warto zaznaczyć, że mamy tutaj do czynienia z przedziałem otwartym tzn. wywołanie range(2,6,1) wykona pętle dla wartości 2, 3, 4 i 5. Jedynie pierwszy argument range() jest obowiązkowy. Istnieją w związku z tym alternatywne wywołania tej funkcji:

W ciele każdej pętli można zagnieżdżać inne pętle, instrukcje warunkowe oraz definiować zmienne. Możliwe jest przygotowanie np. skryptu sumującego liczby parzyste z zadanego przedziału, co przedstawiono w przykładzie poniżej.

sum_of_elements = 0 
for element in range(1, 101):
    if element % 2 == 0:
        sum_of_elements = sum_of_elements + element # one can also use sum_of_elements += element
print(sum_of_elements)

💥 Zadanie 12 💥

Napisz program, który znajdzie i wypisze liczby pierwsze z zakresu od 1 do b, gdzie b to wartość nie mniejsza niż 1000.

💥 Zadanie 13 💥

Napisz program, który wczyta od użytkownika tekst i sprawdzi, czy jest on palindromem.


Funkcje

Podstawową strukturą, która umożliwia uporządkowanie i ograniczenie rozmiaru kodu są funkcje. Funkcja pełni rolę bloku wykonawczego, który realizuje pewne, specyficzne zadanie. Zaletami takiego podejścia są przede wszystkim częściowa niezależność od pozostałej części kodu oraz możliwość wielokrotnego wykonania tego samego fragmentu kodu, bez konieczności jego duplikowania. Deklaracja funkcji składa się ze słowa kluczowego def, nazwy własnej funkcji (zgodnie z zasadą deklaracji zmiennych) oraz argumentów, o ile są one potrzebne. Co więcej, argumenty mogą być obowiązkowe lub opcjonalne.

def function_a():  # funkcja bez argumentów wejściowych
    return True
  
def function_b(a):  # funkcja z jednym, obowiązkowym argumentem wejściowym
    return a * a
  
def function_c(a, b=1):  # funkcja z dwoma argumentami wejściowymi, w tym z jednym opcjonalnym
    return a * b

Wartość wyjściowa funkcji może zostać zwrócona za pośrednictwem słowa kluczowego return. Poniżej przedstawiony został przykład, który obrazuje zasadę konstruowania i działania funkcji. Oba bloki kodu realizują to samo zadanie, przy czym drugi z nich został zdefiniowany z użyciem funkcji.

value_a = 5
value_b = 8
result_a = value_a + value_b * value_a

value_a = 7
value_b = 9
result_b = value_a + value_b * value_a
def my_function(a, b):
    return a + b * a

result_a = my_function(5, 8)
result_b = my_function(7, 9)

Funkcje w języku Python mogą zwracać kilka wartości. W rzeczywistości zwracana jest wtedy krotka (tuple), która następnie jest rozpakowywana do kilku zmiennych:

def my_function_that_returns_multiple_values():
    return 'my text', 42

some_text, some_number = my_function_that_returns_multiple_values()

Nazwy funkcji powinny jednoznacznie opisywać co dana funkcja robi (albo raczej: ma zrobić) i używać języka angielskiego. W związku z tym, że funkcje wykonują pewne zadania, ich nazwy zazwyczaj powinny zawierać czasownik. Przykłady dobrych nazw funkcji to add_numbers, sum_values, load_data, read_text. Do kiepskich nazw funkcji można, co ciekawe, zaliczyć funkcję wbudowaną input() - brakuje jej czasownika informującego o wykonywanej czynności.

Szablon skryptu w języku Python

O ile w plikach źródłowych języka Python kod można pisać bez jakiegokolwiek podziału na funkcje, zalecane jest korzystanie z następującego szablonu:

def main():
    print('Here goes my code')


if __name__ == '__main__':
    main()

Kod powinien znajdować się w funkcji main(). Jej nazwa jest tylko konwencją i można ją zmieniać (choć pewnie nie warto).

Tajemniczo wyglądający warunek wywołania tej funkcji zyska więcej sensu w przyszłości. Na ten moment warto pamiętać, że w odróżnieniu od wielu innych języków funkcja main() nie zostanie wywołana automatycznie i dlatego jej wywołanie pojawia się w kodzie.

Poniżej znajduje się przykład nieco bardziej skomplikowanego pliku źródłowego języka Python:

def main():
    first_user_number = load_number()
    second_user_number = load_number()

    print(sum_numbers(first_user_number, second_user_number))

def load_number():
    return int(input("Enter a number: "))

def sum_numbers(a, b):
    return a + b


if __name__ == '__main__':
    main()

Zadania dodatkowe

💥 Zadanie dodatkowe 1 💥

Napisz program, który wczyta od użytkownika dwa teksty i w osobnej funkcji sprawdzi, czy są one anagramami.

💥 Zadanie dodatkowe 2 💥

Napisz program, który wczyta od użytkownika liczbę n i obliczy wartość silni \(n!\). Wyniki pośrednie zapisz na liście, której zawartość zostanie następnie wyświetlona wraz z końcowym wynikiem obliczeń. Obliczenie silni powinno zostać wykonane w funkcji, która powinna zwrócić zarówno wspomnianą listę, jak i obliczoną wartość do kodu wywołującego.