W niniejszym rozdziale rozszerzamy wiedzę o typach danych dostępnych w Pythonie. Niezbędna jest znajomość typów danych, przynajmniej w zakresie opisanym w rozdziale 2., oraz ich realizacji w Pythonie, co zostało objaśnione w podrozdziale 5.3. Przyda się również doświadczenie w pracy z konsolą Pythona oraz umiejętność tworzenia plików źródłowych z elementarnymi ciągami instrukcji Pythona.
W Pythonie 3 wewnętrznie teksty przechowywane są jako ciągi znaków Unicode.
Obowiązującym standardem kodowania znaków w tekście programów jest UTF-8.
Można użyć każdego innego kodowania, o ile zostanie opisane dyrektywą postaci # coding: nazwa
w pierwszym wierszu pliku źródłowego.
# nie określono standardu kodowania, przyjmuje się więc że jest to utf-8 # rozumieją nas wszystkie urządzenia, niezależnie od zaprogramowanego kodowania imie = 'Łukasz' nazwisko = 'Świątek' print('Nazywam się ' + imie + ' ' + nazwisko)
W pliku źródłowym standard kodowania podaje się w komentarzu na początku pliku. Po tym zabiegu w napisach i w komentarzach można umieszczać znaki w opisanym kodowaniu (w przykładach niżej jest to UTF-8):
# coding: utf-8 # w tym pliku używany jest standard kodowania utf-8; jest to zadeklarowane wyżej imie = 'Łukasz' nazwisko = 'Świątek' print('Nazywam się ' + imie + ' ' + nazwisko)
W pliku źródłowym programu napisanego w Pythonie 2, jeżeli nie podano standardu kodowania znaków, to mógł on zawierać wyłącznie znaki ASCII (ściślej: zestawu Latin-1). Dotyczyło to także komentarzy i zawartości napisów.
W języku tym rozróżniano napisy typu str
i napisy typu unicode
.
Jedne z nich były ciągami znaków alfabetu opartego na kodowaniu jednobajtowym, a drugie —
ciągami znaków Unikodu
. Było to przejściowe rozwiązanie
spowodowane stopniowym wdrażaniem Unikodu
. W Pythonie 3
istnieją jedynie napisy unikodowe, a typ danych nazywa się str
.
Komunikacja z użytkownikiem przebiega w oparciu o kodowanie zgodne z trybem pracy urządzenia. Np. teksty na terminal są wyprowadzane w kodowaniu używanym przez ten terminal; podobnie jest z oknami dialogowymi i z plikami.
Środowisko automatycznie dopasowuje kodowanie napisów unikodowych do urządzeń wyjściowych (terminale tekstowe, pliki tekstowe z danymi). Jeśli występuje konflikt lub brak możliwości rozpoznania trybu, obserwujemy nieoczekiwane zakłócenia w prezentowanym tekście (tzw. „krzaki”, czyli znaki o numerach wyznaczonych dla innego kodowania).
Python 3 posiada także standardowy typ bytes
,
przeznaczony do przechowywania ciągów bajtów. Przypomina on pod pewnymi
względami typ str
znany z wcześniejszych wersji języka,
jednak winien on być używany tylko wtedy, kiedy istotna jest forma tekstu,
a nie jego znaczenie.
Kod informacyjny określa, jaki znak tekstowy odpowiada danemu numerowi porządkowemu.
Do znalezienia numeru danego znaku służy w Pythonie funkcja ord(znak)
.
ord('a') 97 ord('0') 48 ord('A') 65 ord(';') 59 ord('ł') # 'ł' jest kodowane dwoma bajtami w utf-8 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ord() expected a character, but string of length 2 found ord(u'ł') # ze znakiem Unicode się uda... 322 ord('\t') 9
Do znalezienia znaku o danym kodzie służy w Pythonie funkcja
chr(numer)
(w Pythonie 2 były dwie funkcje:
chr(numer)
oraz unichr(numer)
dla dwóch różnych typów napisów).
Obsługuje ona wszystkie znaki UNICODE;
obecnie (UNICODE 12.0 w roku 2020) jest ich ponad 150000.
chr(65) 'A' chr(49) # cyfra '1' ma numer 49, nie 1 '1' chr(179) '\xb3' chr(9) '\t' chr(322) 'ł'
Zmienne już poznaliśmy przy okazji pierwszego spotkania z interpreterem Pythona (rozdział 5.4.1.: kalkulator z pamięcią). Oto najważniejsze właściwości zmiennych:
Tablica jest tradycyjną nazwą typu danych, umożliwiającego przechowywanie numerowanych elementów jednego typu. W Pythonie mamy do czynienia z listami; w przeciwieństwie do tablic, poszczególne elementy listy mogą różnić się typami.
Elementarz:
[] # pusta lista [] type([]) # typ listy nazywa się 'list' <type 'list'> ['Ala', 'As'] ['Ala', 'As'] type(['Ala', 'As']) <type 'list'>
Niektóre operacje na listach nieco przypominają operacje na napisach.
['Ala', 'As'][0] # numeracja elementów zaczyna się od zera 'Ala' ['Ala', 'As'][-2] # działają tricki z numerowaniem „od końca” za pomocą ujemnych indeksów 'Ala' len(['Ala', 'As']) # liczbę elementów oblicza się za pomocą funkcji len() 2 'As' in ['Ala', 'As'] # przynależność elementu do listy da się sprawdzić za pomocą operatora in True ['As'] in ['Ala', 'As'] # być elementem jest czymś innym niż być podzbiorem. in sprawdza to pierwsze False ['Ala', 'As'] + ['Ola', 'Osa'] # operator + powoduje łączenie gotowych list… ['Ala', 'As', 'Ola', 'Osa'] [1, 2, 3] + [0, -1, 1] # …nawet gdybyśmy troszkę chcieli, żeby wynik wynosił [1, 1, 4] [1, 2, 3, 0, -1, 1] 2 * ['Ala', 'As'] # operator * powoduje zwielokrotnienie listy… ['Ala', 'As', 'Ala', 'As'] 2 * [3, 2, 5] # …nawet gdybyśmy troszkę chcieli, żeby wynik wynosił [6, 4, 10] [3, 2, 5, 3, 2, 5]
Listy w nawiasach [...]
można modyfikować.
a = ['a', 1.0, 1.0] type(a) <type 'list'> a[0] = 'A' a ['A', 1.0, 1.0]
Listy w nawiasach (...)
są niemodyfikowalne.
Ten typ danych nosi nazwę krotka (tuple
).
a = ('a', 1.0, 1.0) type(a) <type 'tuple'> a[0] = 'A' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
Poza tym krotki przypominają zwykłe listy, przynajmniej w tym sensie, że najważniejsze operacje określone dla list zachowują znaczenie także dla krotek. Mogą też w wielu sytuacjach być stosowane zamiennie z listami.
Ponieważ zawartość krotki deklaruje się w zwykłych nawiasach, wyrażenie takie jak
(1)
nie jest jednoznaczne, gdyż nie wiadomo, czy chodzi o krotkę z jednym elementem, czy o liczbę. Dlatego w Pythonie przyjęto, że nawias otaczający pojedynczą wartość jest nawiasem grupującym. Jednoelementową krotkę deklaruje się nieco inaczej:
(1,) (1,) type((1,)) <type 'tuple'>
Dla zachowania spójnej notacji przyjęto, że przy deklaracji krotek z dowolną liczbą składowych jest wszystko jedno, czy po ostatnim elemencie wstawi się przecinek, czy nie:
(1,2) (1,2) (1,2,) (1,2) (1,2,) == (1,2) True
Z krotkami mamy do czynienia także w sytuacji przypisań typu
a, b = wartość1, wartość2
omówionych w końcowej części podrozdziału 5.4.1.. Wyrażenia składające się z ciągu wartości oddzielonych przecinkami, takich jak
wartość1, wartość2
w istocie definiują krotki. Łatwo to sprawdzić:
'Adam', 'Nowak', 1.81 ('Adam', 'Nowak', 1.8100000000000001) osoba = 'Adam', 'Nowak', 1.81 type(osoba) <type 'tuple'>
Tak więc efekty przypisania
osoba = 'Adam', 'Nowak', 1.81
i przypisania
osoba = ('Adam', 'Nowak', 1.81)
są identyczne.
Notacja obiektowa: czynność jest właściwością danych.
lista.append(element)
lista.insert(pozycja, element)
lista[pozycja] = wartość
del element
len(lista)
element in lista
dane = [] dane.append(3) dane.append(0.5) dane.insert(1, -2.5) dane [3, -2.5, 0.5] dane[2] = 'kawa z automatu' [3, -2.5, 'kawa z automatu'] len(dane) 3
Jak widać, elementy listy nie muszą być tego samego typu.
max(lista)
sum(lista)
lista.sort()
lista.sort(funkcja)
lista2 = sorted(lista)
lista2 = sorted(lista, funkcja)
lista1 + lista2
lista * liczba
Konwersja bool(lista)
prowadzi do wartości True
wtedy
i tylko wtedy, kiedy lista jest niepusta. Nie jest natomiast istotne, jaki(e) element(y) zawiera:
dane = [] bool(dane) False dane.append(0.5) bool(dane) True dane = [False] # dane jest listą z jedną wartością: False bool(dane) True dane = [[]] # dane jest listą zawierającą tylko pustą listę bool(dane) True
Elementem listy może być dowolny obiekt — także lista.
dane = [] dane.append([0.0, 0.0]) dane.append([0.5, 0.25]) dane.append([1.0, 1.0]) dane.append([1.5, 2.25]) dane.append([2.0, 4.0]) dane [[0.0, 0.0], [0.5, 0.25], [1.0, 1.0], [1.5, 2.25], [2.0, 4.0]] dane[1] [0.5, 0.25] dane[1][0] 0.5 len(dane) 5 len(dane[0]) 2
Listy można przyrównywać za pomocą operatorów ==
i !=
sprawdzających wartości, a także za pomocą operatora is
sprawdzającego
tożsamość.
Równość list jest rozumiana jako równość ich elementów „wyraz po wyrazie”, z uwzględnieniem kolejności.
Do porównywania list używa się standardowych operatorów porównywania, czyli
<
,
<=
,
>=
oraz
>
.
Porównywanie list odbywa się podobnie, jak porównywanie tekstów,
czyli wyraz po wyrazie, począwszy od pierwszego. Jest to tzw. porządek słownikowy.
['Ala', 'As'] == ['Ala'] + ['As'] True ['Ala', 'As'] == ['As', 'Ala'] False ['Ala', 'As'] == ['Ala', 'Osa'] False ['Ala', 'As'] < ['Ola', 'As'] True # bo 'Ala' < 'Ola' ['Ala', 'As'] < ['Ala', 'Osa'] True # bo 'Ala' == 'Ala', zaś 'As' < 'Osa' [1, 5, 2] < [1, 5] False # dwie współrzędne są identyczne, ale pierwsza lista ma dodatkowy element, więc jest „większa”
Oprócz list, Python posiada także inne wbudowane złożone typy danych.
Do najważniejszych należą
pliki (typ file
),
słowniki (typ dict
)
i zbiory (typ set
).
Wiele innych przydatnych typów jest zdefiniowanych w dodatkowych modułach.
Umożliwia też tworzenie własnych typów danych.
Dane typu plikowego umożliwiają operacje na zawartości plików komputerowych, przede wszystkim ich czytanie i zapisywanie. Do tematu tego powrócimy w rozdziale 10.4.
Słownik (‘dictionary’) jest rodzajem spisu indeksowanego za pomocą dowolnych kluczy. Tym słowniki różnią się od list, których elementy zawsze są numerowane kolejnymi liczbami naturalnymi.
Zawartość słownika składa się z par postaci klucz : wartość
.
W notacji oddziela się je przecinkami, a całość ujmuje się w nawiasy klamrowe.
cennik = {'chleb' : 2.5, 'masło' : 2.45, 'ser' : 10.99} type(cennik) <type 'dict'> cennik['chleb'] 2.5 'chleb' in cennik True 'mąka' in cennik False cennik.update({'mąka': 1.55, 'chleb' : 2.45, 'kiełbasa' : 18.0}) cennik {'chleb': 2.45, 'kiełbasa': 18.0, 'masło': 2.45, 'mąka': 1.55, 'ser': 10.99} len(cennik) 5
Danych typu słownikowego nie będziemy szerzej omawiać ani z nich korzystać.
Zbiór (‘set’) jest nieuporządkowaną i nienumerowaną kolekcją niepowtarzających się elementów.
set([1, 2, 3]) set[1, 2, 3]) set([2, 2, 1, 3]) set[1, 2, 3]) type(set([1, 2, 3])) <type 'set'> set(['chleb', 'ser', 'masło', 'kiełbasa']) set(['chleb', 'kiełbasa', 'masło', 'ser'])
Tradycyjna notacja zbiorów w Pythonie wymaga konwersji
z listy do typu set
, tak jak w powyższym przykładzie.
W Pythonie 3 do opisywania zbiorów służą nawiasy klamrowe,
np. {1, 2, 3}
lub {'chleb', 'masło', 'ser'}
.
W Pythonie 3 wartość {}
odnosi się do pustego słownika, a nie do pustego zbioru.
Ten ostatni trzeba opisywać jako set()
, set([])
albo set({})
.
W przypadku zbiorów pytanie o przynależność elementów realizujemy za pomocą znanego już operatora in
.
Obiektowe metody zbiorów pozwalają natomiast dodawać i usuwać elementy, dołączać podzbiory, obliczać sumę,
część wspólną i różnicę zbiorów oraz wykonywać inne charakterystyczne dla zbiorów operacje.
zapasy = set(['chleb', 'ser', 'masło', 'kiełbasa']) zakupy = set(['chleb', 'pomidory']) 'chleb' in zapasy True 'mleko' in zapasy False 'mleko' in zakupy False zakupy.add('mleko') 'mleko' in zakupy True len(zakupy) 3 zapasy.union(zakupy) set(['chleb', 'kiełbasa', 'masło', 'mleko', 'pomidory', 'ser']) zapasy set(['chleb', 'kiełbasa', 'masło', 'ser']) zapasy.update(zakupy) 'mleko' in zapasy True zapasy set(['chleb', 'kiełbasa', 'masło', 'mleko', 'pomidory', 'ser'])
Danych typu zbiór nie będziemy szerzej omawiać ani z nich korzystać.
Powracamy po raz kolejny do danych typu tekstowego. Do pełnego
omówienia zagadnień formatowania danych tekstowych potrzebne są
nie tylko teksty, ale także krotki. Nie mogliśmy więc ich poruszyć
przy wcześniejszych spotkaniach z typem str
.
Przy tworzeniu różnego rodzaju raportów wygodna jest możliwość oddzielenia niezmiennej części ich treści od parametrów podstawianych zależnie od potrzeb. Dobrym przykładem jest list z zawiadomieniem, wysyłany do wielu adresatów, przy czym imię, nazwisko i inne dane (np. stan konta) są inne w każdej kopii.
%
Stałą część wzorca opisuje się w Pythonie za pomocą tekstu,
w którym miejsce występowania zmiennych parametrów jest oznaczane symbolami:
%s
w przypadku parametru tekstowego,
%i
lub %d
w przypadku parametru całkowitoliczbowego,
%f
w przypadku parametru zmiennopozycyjnego.
Istnieją też inne specyfikatory.
Podstawienia wartości parametrów dokonuje się za pomocą operacji
wzorzec % argumenty
przy czym wzorzec
jest tekstem z symbolami
parametrów, zaś argumenty
jest krotką zawierającą
argumenty w kolejności ich włączania do wzorca. Typy deklarowanych
i realnie zadanych argumentów nie muszą być zgodne, ale musi dać się dokonać konwersji.
Wynik jest napisem, w którym symbole parametrów zastąpiono ich sformatowanymi wartościami.
W przypadku jednego argumentu zamiast krotki można podstawić jego wartość, tak jak to miało miejsce w podrozdziale 5.3.3.2.
Na przykład wzorzec
raport = '''%s Informujemy %s, że stan konta %s w dniu %s wynosi %f PLN.'''
w którym pierwszy parametr oznacza nazwę klienta, drugi — zwrot grzecznościowy, trzeci — nazwę konta, czwarty — datę, a ostatni — stan konta, może być wykorzystany w następujący sposób:
raport % ('Karol Kozioł', 'Pana', 'abcd-00-11-x', '22 stycznia 1988 r.', '122.33') raport % ('Karolina Kozłowicz', 'Panią', 'qwer-88-99-y', '23 stycznia 1988 r.', '13357.05')
Metoda ta wydaje się bardziej skomplikowana niż zwykłe konstruowanie tekstu za pomocą operacji łączenia napisów, lecz w praktyce jest znacznie wygodniejsza. Daje też większą kontrolę nad formatem prezentowanych danych.
Dodatkowe opcje specyfikatorów decydują o sposobie przedstawienia podstawianych wartości. Pokażemy to na przykładzie liczb zmiennopozycyjnych:
'Stan konta w dniu %s wynosi %f PLN' % ('2 stycznia 2007 r.', 123.45) # domyślna dokładność przedstawienia liczby 'Stan konta w dniu 2 stycznia 2007 r. wynosi 123.450000 PLN' 'Stan konta w dniu %s wynosi %.2f PLN' % ('2 stycznia 2007 r.', 123.45) # dwie cyfry dziesiętne 'Stan konta w dniu 2 stycznia 2007 r. wynosi 123.45 PLN' 'Stan konta w dniu %s wynosi %8.2f PLN' % ('2 stycznia 2007 r.', 123.45) # co najmniej osiem znaków, w tym dwie cyfry dziesiętne 'Stan konta w dniu 2 stycznia 2007 r. wynosi 123.45 PLN' 'Stan konta w dniu $s wynosi %8.3f PLN' % ('2 stycznia 2007 r.', 123.45) # co najmniej osiem znaków, w tym trzy cyfry dziesiętne 'Stan konta w dniu 2 stycznia 2007 r. wynosi 123.450 PLN'
format
Do napisania …
Zamiast konstrukcji
wzorzec % argumenty
używać można
wzorzec.format(argumenty)
Różnią się one sposobem opisu wzorca oraz sposobem podstawiania
do niego argumentów. Metoda z funkcją format
daje możliwość wielokrotnego wykorzystania jednego argumentu
oraz sterowania kolejnością. Jest dzięki temu wygodniejsza w użyciu.
Zamiast
raport = '''%s Informujemy %s, że stan konta %s w dniu %s wynosi %f PLN.'''
zdefiniujemy wzorzec raportu jako
raport = '''{0} Informujemy {1}, że stan konta {2} w dniu {3} wynosi {4} PLN.'''
po czym użyjemy go w następujący sposób:
raport.format('Karol Kozioł', 'Pana', 'abcd-00-11-x', '22 stycznia 1988 r.', '122.33') raport.format('Karolina Kozłowicz', 'Panią', 'qwer-88-99-y', '23 stycznia 1988 r.', '13357.05')
Pełny wykaz dyrektyw formatowania znajduje się w dokumentacji języka.
Do dokładniejszego omówienia niektórych typów danych i ich właściwości powrócimy w rozdziale 10. poświęconym obiektom.