Typy
Typy w Pythonie
Wiele języków programowania (np. Java, C, C++) wymagają podawania typu zmiennej, tzn. zdefiniowania z góry typu wartości w niej przechowywanej, na przykład:
- liczba całkowita (int od angielskiego integer),
- liczba niecałkowita (float od angielskiego floating point),
- znak (char od angielskiego character),
- napis (string),
- lista, krotka i wiele innych.
W Pythonie nie trzeba podawać typu zmiennych, a co więcej, ich typ można zmieniać w trakcie wykonywania programu. W pythonie poprawnym programem jest na przykład:
x = 12
y = "test"
y = x
z = x + y # `z` przyjmuje wartość 24
Taki kod nie jest możliwy do napisania np. w języku C++:
int x = 12;
std::string y = "test";
y = x; // Ta linijka spowoduje błąd, ponieważ typ `string` to co innego niż `int`
Możemy jednak (i nawet powinniśmy) dopisywać do zmiennych w Pythonie typy. Nie spowoduje to błędu w trakcie wykonywania programu, tak jak w C++ i operacja przypisania wartości niewłaściwego typu na zmienną nadal się powiedzie, jednak nasze środowisko programistyczne (np. PyCharm) poinformuje nas o błędnym przypisaniu. Pomoże to też podpowiadać środowisku odpowiednie funkcje, które dostępne są tylko dla niektórych typów (o funkcjach będzie później mowa tutaj).
W przypadku zmiennych, typy można dopisać w Pythonie po dwukropku:
x: int = 5
y: int = 3
z: int = 5 + 3
Typy proste
Python posiada bardzo wiele wbudowanych typów, jak również możliwość tworzenia własnych. Znajdująca się poniżej lista typów nie jest wyczerpująca, ale powinna w zupełności wystarczyć na poziomie matury z informatyki.
Napisy
Napisy są w Pythonie reprezentowane przez typ str
. Do poszczególnych znaków
napisu możemy dostać się tak samo jak do elementów listy (więcej o listach
poniżej):
napis: str = "Ala ma kota"
znak1: str = napis[0] # Wynikiem będzie "A"
znak2: str = napis[-2] # Wynikiem będzie "t"
ciag: str = napis[:3] # Wynikiem będzie "Ala"
Warto też znać funkcję split()
, którą można wykonać na napisie. Funkcja
split()
ma jeden opcjonalny argument, który oznacza znak, na którym ma zostać
podzielony napis - domyślnie jest to każdy biały znak (spacja, tabulator).
Wynikiem funkcji split jest lista napisów:
napis: str = "Ala ma kota"
podzielony1: list[str] = napis.split()
# Zmienna podzielony1 przyjmuje wartość:
# ["Ala", "ma", "kota"]
podzielony2: list[str] = napis.split("a")
# Zmienna podzielony2 przyjmuje wartość:
# ["Al", " m", " kot", ""]
Na napisach można też wykonywać niektóre operacje "arytemtyczne", na przykład:
napis1: str = "test "
napis2: str = "dodawania"
# To spowoduje wypisanie: "test dodawania"
print(napis1 + napis2)
# To spowoduje wypisanie: "test test test test "
print(napis1 * 4)
Napisy można także porównywać przy użyciu operatorów logicznych. Porównywanie napisów odbywa się zgodnie z porządkiem leksykograficznym, czyli:
- napisy porównywane są znak po znaku,
- litery
A-Z
są przeda-z
, - litery pojawiające się w alfabecie wcześniej, są mneijsze, czyli
a
jest mniejsze odb
.
Tak naprawdę napisy porównywane są zgodnie z kodem ASCII. Każdy znak (litera) ma przypisaną wartość liczbową i porównywanie napisów polega na porównaniu wartości liczbowej każdego znaku po kolei.
Liczby
Większość języków programowania dzieli liczby na dwie grupy:
- liczby całkowite (ang. integer) - w Pythonie jest to typ
int
- liczby zmienno przecinkowe (ang. floating-point albo double precision),
czyli niecałkowite, posiadające część dziesiętną - w Pythonie jest to typ
float
Na liczbach można wykonywać operacje, które zostały przedstawione wyżej, w
rozdziale o operacjach arytmetycznych. Dodatkowo warto wiedzieć, że istnieje
możliwość zamienienia liczby zapisanej w formie napisu, na jej faktyczną
wartość liczbową przy użyciu funkcji odpowiednio float()
oraz int()
. Pomoże
to zrozumieć poniższy przykład:
napis1: str = "5"
napis2: str = "5.0"
# Pomnóżmy teraz napisy przez 4 na dwa różne sposoby
print(napis1 * 4) # Wynik: 5555
print(int(napis1) * 4) # Wynik: 20
print(napis2 * 4) # Wynik: 5.05.05.05.0
print(float(napis2) * 4) # Wynik: 20.0
Zobaczmy też, co stanie się, jeśli do funkcji int()
podamy wartość, która nie
jest liczbą:
print(int("to nie jest liczba!"))
Jeśli powyższe polecenie wykonaliśmy w interpreterze, to powinien wypisać się błąd:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'to nie jest liczba'
Taki błąd w informatyce nazywamy wyjątkiem i więcej o wyjątkach pojawi się w rozdziale Wyjątki, a na razie wystarczy nam wiedzieć, że oznacza to, że nasz program napotkał sytuację, z którą nie mógł sobie poradzić (zamienienie zwykłego tekstu na liczbę) i przestał się wykonywać.
Niedokładność arytmetyki zmiennoprzecinkowej
Warto zaznaczyć przy tej okazji, że z powodu sposobu reprezentacji liczb
niecałkowitych w pamięci komputera, pojawiają się tu pewnie niedokładności.
Jeśli w pythonie wykonamy obliczenie 0.3-0.1
, to wynikiem będzie
0.19999999999999998
. Więcej o powodach tej niedokładności będzie mowa później
(tutaj), ale warto pamiętać, że
takie zjawisko zachodzi i może wpływać na wyniki naszych obliczeń.
Wartości logiczne
O wartościach logicznych była już mowa w przypadku operatorów logicznych.
Typ reprezentujący wartość logiczną nazywamy w Pythonie bool
i może przyjąć
wartość True
albo False
.
Warto tutaj zaznaczyć, że inne typy można rzutować na typ bool
(rzutować,
czyli w pewnym sensie zmieniać/konwertować ich typ). To, jak rzutowane są
wartości, możemy sprawdzić przy pomocy funkcji bool()
:
# W przypadku liczb, zero jest interpretowane jako False, a wszystkie inne
# wartości jako True
print(bool(0)) # Wynik: False
print(bool(1)) # Wynik: True
print(bool(-1)) # Wynik: True
# W przypadku napisów, puste napisy interpretowane są jako False, a wszystkie
# inne jako True:
print(bool("")) # Wynik: False
print(bool("napis")) # Wynik: True
# Podobnie listy:
print(bool([])) # Wynik: False
print(bool([1])) # Wynik: True
print(bool(["test"])) # Wynik: True
Warto o tym pamiętać, bo w programowaniu często wykorzystuje się takie rzutowanie.
Listy
Listy w większości języków programowania mogą przechowywać dowolnie wiele elementów, ale wszystkie elementy muszą być tego samego typu. Python nie ma takiego wymagania, ale lepiej nie definiować list, które mają elementy różnych typów, bo łatwo wtedy o błąd w programie.
Typ listy, jest pierwszym typem parametryzowanym, który poznamy. Typ parametryzowany, to taki typ, który jako argument przyjmuje inny typ. W przypadku listy ten typ będzie oznaczał, jakiego typu są elementy na liście. Spójrzmy na poniższy przykład:
# Tutaj mówimy, że lista1 jest typu list, ale nie podajemy, jakiego typu są jej
# elementy, co jest dopuszczalne, ale lepiej ten typ podawać
lista1: list = [1, 2, 3, 4]
# Tutaj definiujemy kilka list, które definiują typ elementu, który zawierają
lista2: list[int] = [1, 2, 3, 4]
lista3: list[str] = ["napis1", "napis2"]
lista4: list[bool] = [True, False, False]
# Co więcej możemy stworzyć listę list:
lista5: list[list[int]] = [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
Dostęp do elementów listy możemy uzyskać poprzez użycie nawiasów kwadratowych. Listy we wszystkich popularnych językach programowania są indeksowane od zera, co znaczy, że pierwszy element znajduje się pod indeksem 0:
lista: list[str] = ["pierwszy", "drugi", "trzeci"]
print(lista[0]) # Wynik: "pierwszy"
print(lista[1]) # Wynik: "drugi"
print(lista[2]) # Wynik: "trzeci"
Próba pobrania elementu o indeksie, który nie znajduje się na liście, zakończy
się błędem. Na przykład gdybyśmy w powyższym przykładzie wykonali polecenie
print(lista[3])
w interpreterze, to powinien wypisać się błąd:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
Dozwolone jest natomiast korzystanie z indeksów ujemnych, które interpretowane są jako liczba elementów licząc od końca listy:
lista: list[str] = ["pierwszy", "drugi", "trzeci"]
print(lista[-1]) # Wynik: "trzeci"
print(lista[-2]) # Wynik: "drugi"
print(lista[-3]) # Wynik: "pierwszy"
Na listach można wykonywać różne operacje (pełna lista tutaj), a najważniejsze z nich to:
list.append(elem)
- dopisanie na koniec nowego elementuelem
list.insert(i, elem)
- wstawienieelem
nai
-ty indekslist.pop(i)
lublist.pop()
- usunięcie i zwróceniei
-tego elementu z listy, jeśli nie podamy indeksui
to usunięty zostanie ostatni elementlist.sort()
- posortowanie elementów listylen(list)
- sprawdzenie długości listy
Typy zaawansowane
Znajomość tych typów nie jest konieczna na poziomie matury, ale często bardzo ułatwiają one zadanie, więc warto umieć z nich korzystać.
Krotki
Krotki (tuple
) są strukturą danych, która do pewnego stopnia przypomina listy,
ale ma z góry określoną liczbę elementów i zwyczajowo może przechowywać elementy
różnych typów.
Załóżmy, że chcemy mieć listę punktów w układzie współrzędnych. Moglibyśmy przechowywać ją jako listę list w następujący sposób:
punkty: list[list[int]] = [[0, 0], [1, 1], [-1, -1]]
# Dostęp do współrzędnych drugiego punktu:
x = punkty[1][0] # x = 1
y = punkty[1][1] # y = 1
Nie jest to najgorsze rozwiązanie, ale żeby uzyskać obie współrzędne punktu,
potrzebujemy aż dwie linijki kodu. Dodatkowo IDE nie ostrzeże nas, jeśli
przez przypadek do listy punkty
dodamy element [0]
- listę jednoelementową,
która spowoduje błąd w trakcie wykonania programu.
Spójrzmy jak wyglądałoby rozwiązanie tego problemu przy pomocy krotki dwuelementowej, czyli pary:
punkty: list[tuple[int, int]] = [(0, 0), (1, 1), (-1, -1)]
# Dostęp do współrzędnych drugiego punktu, krotka zostaje rozpakowana:
(x, y) = punkty[1] # x = 1, y = 1
Krotki mogą oczywiście zawierać dowolnie wiele elementów:
para: tuple[int, int] = (12, 13)
trojka: tuple[int, str, str] = (1, "Anna", "Kowalska")
czworka: tuple[int, str, str, float] = (12, "al. Jerozolimskie", "Warszawa", 15.4)
Zbiory
Zbiór jest kolejną strukturą danych podobną do listy, ale z jedną znaczącą różnicą - elementy na liście znajdują się w określonej kolejności, a zbiór jest raczej workiem, w którym znajdują się wartości, ale bez ustalonego porządku. Co więcej, na liście ten sam element może znajdować się wielokrotnie, a w zbiorze każdy element występuje tylko raz.
zbior1: set[str] = {"jabłko", "banan", "marchewka"}
zbior2: set[str] = {"jabłko", "banan", "marchewka", "jabłko"}
# Oba te zbiory zawierają te same elementy:
print(zbior1) # Wynik: {'marchewka', 'banan', 'jabłko'}
print(zbior2) # Wynik: {'marchewka', 'banan', 'jabłko'}
Na zbiorach można wykonywać funkcje podobne do tych na listach:
set.add(elem)
- wstawienie elementuset.remove(elem)
- usunięcie elementu- operator
in
- sprawdzenie czy element jest w zbiorze len(set)
- sprawdzenie liczby elementów w zbiorze
Przykład użycia tych funkcji:
zbior: set[int] = {2, 3, 5, 7}
print(zbior) # Wynik: {2, 3, 5, 7}
zbior.add(11)
zbior.add(13)
print(zbior) # Wynik: {2, 3, 5, 7, 11, 13}
zbior.remove(2)
print(zbior) # Wynik: {3, 5, 7, 11, 13}
czyZawiera5 = 5 in zbior # Wynik: True
rozmiar = len(zbior) # Wynik: 5
Na zbiorach można też wykonywać operacje znane nam z matematyki:
a | b
- suma, wszystkie elementy z a i ba ^ b
- suma wszystkie elementy z a i b, które nie są w obua - b
- różnica, elementy w a, ale nie w ba & b
- przecięcie, elementy w a i b jednocześnie
Słowniki
Słownik, tak samo jak w rzeczywistości, to zbiór par. Pierwszy element takiej pary nazywamy kluczem (ang. key), a drugi wartością (ang. value). Dostęp do elementów odbywa się przy pomocy nawiasów kwadratowych, podobnie jak w przypadku listy. Zobaczmy działanie zbiorów na przykładzie:
ceny: dict[str, float] = {
"czekolada": 4.99,
"woda": 1.89,
"sok": 2.99
}
# Sprawdzenie ceny produktu
cena: float = ceny["woda"] # cena = 1.89
# Dodanie nowej ceny
ceny["masło"] = 6.99
# Zaaktualizowanie ceny
ceny["woda"] = 1.99
# Usunięcie elementu ze słownika
del ceny["czekolada"]
# Wypisanie słownika
print(ceny) # Wynik: {'woda': 1.99, 'sok': 2.99, 'masło': 6.99}
# Sprawdzenie czy klucz jest w słowniku
zawieraWode: bool = "woda" in ceny # Wynik: False
Podobnie jak w przypadku zbiorów, każdy klucz może pojawić się w słowniku tylko raz. Przypisanie wartości na istniejący klucz zakończy się powodzeniem, ale nadpisze wcześniej zapisaną tam wartość.
Sprawdzanie typu zmiennej
Może zdarzać się tak, że w programach, które będziemy pisali, pojawi się błąd
związany z niewłaściwym typem pewnej zmiennej. Przydać może nam się wtedy
funkcja type()
, która zwraca nazwę typu zmiennej jako napis. Przykładowo
możemy z niej skorzystać tak:
a = 1
b = "test"
c = (a, b)
d = [1, 2, 3]
print(type(a)) # Wynik: <class 'int'>
print(type(b)) # Wynik: <class 'str'>
print(type(c)) # Wynik: <class 'tuple'>
print(type(d)) # Wynik: <class 'list'>