2010.08_Kurs Pythona. Cz. II – Struktury danych, funkcje i moduły_[Python].pdf

(941 KB) Pobierz
441734034 UNPDF
PROGRAMOWANIE PYTHON
Cz. II – Struktury danych,
funkcje i moduły
W odcinku wprowadzającym zainstalowaliśmy Pythona
i trochę pobawiliśmy się różnymi jego cechami. Po nabraniu
swobody w wykorzystaniu linii poleceń możemy zabrać się
za bardziej metodyczny przegląd tego, co oferuje nam język
spod znaku węża.
Dowiesz się:
• Jak zainstalować i uruchomić interpreter Python 2.6
• Jak poruszać się po interaktywnej sesji w interpreterze
Powinieneś wiedzieć:
• Czym różni się kultura języka Python od Javy i innych statycz-
nie typowanych języków
• Jak używać wbudowanych w Pythona list, słowników, krotek i
zbiorów
materiału dla osób, które nie miały wcześniej
styczności z językiem. Przedstawiony tu zakres
jest omawiany w praktycznie każdej książce poświę-
conej językowi. W tym kursie jednak za punkt hono-
ru przyjęliśmy sobie promowanie dobrych zwyczajów
nad suchą prezentację faktów.
Język to budulec, narzędzie służące do opisu zacho-
wania systemu komputerowego. Od powstania pierw-
szego języka programowania trwa nieustanna walka
o to, żeby produkowany kod źródłowy prowadził do
oczekiwanego zachowania komputera. Mówimy, "że-
by był poprawny", "bez błędów".
Kompilowane języki programowania takie jak C czy
Java w swojej kulturze kultywują sprawdzanie na pro-
gramiście na każdym kroku, czy jest pewien swoich
poczynań. Składnia wielu fundamentalnych wyrażeń
jest celowo bardzo rozwlekła i nadmierna, żeby kon-
trolowała programistę przed pomyłkami, chwilowymi
zaćmieniami umysłu. W szczególności Java jest języ-
kiem, w którego kulturze kultywuje się ścisłą kontrolę
nad tym, co programista powinien móc zrobić, co po-
winien widzieć w danym kontekście, do czego powi-
nien mieć w danym momencie dostęp. O ile takie za-
chowanie jest bardzo pomocne, w szczególności na
początku, dla programistów o niewielkim doświadcze-
niu, po jakimś czasie zaczyna ciążyć zaawansowanym
programistom jak kula u nogi.
Python idzie natomiast zupełnie pod prąd, traktując
swoich programistów od samego początku jak ludzi in-
teligentnych.
Wszyscy tutaj jesteśmy dorośli
Po kilku latach intensywnego kodowania w Javie za-
cząłem mieć wrażenie, że zarówno sam język, jak i je-
go kompilator i maszyna wirtualna traktują mnie jak
małe dziecko. Na każdym kroku musiałem wprost i ja-
sno przedstawiać swoje zamiary i założenia, język
nie dawał mi tutaj żadnej dowolności. Nie pozosta-
wiając pola do domysłów, minimalizował liczbę uste-
rek. Lub tak mu się wydawało. Kompilator krytykował
wiele sprytnych manewrów, oceniając je za podejrza-
ne lub potencjalnie niebezpieczne. W końcu sama ma-
szyna wirtualna ukrywała w czasie uruchamiania wie-
le kwestii w obawie, że użyłbym ich w sposób nieod-
powiedzialny.
Python wygląda w porównaniu jak wejście w doro-
słość. Nadal zachowania w oczywisty sposób szkodli-
we lub nieprzemyślane powodują krytykę interpretera,
jednak zostawia on bardzo wiele dowolności w sposo-
bie wykorzystania języka przez programistę. Python
zakłada, że wiesz, co robisz.
Środowisko programistów Pythona od pierwszych
dni istnienia miało świadomość swobody, jaką on da-
je, i wiążącej się z nią odpowiedzialności. Przez la-
ta intensywnego wykorzystania języka środowisko to
20
8/2010
Kurs Pythona.
T a część kursu zawiera bardzo wiele nowego
441734034.026.png 441734034.027.png
 
