Spis treści Skorowidz Poziom główny Poziom nadrzędny ©
«« Początek »» Koniec

Dane w programie, czyli nasze własne zabawki

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.

Kodowanie znaków

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.

Numery znaków

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

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:

Zmienne proste

Listy i tablice

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.

Zarządzanie zawartością list

Notacja obiektowa: czynność jest właściwością danych.

lista.append(element)
dołącza element na koniec listy lista
lista.insert(pozycja, element)
wstawia element na pozycja-tym miejscu listy lista
lista[pozycja] = wartość
zastępuje pozycja-ty element listy lista obiektem o wskazanej wartości
del element
usuwa dowolny obiekt — w tym także element listy, zbiór elementów listy, a nawet całą listę
len(lista)
zwraca liczbę elementów listy lista
element in lista
zwraca wartość logiczną zdania „element występuje co najmniej raz w liście
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.

Inne operacje na listach

max(lista)
zwraca wartość największego elementu listy; patrz operator porównywania, funkcje
sum(lista)
zwraca wartość sumy elementów listy; elementy muszą być liczbami
lista.sort()
porządkuje kolejność elementów listy
lista.sort(funkcja)
pozwala określić kryterium porządkowania
lista2 = sorted(lista)
lista2 = sorted(lista, funkcja)
tworzy inną listę (lista2), zawierającą uporządkowane rosnąco elementy danej listy
lista1 + lista2
łączenie list; zwraca listę zawierającą wszystkie elementy listy lista1, a po nich wszystkie elementy listy lista2
lista * liczba
zwielokrotnianie listy: zwraca listę powstałą przez n-krotne dołączanie listy lista; jeden z argumentów musi być listą, a drugi liczbą całkowitą nieujemną

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

Listy list

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

Przykłady

Porównywanie list

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”

Inne typy danych

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.

Pliki

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łowniki

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ć.

Zbiory

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ć.

Formatowanie napisów

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.

Formatowanie przy użyciu operatora %

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'

Formatowanie przy użyciu funkcji 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.

Pytania kontrolne

  1. Jakie typy danych są standardowo dostępne w Pythonie i do czego one służą?
  2. Jak jest różnica między wartością a zmienną (w dowolnym języku programowania)?
  3. Jak jest różnica między zmienną a obiektem w Pythonie?
  4. Co to są funkcje?
  5. Co to są metody?
© Copyright 2000–2021 by Jan Jełowicki, Katedra Matematyki Uniwersytetu Przyrodniczego we Wrocławiu
Ostatnia modyfikacja w marcu 2021 r.
janj@aqua.up.wroc.pl
http://karnet.up.wroc.pl/~jasj