441734034.028.png 441734034.001.png 441734034.002.png 441734034.003.png 441734034.004.png
Kurs Pythona. Cz II – Struktury danych, funkcje i moduły
wykształciło pewną kulturę, która opisuje rekomendo-
wane rozwiązania różnych typowych kwestii. Ta kultu-
ra daje wszystkim bazę do rozwijania oprogramowa-
nia, które w sposób domyślny zachowuje się w spo-
sób, którego inni programiści w środowisku oczekują,
a którego kod źródłowy jest łatwy do czytania i zrozu-
mienia przez innych programistów.
Jak to w dorosłym życiu, zachowania zgodnego
z normami kulturalnymi oczekuje się również po po-
czątkujących, czyli także po Tobie. Duży nacisk w tym
kursie, a w szczególności w tej części, poświęcimy na
zapoznanie Cię z zasadami pythonowego savoir-vi-
vre. Jak zobaczysz, zasady te nie powstały bez przy-
czyny, a w istocie stoją za nimi mocne argumenty. Za-
cznijmy od najbardziej znanej konwencji w kulturze
Pythona...
kiedy elementy będą innych typów, program zwróci
oczekiwany wynik, np. dla listy [1, 2.0, ["lista", "jako",
"element"]]:
Element: 1
Element: 2.0
Element: ['lista', 'jako', 'element']
Gdzie tkwi tajemnica? To proste! W Pythonie wszyst-
kie typy danych są obiektami. Wartość łańcuchową
(%s) dla dowolnego typu interpreter uzyskuje przez
wywołanie specjalnej metody _ _ str _ _ () na danym
obiekcie (więcej o specjalnych metodach w Ramce
1 obok). Autorzy Pythona zauważyli więc, że wystar-
czy wyposażyć możliwie dużo typów danych w taką
specjalną metodkę i dla większości kodu źródłowe-
go obiekty tych typów będą wyglądały zupełnie jak
prawdziwe łańcuchy znaków. I nie ma znaczenia, że
tak naprawdę to nie są łańcuchy. Nie jest to dla nas
ważny szczegół, ważne, że działa. I to poprawnie!
W środowisku zakwitła więc jedna z najważniej-
szych zasad kultury programowania w Pythonie: "je-
żeli coś wygląda jak kaczka, chodzi jak kaczka i kwa-
cze jak kaczka, to musi to być kaczka!". Oznacza to
mniej więcej, że Twój kod powinien przyjmować do-
wolne typy danych, które spełniają jakiś kontrakt (np.
posiadają określoną metodę lub atrybut). Nie ma sen-
su wieczne sprawdzanie, czy dana zmienna jest odpo-
wiedniego typu. Zakładamy, że jest i że spełnia nasz
kontrakt. Taki styl kodowania nazywany jest duck ty-
ping. Co on nam daje?
Jeżeli kwacze jak kaczka...
Jak wspomnieliśmy w pierwszej części naszego kur-
su, Python jest językiem silnie typowanym, ale o dy-
namicznym systemie typów. Oznacza to w praktyce,
że zmienne (w tym argumenty funkcji) mogą przyjmo-
wać argumenty dowolnych typów, ale typ jest rzeczą
twardą, tzn. łańcuch znaków nigdy bez jawnej konwer-
sji nie zachowa się jak liczba. Najłatwiej można to zro-
zumieć, wpisując w linii poleceń:
>>> 10 == "10"
False
>>> 10 == int("10")
True
>>> str(10) == "10"
True
• Po pierwsze: możliwość późniejszego rozszerze-
nia programu w taki sposób, że pod daną zmien-
ną (lub jako argument do danej funkcji) zostanie
podana wartość innego typu, który jednak wyglą-
da zupełnie jak oczekiwany typ! Szalenie przydat-
na możliwość np. w testowaniu jednostkowym ko-
du. I to bez żadnych zmian w oryginalnym kodzie!
• Po drugie: ryzyko, że w jakiś sposób trai do nas
niekompatybilny typ. Czy jednak jest się w tym
przypadku o co martwić? Możemy niby sprawdzić,
czy dana zmienna zawiera wartość danego typu,
czy też może dany typ posiada oczekiwaną me-
todę lub atrybut. Co jednak możemy zrobić, jeże-
li okaże się, że nie? Domyślnym zachowaniem Py-
thona jest rzucenie wyjątku. I nie wymaga to żad-
nej pracy z naszej strony. Jeżeli chcielibyśmy wo-
bec tego rzucić wyjątek w takim przypadku, nie
ma sensu kodować rozwiązania, które naśladuje
to, co i tak dzieje się domyślnie.
Jak widać, Python rozróżnia liczbę 10 od łańcucha
znaków reprezentującego "10". Za pomocą jawnej
konwersji możemy jednak w razie potrzeby przecho-
dzić między typami. Dynamiczne, ale silne typowa-
nie to podstawowa cecha języka. Umożliwia z jednej
strony uzyskanie bardzo zwięzłego kodu, który nie
tylko szybciej się pisze, ale też szybciej czyta:
irst_name = "Winona"
birth_year = 1971
Środowisko programistów Pythona wykorzystuje fakt,
że każda zmienna może potencjalnie przechowywać
wartość dowolnego typu. Przykładowo, mamy kod,
który wypisuje na ekranie elementy listy:
for elem in a_list:
print "Element: %s" % elem
Poproszę indeks
Biorąc pod uwagę duck typing, skupianie się na poje-
dynczych strukturach danych nie jest tak bardzo cie-
Kod oczywiście działa dla elementów będących łań-
cuchami znaków. Jak się jednak okazuje, również
www.sdjournal.org
21
441734034.005.png 441734034.006.png 441734034.007.png 441734034.008.png 441734034.009.png 441734034.010.png 441734034.011.png
 
PROGRAMOWANIE PYTHON
kawe jak dokładne omówienie sposobu ich wykorzy-
stania. W końcu zgodnie z naturą języka, wiele metod
bezpośrednio przenosi się również na inne typy da-
nych i działa w zupełnie przewidywalny sposób. Za-
cznijmy od dostępu do danych przez indeks.
Struktury danych typu listy, krotki (ang. tuple , patrz
Ramka 2), słowniki, łańcuchy znaków i inne umożli-
wiają dostęp do danych, które przechowują przez bar-
dzo sprytny mechanizm indeksowania. Spójrzmy na
przykład:
Jak widać, lista daje dostęp do danych po indeksie
(licząc - jak w każdym rozsądnym języku - od zera
[1]). Jeżeli wybierzemy niepoprawny indeks, lista rzu-
ca wyjątek IndexError . Więcej o wyjątkach w dalszej
części artykułu.
Podawany indeks może być ujemny, wówczas liczy-
my "od końca". To nie jedyny sprytny manewr dostęp-
ny przy okazji indeksowania. Python umożliwia rów-
nież wycinanie (ang. slicing ) fragmentów z większych
struktur:
>>> movies = ['Lucas', 'Beetlejuice', 'Dracula']
>>> movies[0]
'Lucas'
>>> movies[1]
'Beetlejuice'
>>> movies[2]
'Dracula'
>>> movies[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> movies[-1]
'Dracula'
>>> movies[-2]
'Beetlejuice'
>>> movies[-3]
'Lucas'
>>> movies[-4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> sisters = ['Meg', 'Jo', 'Beth', 'Amy']
>>> sisters[0:3]
['Meg', 'Jo', 'Beth']
>>> sisters[0:-1]
['Meg', 'Jo', 'Beth']
>>> sisters[:3]
['Meg', 'Jo', 'Beth']
>>> sisters[:-1]
['Meg', 'Jo', 'Beth']
>>> sisters[1:4]
['Jo', 'Beth', 'Amy']
>>> sisters[1:]
['Jo', 'Beth', 'Amy']
>>> sisters[0:4:2]
['Meg', 'Beth']
>>> sisters[1:4:2]
['Jo', 'Amy']
>>> sisters[0::2]
['Meg', 'Beth']
>>> sisters[::2]
['Meg', 'Beth']
Ramka 1: Metody specjalne
Od pierwszego dnia w Pythonie istnieje grupa wbudowanych funkcji, które spełniają określoną funkcjonalność dla podanych ar-
gumentów. I to niezależnie od typu tych argumentów. Zwane z angielska builtin functions lub po prostu builtins udo-
stępniają m.in. możliwość sprawdzenia długości danej sekwencji (w tym: łańcucha znaków), porównania dwóch obiektów czy
skonwertowania dowolnego typu na łańcuch znaków. Pełna lista builtins wraz z dokumentacją znajduje się pod adresem http:
//docs.python.org/library/functions.html .
Kiedy tylko jest to możliwe, wykorzystanie builtins jest preferowane nad inne metody programowania. Stąd bardzo często
spotkać można kod np.:
if len(s) < 10:
...
Obiekty w Pythonie implementują zachowanie poszczególnych builtinów w metodach specjalnych, które łatwo można poznać
po dwóch podkreślnikach przed ich nazwą i po ich nazwie. Przykładowo:
>>> dir("")
['__add__', ... , '__len__', ...]
Kiedy użytkownik wykona len(s) to interpreter w rzeczywistości wykonuje odpowiednią metodę specjalną s.__len__() . Jest
to o tyle fajne, że deiniując własne typy danych również możemy deiniować metody specjalne, przez co umożliwiamy wyko-
rzystanie builtinów na naszych typach. Jest to bardzo często wykorzystywana cecha języka, ponieważ ujednolicenie interfejsu
nowych typów danych do już istniejących bardzo ułatwia zapamiętanie, jak z nich korzystać. Tak wyprodukowane API jest spój-
ne, przewidywalne i intuicyjne. W Pythonie sprawdzanie długości, rozmiaru lub innych tego typu cech zawsze odbywa się przez
len() . Jak jest w Javie? Mamy metody length() , atrybuty length , metody size() , count() , getSize() , itd. itp. Możliwe do
zapamiętania? Z pewnością. Ale czy wygodne i intuicyjne?
22
8/2010
441734034.012.png 441734034.013.png
 
441734034.014.png
 
Kurs Pythona. Cz II – Struktury danych, funkcje i moduły
Jak widać, jako "indeks" można podać zakres, skład-
nia zakresów jest zresztą bardzo intuicyjna, a jedno-
cześnie elastyczna. Można podawać zakresy, używa-
jąc indeksu rozpoczynającego (włącznie), zamykają-
cego (wyłącznie) lub obu. Indeks zamykający może
być podawany jako ujemna wartość liczona elemen-
tami od końca. Można też podać opcjonalnie krok,
z jakim ma być dokonane wycinanie.
Efektem wycinania jest zupełnie nowa lista przecho-
wująca elementy z oryginalnej listy. Oznacza to, że
możemy bezstratnie tworzyć wiele skrawków z poje-
dynczej listy i nie marnujemy w ten sposób pamięci,
bo nowe listy przechowują referencje do tych samych
elementów co oryginalna. Jest to często wykorzysty-
wana właściwość, np. podczas ładnego wypisywania
elementów:
Tymczasem spójrzmy jeszcze pokrótce na "indek-
sowanie" w słownikach. W tym wypadku sprawa wy-
gląda zgoła inaczej, ponieważ słowniki w swej natu-
rze przechowują zbiory unikalnych kluczy, pod który-
mi kryją się jakieś wartości. Dostęp do tych wartości
przez klucz przypomina indeksowanie w listach, ale
z oczywistych względów nie umożliwia zabawy w wy-
cinanki. Spójrzmy na krótki przykład:
>>> movie_releases = {'Edward Scissorhands': 1990,
'Dracula': 1992, 'Star Trek': 2009}
>>> movie_releases['Star Trek']
2009
>>> movie_releases['Dracula']
1992
>>> movie_releases['A Scanner Darkly']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'A Scanner Darkly'
>>> def print_mermaids():
... for mermaid in mermaids[:-1]:
... print "%s," % mermaid,
... print "%s." % mermaids[-1]
>>> print_mermaids()
Cher, Ryder, Ricci.
Jak widać, można się do przechowywanych obiek-
tów odwoływać poprzez klucze, w przypadku użycia
nieistniejącego klucza rzucany jest wyjątek KeyError .
Nie ma możliwości odwołania się do "pierwszego"
klucza albo "ostatniego" klucza, ponieważ słowni-
ki przechowują z zasady nieuporządkowane zbio-
ry danych. Fakt ten jest wykorzystywany przez Py-
thona do optymalizacji struktury słownika w pamięci
tak, żeby dostęp do dowolnego klucza był tak samo
szybki. Może się więc zdarzyć, że wartości są prze-
chowywane w innej kolejności niż je deiniowaliśmy.
Spójrzmy przykładowo na nasze movie _ releases
z przykładu wyżej:
Zdeiniowaliśmy na szybko funkcję wypisującą nam
aktorki grające w "Syrenach". Właściwy kod to tylko
3 linijki, a w elegancki sposób udało nam się uzyskać
oddzielanie poszczególnych syrenek przecinkami,
z kropką na końcu. To bardzo przydatny idiom, prędzej
czy później w jakiś sposób będziesz go potrzebować.
Ale trochę się zapędziliśmy, o pętlach i funkcjach do-
piero za chwilkę! Jeżeli dziwi Cię zachowanie wyraże-
nia print , zapraszam do Ramki 3.
Wracając do indeksowania, zgodnie z naturą Py-
thona przewidujemy, że w dokładnie ten sam spo-
sób można korzystać z danych w łańcuchach znaków
i krotkach. Faktycznie, nie ma żadnej różnicy:
>>> movie_releases
{'Dracula': 1992, 'Edward Scissorhands': 1990, 'Star
Trek': 2009}
>>> celebrity = "Winona Ryder"
>>> celebrity[0]
'W'
>>> celebrity[7]
'R'
>>> celebrity[-1]
'r'
>>> celebrity[:6]
'Winona'
>>> celebrity[7:]
'Ryder'
Jak widać, na moim komputerze interpreter zamienił
miejscami Nożycorękiego z Drakulą. W Twoim przy-
padku wynik może być inny, spróbuj.
Zgadnij kotku, co mam w środku
Co prawda interfejs dostępu do kluczy jest w przypad-
ku słowników minimalistyczny, struktura danych wyna-
gradza nam to jednak bogatym API do operowania na
kluczach i wartościach. Zacznijmy od metody spraw-
dzenia, czy dany klucz istnieje w słowniku:
Nie jest to jednak najbardziej użyteczny sposób ma-
nipulacji łańcuchami znaków, o manipulacji tekstem
w ogólności, wyrażeniach regularnych i ich mocy wię-
cej w przyszłym numerze Software Developer's Jour-
nal.
>>> alien4 = {'Sigourney Weaver': 'Ellen Ripley',
... 'Winona Ryder': 'Annalee Call'}
>>> 'Winona Ryder' in alien4
True
>>> 'Johnny Depp' in alien4
False
www.sdjournal.org
23
441734034.015.png 441734034.016.png 441734034.017.png 441734034.018.png 441734034.019.png 441734034.020.png 441734034.021.png
 
PROGRAMOWANIE PYTHON
Składnia ELEM in DICT jest bardzo przejrzysta i moż-
na ją wykorzystać wszędzie, gdzie oczekujemy war-
tości boolean , m.in. w instrukcjach warunkowych, wa-
runkach wyjścia z pętli, itd. itp. Są też inne metody na
sprawdzenie obecności klucza w słowniku, ale istnie-
ją one już tylko z powodów historycznych i ich użycie
nie jest zalecane.
Do słowników można oczywiście dokładać kolejne
wartości, robimy to zasadniczo na dwa sposoby:
>>> numbers = [4, 8, 15, 16, 23, 42]
>>> numbers[0]
4
>>> numbers[3]
16
>>> 16 in numbers
True
>>> del numbers[3]
>>> 16 in numbers
False
>>> numbers
[4, 8, 15, 23, 42]
• dopisując nowy klucz bezpośrednio:
>>> alien4['Dan Hedaya'] = 'General Perez'
>>> alien4
{'Winona Ryder': 'Annalee Call',
'Sigourney Weaver': 'Ellen Ripley',
'Dan Hedaya': 'General Perez'}
Na marginesie, sprawdzenie pierwszego indeksu, pod
którym kryje się dana wartość w liście, można wyko-
nać metodką index() , np.:
• rozszerzając słownik za pomocą innego słownika:
>>> numbers.index(42)
4
>>> numbers[4]
42
>>> numbers.index(44)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.index(x): x not in list
>>> secondary_characters = {'Gary Dourdan': 'Christie',
... 'Ron Perlman': 'Johner',
... 'Dominique Pinon': 'Vriess'}
>>> alien4.update(secondary_characters)
>>> alien4
{'Dominique Pinon': 'Vriess', 'Winona Ryder': 'Annalee
Call',
'Sigourney Weaver': 'Ellen Ripley', 'Dan Hedaya':
'General Perez',
'Ron Perlman': 'Johner', 'Gary Dourdan': 'Christie'}
Oczywiście, jak opisuje Ramka 2, krotki są niezmie-
nialne, więc nie możemy usuwać z nich elementów,
natomiast do zbiorów nie da się odwołać po indeksie.
Stąd też w zbiorach dodawanie i usuwanie elemen-
tów trzeba rozwiązać inaczej:
Obie metody są dobre, ta druga jest zalecana w przy-
padku zmian w wielu kluczach jednocześnie. A co
w przypadku, kiedy jakiś klucz chcemy usunąć ze
słownika? Wykorzystujemy wyrażenie del :
>>> latware = set(('fork', 'knife', 'spoon'))
>>> latware.remove('fork')
>>> latware.remove('spoon')
>>> latware.add('spork')
>>> latware
set(['spork', 'knife'])
>>> 'chopsticks' in latware
False
>>> latware.remove('chopsticks')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'chopsticks'
>>> 'Dominique Pinon' in alien4
True
>>> del alien4['Dominique Pinon']
>>> 'Dominique Pinon' in alien4
False
>>> del alien4['Dominique Pinon']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Dominique Pinon'
Funkcje i moduły
W naszych dotychczasowych przykładach przemycili-
śmy już kilka funkcji tam, gdzie inaczej nie dało się za-
prezentować konkretnej konstrukcji językowej. Przyj-
rzyjmy się jednak bliżej tym bestiom, bo w Pythonie są
one wybitnie potężne, choć na pierwszy rzut oka tego
nie widać.
Definiowanie funkcji jest proste jak budowa cepa,
przykładowo:
Jak widać, w przypadku gdy klucza nie ma w słowni-
ku, podczas próby usunięcia elementu rzucany jest
wyjątek KeyError .
Patrząc na metodę sprawdzania, czy dany element
należy do słownika, albo na możliwość usuwania poje-
dynczego klucza, wydaje się, że użyteczne byłoby coś
podobnego dla list, krotek i zbiorów. Jak się okazuje,
w Pythonie tak właśnie jest:
24
8/2010
441734034.022.png 441734034.023.png
 
441734034.024.png 441734034.025.png
 
Zgłoś jeśli naruszono regulamin