You are on page 1of 426

Spis treści

O autorze ...........................................................................11
Wstęp ...............................................................................13
Rozdział 1. Wprowadzenie. Program Koniec gry ....................................15
Analiza programu Koniec gry ..........................................................15
Co warto wiedzieć o Pythonie? .......................................................16
Konfiguracja Pythona w systemie Windows ......................................19
Konfiguracja Pythona w innych systemach operacyjnych ...................20
Wprowadzenie do IDLE ..................................................................20
Powrót do programu Koniec gry ......................................................26
Podsumowanie .............................................................................29
Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia.
Program Nieistotne fakty ...................................................31
Wprowadzenie do programu Nieistotne fakty ...................................31
Użycie cudzysłowów przy tworzeniu łańcuchów znaków .....................32
Używanie sekwencji specjalnych w łańcuchach znaków .....................36
Konkatenacja i powielanie łańcuchów .............................................40
Operacje na liczbach .....................................................................42
Pojęcie zmiennych .........................................................................45
Pobieranie danych wprowadzanych przez użytkownika ......................48
Używanie metod łańcucha ..............................................................50
Stosowanie właściwych typów ........................................................54
Konwersja wartości .......................................................................56
Powrót do programu Nieistotne fakty ..............................................59
Podsumowanie .............................................................................61
Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu.
Gra Odgadnij moją liczbę ....................................................63
Wprowadzenie do gry Jaka to liczba? ..............................................63
Generowanie liczb losowych ...........................................................64
Używanie instrukcji if .....................................................................67
6 Python dla każdego. Podstawy programowania

Używanie klauzuli else ...................................................................71


Używanie klauzuli elif .....................................................................73
Tworzenie pętli while .....................................................................76
Unikanie pętli nieskończonych ........................................................80
Traktowanie wartości jako warunków ..............................................83
Tworzenie umyślnych pętli nieskończonych ......................................86
Korzystanie z warunków złożonych ..................................................88
Projektowanie programów ..............................................................93
Powrót do gry Jaka to liczba? .........................................................95
Podsumowanie .............................................................................97
Rozdział 4. Pętle for, łańcuchy znaków i krotki.
Gra Wymieszane litery ........................................................99
Wprowadzenie do programu Wymieszane litery ................................99
Liczenie za pomocą pętli for .........................................................102
Stosowanie funkcji i operatorów sekwencji do łańcuchów znaków ...105
Indeksowanie łańcuchów .............................................................107
Niemutowalność łańcuchów .........................................................111
Tworzenie nowego łańcucha .........................................................113
Wycinanie łańcuchów ..................................................................116
Powrót do gry Wymieszane litery ...................................................128
Podsumowanie ...........................................................................131
Rozdział 5. Listy i słowniki. Gra Szubienica ........................................133
Wprowadzenie do gry Szubienica ..................................................133
Korzystanie z list .........................................................................135
Korzystanie z metod listy .............................................................140
Kiedy należy używać krotek zamiast list? .......................................144
Używanie sekwencji zagnieżdżonych ..............................................145
Referencje współdzielone ............................................................149
Używanie słowników ....................................................................152
Powrót do gry Szubienica .............................................................159
Podsumowanie ...........................................................................165
Rozdział 6. Funkcje. Gra Kółko i krzyżyk .............................................167
Wprowadzenie do gry Kółko i krzyżyk .............................................167
Tworzenie funkcji .........................................................................169
Używanie parametrów i wartości zwrotnych ....................................172
Wykorzystanie argumentów nazwanych i domyślnych wartości
parametrów ..............................................................................176
Wykorzystanie zmiennych globalnych i stałych ...............................181
Powrót do gry Kółko i krzyżyk ........................................................185
Podsumowanie ...........................................................................196
Spis treści 7

Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy .....................................199


Wprowadzenie do programu Turniej wiedzy ....................................199
Odczytywanie danych z plików tekstowych .....................................200
Zapisywanie danych do pliku tekstowego ......................................206
Przechowywanie złożonych struktur danych w plikach .....................209
Obsługa wyjątków ........................................................................214
Powrót do gry Turniej wiedzy .........................................................218
Podsumowanie ...........................................................................223
Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka .............225
Wprowadzenie do programu Opiekun zwierzaka .............................225
Podstawy programowania obiektowego .........................................227
Tworzenie klas, metod i obiektów .................................................228
Używanie konstruktorów ..............................................................230
Wykorzystywanie atrybutów ..........................................................232
Wykorzystanie atrybutów klasy i metod statycznych ........................236
Hermetyzacja obiektów ................................................................240
Używanie atrybutów i metod prywatnych ........................................241
Kontrolowanie dostępu do atrybutów ............................................245
Powrót do programu Opiekun zwierzaka ........................................249
Podsumowanie ...........................................................................252
Rozdział 9. Programowanie obiektowe. Gra Blackjack ........................255
Wprowadzenie do gry Blackjack ....................................................255
Wysyłanie i odbieranie komunikatów .............................................256
Tworzenie kombinacji obiektów ....................................................259
Wykorzystanie dziedziczenia do tworzenia nowych klas ...................263
Rozszerzanie klasy poprzez dziedziczenie ......................................263
Modyfikowanie zachowania odziedziczonych metod ........................269
Polimorfizm ................................................................................273
Tworzenie modułów .....................................................................273
Powrót do gry Blackjack ...............................................................277
Podsumowanie ...........................................................................287
Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib ............................289
Wprowadzenie do programu Mad Lib .............................................289
Przyjrzenie się interfejsowi GUI .....................................................291
Programowanie sterowane zdarzeniami .........................................292
Zastosowanie okna głównego .......................................................293
Używanie przycisków ...................................................................298
Tworzenie interfejsu GUI przy użyciu klasy .....................................300
Wiązanie widżetów z procedurami obsługi zdarzeń .........................303
Używanie widżetów Text i Entry oraz menedżera układu Grid ...........305
8 Python dla każdego. Podstawy programowania

Wykorzystanie pól wyboru ............................................................310


Wykorzystanie przycisków opcji .....................................................314
Powrót do programu Mad Lib ........................................................318
Podsumowanie ...........................................................................322
Rozdział 11. Grafika. Gra Pizza Panic ...................................................323
Wprowadzenie do gry Pizza Panic ..................................................323
Wprowadzenie do pakietów pygame i livewires ..............................325
Tworzenie okna graficznego .........................................................325
Ustawienie obrazu tła ..................................................................328
Układ współrzędnych ekranu graficznego .......................................331
Wyświetlanie duszka ...................................................................332
Wyświetlanie tekstu ....................................................................336
Wyświetlanie komunikatu .............................................................339
Przemieszczanie duszków ............................................................342
Radzenie sobie z granicami ekranu ...............................................344
Obsługa danych wejściowych z myszy ...........................................347
Wykrywanie kolizji ........................................................................350
Powrót do gry Pizza Panic .............................................................353
Podsumowanie ...........................................................................360
Rozdział 12. Dźwięk, animacja i rozwijanie programu.
Gra Astrocrash .................................................................361
Wprowadzenie do gry Astrocrash ..................................................361
Odczyt klawiatury ........................................................................363
Obracanie duszka .......................................................................365
Tworzenie animacji ......................................................................368
Przegląd obrazów eksplozji ...........................................................369
Wykorzystywanie dźwięku i muzyki ................................................371
Planowanie gry Astrocrash ...........................................................376
Utworzenie asteroidów .................................................................377
Obracanie statku ........................................................................380
Poruszanie statku .......................................................................382
Wystrzeliwanie pocisków ..............................................................385
Regulowanie tempa wystrzeliwania pocisków .................................388
Obsługa kolizji ............................................................................390
Dodanie efektów eksplozji ...........................................................393
Dodanie poziomów gry, rejestracji wyników
oraz tematu muzycznego ...........................................................397
Podsumowanie ...........................................................................405
Spis treści 9

Dodatek A. Opis pakietu livewires ......................................................407


Pliki archiwów .............................................................................407
Dodatek B. Opis pakietu livewires ......................................................409
Pakiet livewires ...........................................................................409
Klasy modułu games ...................................................................409
Funkcje modułu games ................................................................417
Stałe modułu games ...................................................................418
Stałe modułu color ......................................................................422
Skorowidz ........................................................................423
O autorze

Michael Dawson pracował zarówno jako programista, jak i projektant i producent gier
komputerowych. Oprócz praktycznego doświadczenia zdobytego w sferze produkcji gier
Mike uzyskał licencjat w dziedzinie informatyki na Uniwersytecie Południowej
Kalifornii. Obecnie uczy programowania gier na Wydziale Produkcji Gier Szkoły
Filmowej w Los Angeles. Mike uczył także studentów programowania gier w ramach
zajęć prowadzonych na UCLA Extension i Digital Media Academy w Stanfordzie.
Jest autorem trzech innych książek: Beginning C++ through Game Programming, Guide
to Programming with Python oraz C++ Projects: Programming with Text-Based Games.
Możesz odwiedzić jego stronę internetową pod adresem www.programgames.com,
aby dowiedzieć się więcej lub uzyskać pomoc w kwestiach dotyczących dowolnej
z jego książek.
Wstęp

Z ekranu wpatrywała się we mnie postać, której twarz wydała mi się znajoma — to
była moja twarz. Ziarnista i spikselizowana, ale pomimo wszystko moja. Patrzyłem
z obojętnym zaciekawieniem na moje oblicze poskręcane i powykrzywiane ponad
wszelką ludzką miarę, aż w końcu z mojej głowy wyskoczył embrion kosmity.
Głos za mną powiedział: „Chcesz to zobaczyć jeszcze raz?”.
Nie, to nie był jakiś koszmarny sen, to była moja praca. Pracowałem w firmie
produkującej i projektującej gry komputerowe. Musiałem także „zagrać główną rolę”
w naszej pierwszej produkcji, grze przygodowej, w której gracz goni mnie po ekranie
kliknięciami. A jeśli graczowi nie uda się w porę znaleźć rozwiązania… cóż, myślę, że
wiesz, czym to się kończy. Pracowałem także jako programista w ważnej firmie oferującej
usługi internetowe, podróżując w różne miejsca kraju. I chociaż te dwa kierunki pracy
mogą wydawać się całkiem różne, podstawowe umiejętności niezbędne do odniesienia
sukcesu w każdym z nich zaczęły się kształtować, gdy pisałem proste gry na moim
domowym komputerze jako dziecko.
Celem tej książki jest nauczenie Cię języka programowania Python oraz umożliwienie
Ci uczenia się programowania w taki sam sposób, w jaki uczyłem się ja — poprzez tworzenie
prostych gier. Nauka programowania poprzez pisanie programów, które bawią, ma w sobie
coś ekscytującego. Lecz nawet w przykładach, które są zabawne, spotkasz się z dozą
poważnego programowania. Omawiam wszystkie podstawowe tematy, jakich mógłbyś
oczekiwać w tekście o charakterze wprowadzenia, a nawet poza nie wykraczam. W dodatku
pokazuję koncepcje i techniki, które mógłbyś zastosować w bardziej mainstreamowych
projektach.
Jeśli programowanie jest dla Ciebie czymś nowym, dokonałeś właściwego wyboru.
Python jest doskonałym językiem dla początkujących. Ma przejrzystą i prostą składnię,
która sprawi, że zaczniesz pisać użyteczne programy niemal natychmiast. Python udostępnia
nawet tryb interaktywny, który oferuje bezzwłoczną informację zwrotną, co pozwoli Ci
na przetestowanie nowych pomysłów prawie natychmiast.
Jeśli trochę już przedtem programowałeś, to mimo wszystko dokonałeś właściwego
wyboru. Python ma w sobie całą moc i elastyczność, jakiej mógłbyś oczekiwać od
nowoczesnego, obiektowego języka programowania. Ale nawet przy całej jego mocy
14 Python dla każdego. Podstawy programowania

możesz być zaskoczony tym, jak szybko możesz budować programy. Faktycznie, koncepcje
tak szybko przekładają się na język komputera, że Python został nazwany
„programowaniem z szybkością myśli”.
Jak każda dobra książka i ta rozpoczyna się od początku. Pierwszą rzeczą, jaką omawiam,
jest instalacja Pythona w systemie Windows. Potem przedstawiam poszczególne koncepcje,
jedną po drugiej, poprzez pisanie małych programów w celu zademonstrowania każdego
kroku. Zanim zakończę książkę, omówię atrakcyjnie brzmiące tematy, takie jak struktury
danych, obsługa plików, wyjątki, projektowanie obiektowe oraz programowanie interfejsu
GUI i obsługi multimediów. Mam też nadzieję na pokazanie Ci nie tylko, jak programować,
ale także, jak tworzyć projekty. Nauczysz się, jak organizować swoją pracę, dzielić problemy
na możliwe do ogarnięcia kawałki oraz jak udoskonalać swój kod. Czasem spotkasz się
z wyzwaniami, ale nigdy nie będziesz przytłoczony. Przede wszystkim, ucząc się, będziesz
się dobrze bawić. I przy okazji utworzysz kilka małych, lecz fajnych gier komputerowych.
Pełny kod programów zaprezentowanych w tej książce wraz z niezbędnymi plikami
pomocniczymi możesz pobrać ze strony internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm). Strona ta zawiera również pliki instalacyjne
oprogramowania, które będzie Ci potrzebne do uruchamiania programów. Bardziej
szczegółowy opis tego, co jest dostępne na stronie internetowej, znajdziesz w dodatku A,
„Strona internetowa książki”.
Na całej trasie podróży poprzez treść tej książki umieszczam pewne drogowskazy
w celu podkreślenia ważnych koncepcji.

Wskazówka
To dobre rady, jakie doświadczeni programiści lubią przekazywać innym.

Pułapka
Istnieje kilka obszarów, w których łatwo o zrobienie błędu. Pokazuję je.

Sztuczka
To propozycje technik i skrótów, które ułatwią Twoje życie programisty.

W świecie rzeczywistym
Kiedy przeanalizujesz gry przedstawione w tej książce, pokażę Ci, jak występujące
w nich koncepcje są wykorzystywane w celach wykraczających poza tworzenie gier.

Sprawdź swoje umiejętności


Na końcu każdego rozdziału zaproponuję Ci kilka programów, jakie możesz napisać
w oparciu o przyswojone umiejętności.
1
Wprowadzenie.
Program Koniec gry

P
ragramowanie polega zasadniczo na spowodowaniu, 7,e
Nie jest to za bardzo techniczna definicja, ale mimo to

�")r
� tter coś zrobił.
okładna. Dzięki
poznaniu Pythona potrafisz utworzyć program- pro �Miewielkie narzędzie czy
też produkt biznesowy wyposażony w pełni profes�t�iczny interfejs u7,ytkownika
(ang. graphical user interface- GUl). Będzie ca��wój - coś, co sam wykonałeś
- i będzie robił dokładnie to, co mu kazałeś. P�mowanie jest po części nauką,

po części sztuką oraz jedną wiel ką p r zygod zpoczyn ając c zyta nie tego rozdziału,
wkraczasz na drogę programowania �),{ython. Z rozdziału tego dowiesz się:
• co to jest Python i co w nim �Vego wspaniałego;
• jak zainstalować Pytho �a iwoim komputerze;
i_
jak wypisywać tekst �nie;

• co to są komen ak ich używać;


• �ać zmtegrowane środowisko programistyczne do tworzenia,
jak wykorzy
edycji, ur�ania i zapisywania na dysku swoich programów.

Analiza programu Koniec gry


Projekt o nazwie Koniec gry, przedstawiony w tym rozdziale, sprowadza się do wyświetlenia
dwóch słów- cieszących się w świecie gier komputerowych złą sławą- "Koniec gry".
Na rysunku 1.1 pokazano ten program w działaniu.
16 Rozdział 1. Wprowadzenie. Program Koniec gry

Rysunek 1.1. Aż nadto znajomy komunikat pojawiający si ��


To, co widać na rysunku 1.1, nazywa się oknem konso �� o okno, w którym
�;cVe' jak okna graficznego
może hyć wyświetlany tylko tekst. Chocia7, nie tak sym
interfejsu użytkownika (ang. Graphical User Inte�'Ł__
...Jt�JI) aplikacje konsolowe są
łatwiejsze do napisania i stanowią dobry punkt � � dla początkującego programisty.
Program Koniec gry jest dość prosty; praw�\eJl wiąc, jest on jednym z najprostszych
programów w języku Python, jakie można .@ ać. Właśnie dlatego jest prezentowany
w tym rozdziale. Poprzez utworzenie�omnego programu poznajesz wszystkie

czynności konfiguracyjne niezbędn��poczęcia programowania w Pythonie, takie
jak instalacja tego języka w wo ·m �mie. Wykonujesz też cały proces tworzenia,
zapisywania na dysku i uruc ia�ia programu. Po opanowaniu tych wszystkich
podstawo �ch czynnoś�k,ę sz gotów zająć się większymi i bardziej interesującymi
programamL

W świecie �wistym
Program Koniec gry jest w gruncie rzeczy jedynie odmianą tradycyjnego
programu Witaj świecie, który wyświetla słowa "Witaj świecie" na ekranie i jest
często pierwszym programem, jaki pisze nowicjusz w celu postawienia swojego
pierwszego kroku w nowym języku. Jest to tak popularny pierwszy program, że
termin "Witaj świecie" stał się powszechnie zrozumiałym pojęciem w dziedzinie
programowania.

Co warto wiedzieć o Pythonie?


Python jest potężnym, lecz mimo to łatwym w użyciu językiem programowania
opracowanym przez Guida van Rossuma i opublikowanym po raz pierwszy w 1991 r.
Za pomocą Pythona można szybko napisać mały projekt. Ale Pythona cechuje także
Co warto wiedzieć o Pythonie? 17

dobra skalowalność i może być używany do tworzenia komercyjnych aplikacji


o kluczowym znaczeniu.
Jeśli zajrzysz do dokumentacji Pythona, hyć może zauważysz niepokojącą liczhę
odniesień do spamu, jaj i liczby 42. Wszystkie te odniesienia są wyrazem hołdu dla
angielskiej trupy komików Monty Python, która stała się inspiracją dla autora przy
wyborze nazwy języka. Mimo że Guido van Rossum wyhrał nazwę Python, nawiązując
do grupy komediowej, oficjalną maskotką języka stał się wąż pyton. (Co było faktycznie
najlepszym rozwiązaniem, ponieważ i tak byłoby dość trudno wkomponować twarze
sześciu brytyjskich komików w ikon\ programu).
Jest na świecie wiele języków programowania. Co takiego wspaniałego jest w Pythonie?
Pozwól, że Ci wyjaśnię.

Python jest łatwy w użyciu


Głównym celem każdego języka programowania jest zapełnie
programisty a komputerem. Większość popularnych językó
słyszałeś, takich jak Visual Basic, C# i J ava, jest uważana z �
��

��
�lu�1 ędzy mózgiem
�c zapewne
wysokiego poziomu,
�..syVwej. I tak jest rzeczywiście.
co oznacza, że są one bliższe języka człowieka niż maSZ)
Ale Python, ze swoimi prostymi i klarownymi re� � nawet jeszcze bliższy języka
angielskiego. Tworzenie programów w Pytho � LK proste, że zostało nazwane

"programowaniem z szybkością myśli". Łatwo� zystania z Pythona przekłada si\
na wydajność profesjonalnych progr.:_��ogramy pisane w Pythonie są krótsze
i są tworzone w krótszym czasie niż �ur w wielu innych popularnych językach.

Python jest mocnl'\ �


�by oczekiwać od nowoczesnego języka programowania.
Python ma całą moc, jakiej
Zanim skończysz lektur�1" książki, potrafisz pisać programy, które stosują interfejs
GUl, przetwarzają.f\�orzystują różnorodne struktury danych.
Python jest � zająca silnym językiem, aby zainteresować twórców
oprogramowania na całym świecie, a także takie firmy, jak Google, IBM, Industrial
Light + Magie, Microsoft, NASA, Red Hat, Verizon, Xcrox i Yahoo!. Python jest również
narzędziem wykorzystywanym przez profesjonalnych programistów gier. Kod tworzony
w Pythonie zawierają gry publikowane przez takich producentów, jak Electronic Arts,
2K Games i Disney Interactive Media Group.

Python jest językiem obiektowym


Programowanie obiektowe (ang. object-orientedpro gramming- OOP) to nowoczesne
podejście do rozwiązywania problemów przy użyciu komputerów. Ucieleśnia ono intuicyjną
metodę reprezentowania informacji i działań w programie. Z pewnością nie jest to jedyny
sposób pisania programów, ale zwłaszcza przy większych projektach stanowi często
najlepszą drogę.
18 Rozdział 1. Wprowadzenie. Program Koniec gry

Języki takie jak C#,Java i Python są obiektowe. Ale Python pod jednym względem je
przewyższa. W C# czyJavie programowanie OOP nie jest opcjonalne. To sprawia, że
krótkie programy stają się niepotrzebnie skomplikowane i niezbędna jest pewna ilość
wyjaśnień, zanim nowy programista będzie mógł zrobić coś znaczącego. W Pythonie
przyjęto inne podejście- korzystanie z technik OOP jest opcjonalne. Masz całą potęgę
OOP do swojej dyspozycji, ale możesz jej używać wtedy, kiedy rzeczywiście jej potrzebujesz.
Masz do napisania krótki program, który naprawdę nie wymaga stosowania OOP?
Nie ma problemu. Tworzysz duży projekt z udziałem zespołu programistów, w którym
korzystanic z OOP jest niczb<;dnc? Masz do tego odpowiednie narz<;dzic. Python daje Ci
moc i elastyczność.

Python jest językiem "klejącym" '


��To oznacza,
Python może zostać zintegrowany z innymi językami, takimi jak C, C+
że programista, używając Pythona, może spo7,ytkować pracę ju7�ko�
� w innym języku.
Oznacza to również, że może on (lub ona) wykorzystać siln �� �mych języków, jak
��, nie tracąc przy tym

na przykład większą szybkość, jaką mogą zaoferować C l
łatwości tworzenia kodu, która jest cechą charakteryst Vogramowania w Pythonie.



Kod Pythona działa wszędzie("\
��
Python pracuje na każdym sprzęcie, od pal o superkomputera Cray. I jeśli
przypadkiem nie masz superkompu � pokoju, możesz nadal używać Pythona
s
na maszynach z systemem Window · tosh lub Linux. A to tylko początek listy.
Programy napisane w Pythonie ezałeżne od platformy, co oznacza, że
niezależnie od systemu opera �eg6l, z którego korzystałeś przy tworzeniu swojego
i.._
programu, b<;dzic on działa �wolnym innym komputerze, na którym zainstalowano
� �z program na swoim PC, będziesz mógł przesłać pocztą
Pythona. Więc jeśli napi4z._
elektroniczną jego k � jemu przyjacielowi, który używa Linuksa lub swojej cioci,
która posiada M· rogram będzie działał (o ile Twój przyjaciel i ciocia mają Pythona
zainstalowanego na oich komputerach).

Python ma silne wsparcie ze strony społeczności


Większości języków programowania towarzyszą poświęcone im grupy dyskusyjne,
ale w przypadku Pythona istnieje coś, co nazywa si<; listą mailingową Python Tutor
("nauczyciel Pythona") i umożliwia początkującym programistom zadawanie tych
pierwszych pytań w bardziej nieformalny sposób. Lista jest dostępna na stronie
http://mail.python.or g!mailman!listinfo!t utor. Chociaż w nazwie listy jest wyraz "Tutor",
każdy, zarówno nowicjusz, jak i ekspert, może odpowiadać na pytania.
Istnieją też inne społeczności Pythona skupiające swoje zainteresowanie na różnych
obszarach tematycznych, ale wspólnym elementem, który je na ogół cechuje, jest
Konfiguracja Pythona w systemie Windows 19

przyjazność i otwartość. Tylko takie podejście ma sens, skoro sam język jest tak
przystępny dla początkujących.

Python jest bezpłatny z otwartym dostępem


do kodu źródłowego
Python jest bezpłatny. Możesz zainstalować go na swoim komputerze i nie płacisz ani
grosza. Ale licencja Pythona pozwala na wiele więcej. Możesz kopiować i modyfikować
kod Pythona. Wolno Ci nawet odsprzedawać Pythona, jeśli chcesz (ale na razie nie porzucaj
swojego dotychczasowego sposobu zarabiania na życie). Realizacja ideałów przyświecających
twórcom oprogramowania open source (z otwartym dostępem do kodu źródłowego) jest
po cz<;ści źródłem popularności i sukcesu Pythona.

Aby zainstalować Pythona w systemi s, wykonaj następujące czynności:


1. Pobierz instalator Pythona d �
poświęconej książce htt :/
instalacyjny znajduje s·

�u Windows dostępny na stronie
.helion.pl!ksiazki!pytdk3.htm. Plik
p�folderze Python folderu Software i ma nazw<;
python-3-l.msiA...\
:

2. Uruchom wind � instalator Pythona,python-3.1.msi. Rysunek 1.2


pokazuje in��tor w działaniu.
3. Zaakcept�yślną konfiguracj<;. Kiedy instalator zakończy prac<;, b<;dziesz
miał Pythona 3.1 w swoim systemie.

Wskazówka
Stronę poświęconą tej książce można znaleźć pod adresem
http:jjwww.helion.pljksiazkijpytdk3.htm. Zawiera ona kod każdego kompletnego
programu prezentowanego na stronach tej książki razem ze wszystkimi niezbędnymi
pomocniczymi plikami i instalatorami oprogramowania. Szczegółowy opis tego,
co jest udostępnione do pobrania, można znaleźć w dodatku A, "Strona
internetowa książki".
20 Rozdział 1. Wprowadzenie. Program Koniec gry

1Gl Python 3.1.1 Setup �


Select De5tination Directory

Please select a directory for the Python 3.1.1 files.

ld Python31

pyt h on
for
lc:\Python31\
windows
< Back

Rysunek 1.2. Twój komputer stanie się w �� em Pythona

Konfiguracja Pythona
w innych systemach o e
(}i'
acyjnych
Python może pracować w dosłownie d si· tkach innych systemów operacyjnych.
Wi<;c jeśli używasz czegoś innego � indows, nic omieszkaj odwiedzić oficjalnej
��p:�www.python.org, aby pobrać najnowszą wersję
strony internetowej Python
języka dla Twojej maszyny�gląda strona główna Pythona, możesz sprawdzić
na rysunku 1.3. �
l �::n : k

oże� reinstalowany na Twoim komputerze; aby jednak programy


prezentowane w tej książce działały poprawnie, musisz używać Pythona 3.

Wprowadzenie do IDLE
Standardowo w skład Pythona wchodzi zintegrowane środowisko programowania o nazwie
IDLE. Środowisko programowania to zestaw narzędzi, które ułatwiają pisanie programów.
Możesz je traktować jak procesor tekstu, którego możesz używać do tworzenia swoich
programów. Ale jest ono czymś wi<;ccj niż miejscem do tworzenia, zapisywania na dysku
i edytowania Twojego kodu - udostępnia tryb interaktywny oraz tryb skryptowy.
Wprowadzenie do IDLE 21

Rysunek 1.3. Odwiedź stronę główną Pythona, aby pobrać jego najnowszą wersję
i przeczytać masę informacji o języku

Programowanie w trybie interaktywnym


W końcu nadszedł czas, aby „pobrudzić sobie ręce” jakimś konkretnym programowaniem
w Pythonie. Najszybszym na to sposobem jest uruchomienie Pythona w trybie interaktywnym.
W tym trybie możesz powiedzieć Pythonowi, co ma zrobić, a on natychmiast zareaguje.

Pisanie swojego pierwszego programu


Aby rozpocząć swoją interaktywną sesję, z menu Start wybierz Wszystkie
programy/Python 3.1/IDLE (Python GUI). Powinieneś zobaczyć na swoim ekranie
coś bardzo podobnego do tego, co przedstawia rysunek 1.4.
To okno ma nazwę Python Shell (powłoka Pythona). Po znaku zachęty (>>>) wpisz
print("Koniec gry"), a potem naciśnij klawisz Enter. Interpreter reaguje przez wyświetlenie
na ekranie tekstu:
Koniec gry

Ta-dam! Napisałeś swój pierwszy program w Pythonie! Jesteś prawdziwym programistą


(pozostało Ci co prawda jeszcze trochę do nauki, ale to dotyczy nas wszystkich).
22 Rozdział 1. Wprowadzenie. Program Koniec gry

Rysunek 1.4. Python w trakcie interaktywnej sesji oczekuje na Twoje polecenie

Użycie funkcji print


Spójrz na wiersz, który wprowadziłeś, print("Koniec gry"). Zauważ, jakie to proste.
Bez żadnej wiedzy o programowaniu potrafiłeś zapewne odgadnąć, jaka jest funkcja tego
kodu. To jest właśnie kwintesencja Pythona, który jest zwięzły i przejrzysty. Docenisz to
jeszcze mocniej, kiedy dowiesz się, jak tworzyć w tym języku bardziej skomplikowane
rzeczy.
Funkcja print() wyświetla tekst ujęty w cudzysłowy, który umieszczasz wewnątrz
nawiasów. Jeśli nie umieścisz niczego wewnątrz nawiasów, zostanie wyprowadzony
pusty wiersz.

Pułapka
Python uwzględnia wielkość liter — nazwy funkcji składają się umownie z małych
liter. Dlatego polecenie print("Koniec gry") zostanie wykonane, ale polecenia
Print("Koniec gry") i PRINT("Koniec gry") już nie.

Nauka żargonu
Teraz, kiedy zostałeś programistą, musisz sypać wokół tymi wymyślnymi terminami,
które są zrozumiałe tylko dla programistów. Funkcja to jakby miniprogram, który
startuje i wykonuje pewne określone zadanie. Zadaniem funkcji print() jest wyświetlenie
Wprowadzenie do IDLE 23

jakiejś wartości (lub ciągu wartości). Uruchamiasz, czyli wywołujesz funkcję, używając
jej nazwy, po której należy umieścić parę nawiasów. Wykonałeś dokładnie tę czynność
w trybie interaktywnym, kiedy wprowadziłeś tekst print("Koniec gry"). Czasem
podajesz, czyli przekazujesz do funkcji wartości, od których będzie zależeć jej działanie.
Umieszczasz te wartości, zwane argumentami, między nawiasami. W przypadku
swojego pierwszego programu przekazałeś do funkcji print() argument "Koniec gry",
którego funkcja użyła do wyświetlenia komunikatu Koniec gry.

Wskazówka
Funkcje w Pythonie również zwracają wartości, czyli dostarczają informacje
z powrotem do tej części programu, która wywołała daną funkcję. Nazywają się
one wartościami zwracanymi. Dowiesz się więcej o wartościach zwracanych
w rozdziale 2.

W tym szczególnym przypadku możesz być jeszcze dokładniejszy, mówiąc, że wartość


"Koniec gry", którą przekazałeś do funkcji print(), jest łańcuchem. Nie oznacza to nic
innego jak ciąg znaków, takich, jakie znajdują się na Twojej klawiaturze. Nazwa „łańcuch”
może wydawać się dziwna — „tekst” lub „słowa” byłyby może bardziej zrozumiałe — ale
u jej źródła jest myśl, że tekst jest łańcuchem, czyli ciągiem znaków. Od strony technicznej
"Koniec gry" jest łańcuchowym literałem, ponieważ jest w sensie dosłownym ciągiem
znaków, które tworzą ten tekst.
Wiersz, który wprowadziłeś w interpreterze, jest traktowany również jako instrukcja.
W języku polskim zdanie wyraża kompletną myśl. W Pythonie instrukcja jest kompletnym
poleceniem. Powoduje wykonanie czegoś. Każdy program zawiera pewną liczbę instrukcji.
W końcu teraz, kiedy już jesteś programistą, możesz komuś oznajmić, że napisałeś
jakiś kod w Pythonie. Kod oznacza instrukcje programu. Możesz również używać
czasownika „kodować”, mając na myśli czynność programowania. Możesz na przykład
powiedzieć, że siedziałeś przy komputerze całą noc, pogryzając Hot Cheetos, popijając
Mountain Dew i kodując jak szalony.

Generowanie błędu
Komputery biorą wszystko dosłownie. Jeśli pomylisz się w nazwie funkcji, choćby to
dotyczyło tylko jednej litery, komputer nie będzie miał absolutnie żadnego pojęcia,
co masz na myśli. Na przykład jeśli w trybie interaktywnym wpiszę po znaku zachęty
primt("Koniec gry"), interpreter odpowie czymś takim:
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
primt("Koniec gry")
NameError: name 'primt' is not defined

W tłumaczeniu na język polski interpreter mówi: „Hę?”. Kluczowy wiersz komunikatu


o błędzie to NameError: name 'primt' is not defined. Jest to stwierdzenie, że interpreter
nie rozpoznaje słowa primt. Jako istota ludzka możesz zignorować moją literówkę
24 Rozdział 1. Wprowadzenie. Program Koniec gry

i zrozumiesz, co miałem na myśli. Komputery nie są tak wyrozumiałe. Na szczęście


z takimi błędami w programie (czyli pluskwami, ang. bugs) można sobie łatwo poradzić
przez poprawienie nieprawidłowej litery.

Co to jest podświetlanie składni?


Prawdopodobnie zauważyłeś, że słowa na ekranie są wyświetlane w różnych kolorach
(nie dotyczy to oczywiście książki). To stosowanie kolorów, zwane podświetlaniem
składni, pomaga szybko zrozumieć wprowadzoną treść poprzez jej wizualną kategoryzację.
A w tym szaleństwie kolorowania jest metoda. Słowa specjalne, które są częścią języka
Python, takie jak print, są wyświetlane na fioletowo. Łańcuchy znaków, takie jak "Koniec
gry", mają kolor zielony, a dane wyjściowe — wszystko to, co interpreter wyświetla jako
wynik tego, co wpisujesz — są wyróżnione kolorem niebieskim. Kiedy będziesz pisać
większe programy, ten system kolorów okaże się bardzo przydatny, ułatwiając Ci
ogarnięcie kodu jednym spojrzeniem i zauważenie ewentualnych błędów.

Programowanie w trybie skryptowym


Korzystanie z trybu interaktywnego daje Ci natychmiastową informację zwrotną.
Jest to wspaniałe, ponieważ od razu widzisz wyniki. Ale tryb interaktywny nie został
zaprojektowany do tworzenia programów, które możesz zapisać na dysku i uruchomić
później. Na szczęście IDLE Pythona oferuje także tryb skryptowy, w którym możesz
tworzyć, edytować, ładować i zapisywać swoje programy. Jest to jakby procesor tekstu
przystosowany do obsługi Twojego kodu. Prawdę mówiąc, możesz wykonywać tak znane
czynności, jak „znajdź i zamień” czy „wytnij i wklej”.

Pisanie swojego pierwszego programu (ponownie)


Możesz otworzyć okno trybu skryptowego z okna interaktywnego, którego używałeś
do tej pory. Z menu File (plik) wybierz New Window (nowe okno). Ukaże się nowe okno,
które wygląda dokładnie tak samo jak to na rysunku 1.5.
W tym nowym oknie skryptowym wpisz tekst print("Koniec gry") i naciśnij klawisz
Enter. Nic się nie dzieje! To dlatego, że jesteś w trybie skryptowym. To, co teraz robisz,
jest wprowadzaniem listy instrukcji dla komputera, które mają zostać wykonane później.
Po zapisaniu swojego programu na dysku możesz go uruchomić.

Zapisywanie i uruchamianie programu


Aby zapisać program na dysku, wybierz z menu File (plik) opcję Save As (zapisz jako).
Ja nadałem swojemu programowi nazwę koniec_gry.py. Aby później było można go
łatwo znaleźć, zapisałem go na pulpicie.
Wprowadzenie do IDLE 25

Rysunek 1.5. Twoje puste okno czeka. Python jest gotowy — możesz zacząć pisanie
programu w trybie skryptowym

Wskazówka
Pamiętaj, aby zapisywać swoje programy z rozszerzeniem .py. To umożliwia różnym
aplikacjom, nie wyłączając IDLE, rozpoznawanie tych plików jako programów
w języku Python.

Aby uruchomić swój program Koniec gry, po prostu wybieram z menu Run
(uruchom) opcję Run Module (uruchom moduł). Wtedy w oknie interaktywnym
wyświetla się wynik programu. Popatrz na rysunek 1.6.
Pewnie zauważysz, że okno interaktywne zawiera stary tekst, który pozostał po
moich poprzednich działaniach. Nadal widoczna jest instrukcja, którą wprowadziłem
w trybie interaktywnym, print("Koniec gry"), oraz jej wynik — komunikat Koniec gry.
Poniżej tego wszystkiego widać komunikat RESTART, świadczący o ponownym
uruchomieniu powłoki, a pod nim wynik uruchomienia mojego programu w trybie
skryptowym: Koniec gry.
Aby uruchomić swój program w IDLE, musisz najpierw go zapisać w postaci pliku
na dysku.
26 Rozdział 1. Wprowadzenie. Program Koniec gry

Rysunek 1.6. Wynik uruchomienia programu Koniec gry w środowisku IDLE

Tryb interaktywny wspaniale się nadaje do szybkiego wypróbowania małego pomysłu.


Tryb skryptowy sprawdza się doskonale przy pisaniu programów, które będzie można
uruchomić później. Łączenie obydwu trybów to znakomity sposób kodowania.
Chociaż wystarczy mi sam tryb skryptowy do napisania programu, to jednak zawsze
pozostawiam otwarte okno interaktywne. W trakcie pisania swoich programów w trybie
skryptowym przeskakuję do okna interaktywnego, aby wypróbować nowy pomysł lub
upewnić się, że mój sposób użycia jakiejś funkcji jest prawidłowy.
Okno skryptowe jest miejscem, gdzie dopracowuję swój produkt końcowy. Okno
interaktywne odgrywa rolę szkicownika, w którym mogę eksperymentować. Łączne ich
użycie pomaga mi pisać lepsze programy i robić to szybciej.

Powrót do programu Koniec gry


Do tej pory uruchamiałeś pewną wersję programu Koniec gry, wykorzystując IDLE.
Kiedy jesteś w trakcie pisania programu, uruchamianie go poprzez IDLE jest znakomitym
sposobem postępowania. Ale jestem pewien, że chciałbyś, aby Twoje gotowe produkty
funkcjonowały tak jak każdy inny program na Twoim komputerze. Chciałbyś, aby
użytkownik mógł uruchamiać Twój program po prostu przez dwukrotne kliknięcie
jego ikony.
Powrót do programu Koniec gry 27

Gdybyś spróbował uruchomić w ten sposób tę wersję programu Koniec gry, którą
wcześniej pokazałem, zobaczyłbyś, jak okno się pojawia i równie szybko znika.
Prawdopodobnie pomyślałbyś, że nic się nie wykonało. Ale coś jednak by się zdarzyło
— za szybko jednak, abyś mógł to zauważyć. Program zostałby uruchomiony, tekst Koniec
gry zostałby wyświetlony i program by się zakończył — wszystko w ułamku sekundy.
To, czego brakuje temu programowi, to sposób na zachowanie otwartego okna konsoli.
W tej poprawionej wersji programu Koniec gry — finalnym projekcie
prezentowanym w tym rozdziale — okno pozostaje otwarte, tak aby użytkownik mógł
zobaczyć komunikat. Po wyświetleniu tekstu Koniec gry program wyświetla także
wskazówkę Aby zakończyć program, naciśnij klawisz Enter. Kiedy tylko użytkownik
naciśnie klawisz Enter, program kończy pracę, a okno konsoli znika.
Prześledzę z Tobą cały kod, fragment po fragmencie. Program ten możesz pobrać
ze strony dedykowanej tej książce (http://www.helion.pl/ksiazki/pytdk3.htm), z folderu
rozdziału 1.; nazwa pliku to game_over.py. Ale lepiej będzie, jeśli napiszesz ten program
samodzielnie, a potem go uruchomisz.

Sztuczka
W systemie operacyjnym Windows możesz bezpośrednio otworzyć program
Pythona w IDLE przez kliknięcie ikony pliku prawym przyciskiem myszy i wybranie
opcji Edit with IDLE (edytuj za pomocą IDLE).

Używanie komentarzy
Dwa pierwsze wiersze programu wyglądają następująco:
# Koniec gry
# Przykład użycia funkcji print

Te dwa wiersze nie są instrukcjami, które komputer ma wykonać. Tak naprawdę to


komputer całkowicie je zignoruje. Te uwagi, zwane komentarzami, są przeznaczone dla
ludzi. Komentarze wyjaśniają kod programu w języku naturalnym (polskim, angielskim
lub jakimś innym). Komentarze są bezcenne dla innych programistów, bo pomagają im
zrozumieć Twój kod. Ale komentarze są pożyteczne także dla Ciebie. Przypominają Ci,
w jaki sposób zrealizowałeś coś, co może nie jest oczywiste na pierwszy rzut oka.
Do tworzenia komentarzy używa się symbolu kratki (#). Wszystko, co się znajduje
po tym symbolu (z wyjątkiem sytuacji, gdy jest on elementem łańcucha znaków),
aż do końca wiersza, jest komentarzem. Komentarze są ignorowane przez komputer.
Zwróć uwagę, że komentarze są w IDLE wyróżnione kolorem czerwonym.
Dobrym pomysłem jest rozpoczynanie wszystkich programów od kilku komentarzy,
podobnie jak ja zrobiłem w swoim przykładzie. Warto umieścić w nich tytuł programu
i jego cel. Chociaż ja tego tu nie zrobiłem, powinieneś także podać nazwisko programisty
i datę utworzenia programu.
Być może myślisz sobie: „Po co mi w ogóle komentarze? Skoro sam napisałem ten
program, to wiem, co on robi”. To może być prawdą miesiąc po napisaniu Twojego
28 Rozdział 1. Wprowadzenie. Program Koniec gry

kodu, ale doświadczeni programiści wiedzą, że po upływie kilku miesięcy od utworzenia


programu Twoje pierwotne intencje mogą już nie być takie jasne. Gdy chcesz
zmodyfikować jakiś stary program, kilka dobrze umieszczonych komentarzy może
bardzo ułatwić Ci życie.

W świecie rzeczywistym
Komentarze są jeszcze użyteczniejsze dla innego programisty, który musi
modyfikować napisany przez Ciebie program. Tego rodzaju sytuacje występują
często w świecie profesjonalnego programowania. Tak naprawdę szacuje się,
że większość czasu i wysiłku programisty jest zużywana na konserwację kodu,
który już istnieje. Wcale nierzadko programista otrzymuje zadanie zmodyfikowania
programu napisanego przez kogoś innego, a może się zdarzyć, że w pobliżu
nie będzie twórcy oryginalnego kodu, który mógłby odpowiedzieć na ewentualne
pytania. Tak więc dobre komentarze mają kluczowe znaczenie.

Używanie pustych wierszy


Z technicznego punktu widzenia kolejny wiersz naszego programu jest pusty. Komputer
na ogół ignoruje puste wiersze; w tym programie zostały także umieszczone tylko ze
względu na ludzi, którzy będą czytać kod. Puste wiersze mogą ułatwić czytanie programu.
Jeśli chodzi o mnie, zazwyczaj łączę wiersze zawierające wewnętrznie powiązany kod
w sekcje, które oddzielam pustym wierszem. W omawianym programie pustym
wierszem oddzieliłem komentarze od wywołania funkcji print.

Wypisywanie łańcucha znaków


Kolejny wiersz programu powinien być Ci znajomy:
print("Koniec gry")

To Twój stary przyjaciel — funkcja print. Ten wiersz, dokładnie tak samo
jak w trybie interaktywnym, wyświetla komunikat Koniec gry.

Czekanie na reakcję użytkownika


Ostatni wiersz programu:
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

wyświetla podpowiedź Aby zakończyć program, naciśnij klawisz Enter i czeka, aż


użytkownik naciśnie klawisz Enter. Kiedy użytkownik naciśnie ten klawisz, program się
zakończy. Jest to niezły sposób na zachowanie otwartego okna konsoli, dopóki
użytkownik nie zakończy pracy w aplikacji.
Jak można by się spodziewać, nadszedł czas, abym wyjaśnił, co takiego dzieje się
w tym wierszu. Ale zamierzam potrzymać Cię w niepewności. Przykro mi. Będziesz
musiał poczekać do następnego rozdziału, aby w pełni docenić ten jeden wiersz programu.
Podsumowanie 29

Podsumowanie
W tym rozdziale dowiedziałeś się wielu podstawowych rzeczy. Poznałeś nieco Pythona
i jego silne strony. Zainstalowałeś ten język na swoim komputerze i wykonałeś w nim
taką małą testową przejażdżkę. Nauczyłeś się wykorzystywać tryb interaktywny Pythona
do natychmiastowego wykonywania instrukcji programu. Zobaczyłeś, jak należy używać
trybu skryptowego do tworzenia, edytowania, zapisywania i uruchamiania dłuższych
programów. Dowiedziałeś się, jak wyświetlać tekst na ekranie i jak czekać na decyzję
użytkownika przed zamknięciem okna konsoli programu. Wykonałeś całą pracę
u podstaw niezbędną do rozpoczęcia przygody z programowaniem w Pythonie.

Sprawdź swoje umiejętności


1. Wygeneruj swój oryginalny błąd poprzez wprowadzenie w trybie
interaktywnym swojego ulubionego smaku lodów. Następnie, w charakterze
rekompensaty za swój zły uczynek, wprowadź instrukcję, która wypisze nazwę
Twoich ulubionych lodów.
2. Utwórz i zapisz program, który wyświetla Twoje imię i (zanim zakończy swoje
działanie) czeka, aż użytkownik naciśnie klawisz Enter. Następnie uruchom ten
program przez dwukrotne kliknięcie jego ikony.
3. Napisz program, który wyświetla Twój ulubiony cytat. W kolejnym wierszu
powinieneś podać nazwisko autora cytowanej wypowiedzi.
30 Rozdział 1. Wprowadzenie. Program Koniec gry
2
Typy, zmienne i proste
operacje wejścia-wyjścia.
Program Nieistotne fakty

T eraz, kiedy już zapoznałeś się z podstawami zapisywania i wykonywania programu,


pora umocnić swoje pozycje i stworzyć coś więcej. W tym rozdziale poznasz różne
sposoby klasyfikowania i przechowywania danych przez komputery i, co ważniejsze,
dowiesz się, jak te dane wykorzystywać w swoich programach. Zobaczysz nawet, jak
uzyskiwać informacje od użytkownika, aby Twoje programy stały się interaktywne.
W szczególności dowiesz się, jak:
 używać łańcuchów w potrójnych cudzysłowach i sekwencji specjalnych
w celu uzyskania większej kontroli nad tekstem,
 sprawić, aby Twoje programy wykonywały operacje matematyczne,
 przechowywać dane w pamięci komputera,
 używać zmiennych w celu uzyskiwania dostępu do tych danych
i manipulowania nimi,
 uzyskiwać dane wejściowe od użytkowników i tworzyć programy interaktywne.

Wprowadzenie do programu Nieistotne fakty


Poprzez połączenie umiejętności zaprezentowanych w tym rozdziale utworzysz program
Nieistotne fakty, którego działanie pokazano na rysunku 2.1.
32 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Rysunek 2.1. Ojej! Karol mógłby pomyśleć o diecie, zanim odwiedzi Słońce

Program pobiera trzy osobiste informacje od użytkownika: imię, wiek i wagę. Z tych
prozaicznych danych program potrafi wyprodukować kilka zabawnych choć trywialnych
faktów dotyczących tej osoby — na przykład, ile by ważyła na Księżycu.
Chociaż może wydawać się, że jest to prosty program (i taki jest w istocie), bardziej
Cię zainteresuje, kiedy sam go uruchomisz, ponieważ Ty podajesz dane wejściowe.
Zwrócisz większą uwagę na wyniki, ponieważ zostaną dopasowane do Twojej osoby.
Ta prawda odnosi się do wszystkich programów — od gier po aplikacje biznesowe.

Użycie cudzysłowów
przy tworzeniu łańcuchów znaków
Przykład łańcucha znaków, "Koniec gry", napotkałeś w poprzednim rozdziale.
Ale łańcuchy mogą być o wiele dłuższe i bardziej skomplikowane. Być może będziesz
chciał przekazać użytkownikowi kilka akapitów instrukcji. Albo mógłbyś chcieć
sformatować swój tekst w bardzo specyficzny sposób. Odpowiednie użycie
cudzysłowów może pomóc Ci w utworzeniu łańcuchów spełniających
te wszystkie wymagania.

Prezentacja programu Koniec gry 2.0


Program Koniec gry 2.0 stanowi ulepszenie swojego poprzednika, programu Koniec gry,
poprzez wyświetlenie atrakcyjniejszej wersji tego samego komunikatu, który informuje
gracza, że jego (lub jej) gra komputerowa dobiegła końca. Sprawdź na rysunku 2.2,
jak wygląda przykładowe uruchomienie programu.
Użycie cudzysłowów przy tworzeniu łańcuchów znaków 33

Rysunek 2.2. Teraz zrozumiałem — gra skończona

Ten program pokazuje, że prezentowanie tekstu na różne sposoby poprzez użycie


cudzysłowów jest dość proste. Jego kod znajdziesz na stronie poświęconej książce (http://
www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 2.; nazwa pliku to koniec_gry2.py.
# Koniec gry - wersja 2
# Demonstruje użycie cudzysłowów w łańcuchach znaków
print("Program 'Koniec gry' 2.0")

print("Taki sam", "komunikat", "jak przedtem,")

print("tylko",
"nieco",
"większy.")

print("Oto", end=" ")


print("on...")

print(
"""
_ __ ____ _ _ _____ ______ _____
| |/ / / __ \ | \ | | |_ _| | ____| / ____|
| ' / | | | | | \| | | | | |__ | |
| < | | | | | . ` | | | | __| | |
| . \ | |__| | | |\ | _| |_ | |____ | |____
|_|\_\ \____/ |_| \_| |_____| |______| \_____|

_____ _____ __ __
/ ____| | __ \ \ \ / /
| | __ | |__) | \ \_/ /
| | |_ | | _ / \ /
| |__| | | | \ \ | |
\_____| |_| \_\ |_|

"""
)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


34 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Używanie cudzysłowów wewnątrz łańcuchów


Widziałeś, jak tworzy się proste łańcuchy poprzez ujęcie tekstu w cudzysłów. Do utworzenia
wartości łańcuchowej możesz użyć pojedynczego (' ') albo podwójnego cudzysłowu
(""). Komputerowi jest wszystko jedno. Więc zapis 'Koniec gry' reprezentuje taki sam
łańcuch jak "Koniec gry". Ale spójrz na pierwsze wystąpienia łańcucha w programie:
print("Program 'Koniec gry' 2.0")

W tej instrukcji występują obydwa rodzaje cudzysłowu. Popatrz jeszcze raz na


rysunek 2.2. Widoczne są tylko znaki pojedynczego cudzysłowu, ponieważ stanowią one
część łańcucha, tak jak na przykład litera K. Lecz znaki podwójnego cudzysłowu nie są
częścią łańcucha. Odgrywają one rolę zewnętrznych ograniczników, które informują
komputer, gdzie łańcuch się zaczyna i gdzie się kończy. Więc jeśli użyjesz podwójnego
cudzysłowu do wyznaczenia granic swojego łańcucha, wewnątrz niego możesz użyć tyle
pojedynczych cudzysłowów, ile tylko chcesz. I na odwrót, jeśli zamkniesz swój łańcuch
w pojedynczym cudzysłowie, możesz wewnątrz tego łańcucha użyć dowolnej liczby
podwójnych cudzysłowów.
Gdy już użyjesz określonego rodzaju cudzysłowu jako ograniczników swojego łańcucha,
nie możesz stosować cudzysłowów tego samego typu wewnątrz tego łańcucha. To ma
swój sens, ponieważ kiedy komputer zobaczy drugie wystąpienie znaku rozpoczynającego
cudzysłów, będzie sądził, że łańcuch się właśnie zakończył. Na przykład tekst "Dzięki
słowom 'Houston, mamy problem.' Jim Lovell stał się jednym z najsławniejszych
amerykańskich astronautów." jest prawidłowym łańcuchem. Lecz zapis "Dzięki
słowom "Houston, mamy problem." Jim Lovell stał się jednym z najsławniejszych
amerykańskich astronautów." nie jest prawidłowy, ponieważ komputer potraktuje
drugie wystąpienie znaku podwójnego cudzysłowu jako koniec łańcucha. Tak więc
komputer widzi łańcuch "Dzięki słowom ", po którym pojawia się słowo Houston.
A ponieważ komputer nie ma pojęcia, co oznacza Houston, będziesz miał w swoim
programie paskudny błąd.

Wypisywanie wielu wartości


Możesz wypisać wiele wartości za pomocą pojedynczego wywołania funkcji print() —
wystarczy, że w nawiasach podasz listę wartości argumentów oddzielonych przecinkami.
Ja wypisuję wiele wartości w wierszu
print("Taki sam", "komunikat", "jak przedtem,")

Przekazuję do funkcji trzy argumenty: "Taki sam", "komunikat" i "jak przedtem,",


co skutkuje wyświetleniem przez kod tekstu Taki sam komunikat jak przedtem,. Zwróć
uwagę, że każda wartość jest wypisywana ze spacją w roli separatora. Jest to domyślne
zachowanie funkcji print().
Kiedy masz do czynienia z listą argumentów, po każdym przecinku oddzielającym
elementy listy możesz rozpocząć nowy wiersz. Kolejne trzy wiersze programu tworzą
Użycie cudzysłowów przy tworzeniu łańcuchów znaków 35

pojedynczą instrukcję, która wypisuje jeden wiersz tekstu tylko nieco większy.
Rozpoczynam nowy wiersz po każdym separatorze w postaci przecinka:
print("tylko",
"nieco",
"większy.")

Czasem przydaje się rozbicie listy argumentów na wiele wierszy, ponieważ może ono
zwiększyć czytelność kodu.

Definiowanie łańcucha końcowego funkcji print


Domyślnie funkcja print() wypisuje jako wartość końcową znak nowego wiersza.
To oznacza, że kolejne wywołanie funkcji print() wyświetliłoby tekst w następnym wierszu.
Na ogół jest to zgodne z Twoim oczekiwaniem, niemniej jednak masz możliwość
zdefiniowania swojego własnego łańcucha, który zostanie wypisany na końcu tekstu.
Możesz na przykład zdefiniować go tak, że kiedy wywołasz funkcję print(), ostatnim
wypisanym znakiem będzie spacja (zamiast znaku nowego wiersza). To by oznaczało,
że kolejna instrukcja print() rozpoczęłaby wypisywanie wartości bezpośrednio po tej
spacji. Korzystam z tej funkcjonalności w następnych dwóch wierszach programu:
print("Oto", end=" ")
print("on...")

Ten kod wypisuje tekst „Oto on...” w jednym wierszu. Dzieje się tak dlatego, że
w pierwszej instrukcji print() zdefiniowałem spację jako ostatni łańcuch do wypisania.
Tak więc instrukcja wypisuje tekst „Oto ” (łącznie ze spacją po ostatnim „o”), ale wyprowadza
znak nowego wiersza. Następna instrukcja print() rozpoczyna wypisywanie tekstu „on...”
bezpośrednio po spacji, która pojawia się po ostatnim „o” w tekście „Oto”. Efekt ten
uzyskuję przez zdefiniowanie spacji jako wartości parametru end funkcji print() za
pomocą kodu end=" ". W swojej własnej instrukcji print() możesz zdefiniować łańcuch,
który ma być wypisany jako ostatnia wartość, dokładnie tak, jak ja to zrobiłem, dodając
przecinek, po nim nazwę parametru end, znak równości i sam łańcuch. Możliwość
zdefiniowania swojego własnego łańcucha, który ma być wypisany przez instrukcję print()
na końcu, daje Ci większą elastyczność w sposobie formatowania Twoich danych wyjściowych.

Wskazówka
Nie martw się, jeśli jeszcze nie wiesz, co to jest parametr. Wszystkiego
o parametrach i przekazywaniu ich wartości dowiesz się w rozdziale 6.,
w podrozdziale „Używanie parametrów i wartości zwrotnych”.

Tworzenie łańcuchów w potrójnych cudzysłowach


Z pewnością najbardziej bajerancką częścią programu jest wypisywanie tekstu
„Koniec gry” w postaci wielkich liter. Odpowiada za to następujący łańcuch:
36 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

"""
_ __ ____ _ _ _____ ______ _____
| |/ / / __ \ | \ | | |_ _| | ____| / ____|
| ' / | | | | | \| | | | | |__ | |
| < | | | | | . ` | | | | __| | |
| . \ | |__| | | |\ | _| |_ | |____ | |____
|_|\_\ \____/ |_| \_| |_____| |______| \_____|

_____ _____ __ __
/ ____| | __ \ \ \ / /
| | __ | |__) | \ \_/ /
| | |_ | | _ / \ /
| |__| | | | \ \ | |
\_____| |_| \_\ |_|

"""

Widzimy coś, co nazywa się łańcuchem w potrójnym cudzysłowie. Jest to łańcuch


ujęty w parę ograniczników złożonych z trzech kolejnych znaków cudzysłowu. Tak jak
przedtem, nie ma znaczenia, jakiego rodzaju cudzysłowów użyjesz, o ile każdy z trzech
cudzysłowów będzie tego samego typu.
Jak możesz zauważyć, łańcuchy w potrójnych cudzysłowach mogą obejmować wiele
wierszy. Są wyświetlane w dokładnie tej postaci, w jakiej je wpisałeś w programie.

W świecie rzeczywistym
Jeśli podobają Ci się litery utworzone z wielu znaków, które wystąpiły w programie
Koniec gry 2.0, to z pewnością Ci się spodoba dziedzina sztuki o nazwie ASCII-Art.
Są to zasadniczo rysunki składające się z samych tylko znaków klawiatury. Przy
okazji wyjaśnię, że ASCII jest akronimem utworzonym od nazwy American Standard
Code for Information Interchange (amerykański standardowy kod do wymiany
informacji). Jest to kod, który zawiera 128 standardowych znaków. (Rodzaj sztuki
reprezentowany przez ASCII-Art nie jest nowy i nie narodził się wraz z komputerem.
W rzeczywistości pierwsze rysunki wykonane na maszynie do pisania datuje się
na 1898 r.).

Używanie sekwencji specjalnych


w łańcuchach znaków
Sekwencje specjalne umożliwiają umieszczanie w łańcuchach znaków o szczególnym
charakterze. Dają większą kontrolę nad wyświetlanym tekstem i elastyczność w jego
tworzeniu. Sekwencje specjalne, które będziesz wykorzystywał, składają się ze znaku
poprzedzonego lewym ukośnikiem. Wszystko to może wydawać się nieco tajemnicze,
ale kiedy już zobaczysz kilka sekwencji specjalnych w działaniu, przekonasz się, jak łatwo
ich używać.
Używanie sekwencji specjalnych w łańcuchach znaków 37

Prezentacja programu Zabawne podziękowania


Oprócz poinformowania gracza, że gra została zakończona, program często wyświetla
podziękowania, wymieniając wszystkie osoby, które tak ciężko pracowały, aby projekt
stał się rzeczywistością. Program Zabawne podziękowania wykorzystuje sekwencje
specjalne w celu osiągnięcia pewnych efektów, które byłyby niemożliwe do uzyskania
w inny sposób. Wynik działania programu pokazuje rysunek 2.3.

Rysunek 2.3. Proszę ograniczyć aplauz

Kod wygląda na pierwszy rzut oka trochę zagadkowo, ale wkrótce zrozumiesz go
w całości. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 2.; nazwa pliku
to zabawne_podziekowania.py.
# Zabawne podziękowania
# Demonstruje sekwencje specjalne

print("\t\t\tZabawne podziękowania")

print("\t\t\t \\ \\ \\ \\ \\ \\ \\ \\ \\ \\")
print("\t\t\t napisał")
print("\t\t\t Michael Dawson")
print("\t\t\t \\ \\ \\ \\ \\ \\ \\")

print("\nSpecjalne podziękowania należą się:")


print("mojemu fryzjerowi,", end=" ")
print("Henry\'emu \'Wielkiemu\', który nigdy nie mówi \"nie da się\".")

# dzwonek systemowy
print("\a")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


38 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Przesuwanie kursora tekstowego w prawo


do najbliższego punktu tabulacji
Czasem chcesz odsunąć jakiś tekst od lewego marginesu, od którego standardowo
zaczyna się wypisywanie. W procesorze tekstu mógłbyś użyć klawisza Tab. W przypadku
łańcuchów znaków możesz wykorzystać sekwencję specjalną zastępującą znak tabulacji
\t. Dokładnie to samo zrobiłem w wierszu poniżej:
print("\t\t\tZabawne podziękowania")

Użyłem sekwencji specjalnej \t trzy razy z rzędu. Więc kiedy program wyświetla
łańcuch, wyprowadza trzy znaki tabulacji, a potem tekst Zabawne podziękowania.
To sprawia, że tekst wygląda, jakby został wyświetlony niemal w środku okna konsoli.
Sekwencje tabulacji dobrze się nadają do odsuwania tekstu od lewego marginesu, jak
w tym programie, lecz są także doskonałym środkiem do ustawiania tekstu w kolumny.

Wypisywanie znaku lewego ukośnika


Jeśli wybiegałeś myślą w przód, być może zastanawiałeś się, w jaki sposób wyświetlić lewy
ukośnik, skoro komputer zawsze interpretuje go jako początek sekwencji specjalnej. Cóż,
rozwiązanie jest dość proste — wystarczy wpisać dwa lewe ukośniki, jeden po drugim.
Każdy z poniższych wierszy wypisuje trzy znaki tabulacji oraz pewną liczbę lewych ukośników
(jako wynik zastosowania odpowiedniej liczby sekwencji \\) oddzielonych spacjami:
print("\t\t\t \\ \\ \\ \\ \\ \\ \\ \\ \\ \\")
print("\t\t\t \\ \\ \\ \\ \\ \\ \\")

Wstawianie znaku nowego wiersza


Jedną z najprzydatniejszych sekwencji, jakie masz do dyspozycji, jest sekwencja nowego
wiersza. Ma ona postać \n. Dzięki użyciu tej sekwencji możesz wstawiać w swoich
łańcuchach znak nowego wiersza, mając na celu wyprowadzenie pustego wiersza
w miejscach, gdzie to jest potrzebne. Możesz umieścić sekwencję nowego wiersza
na samym początku łańcucha, aby oddzielić go od tekstu wypisanego poprzednio.
To właśnie zrobiłem w wierszu:
print("\nSpecjalne podziękowania należą się:")

Komputer po napotkaniu sekwencji \n wyprowadza pusty wiersz, a potem wypisuje


tekst Specjalne podziękowania należą się:.

Wstawianie znaku cudzysłowu


Wstawienie znaku cudzysłowu do łańcucha — nawet cudzysłowu tego samego typu co
cudzysłów wyznaczający granice tego łańcucha — jest proste. Wystarczy użyć sekwencji \'
w przypadku znaku pojedynczego cudzysłowu oraz \" w przypadku znaku podwójnego
Używanie sekwencji specjalnych w łańcuchach znaków 39

cudzysłowu. Sekwencje te znaczą „wstaw w tym miejscu znak cudzysłowu” i żadna


z nich nie zostanie błędnie potraktowana przez komputer jako znacznik końca Twojego
łańcucha. Właśnie tego sposobu użyłem, aby umieścić obydwa rodzaje cudzysłowów
w jednym wierszu tekstu:
print("Henry\'emu \'Wielkiemu\', który nigdy nie mówi \"nie da się\".")

Pierwszy i ostatni znak podwójnego cudzysłowu stanowią zewnętrzne ramy ograniczające


łańcuch. Aby łatwiej zrozumieć zawartość łańcucha, przypatrzmy się jego poszczególnym
częściom:
 fragment \'Wielkiemu\' jest wyświetlany w postaci 'Wielkiemu';
 każda sekwencja \' powoduje wyświetlenie znaku pojedynczego cudzysłowu;
 fragment \"nie da się\" przyjmuje na wyjściu postać "nie da się";
 obie sekwencje \" są wyświetlane jako znaki podwójnego cudzysłowu;
 fragment Henry\'ego przyjmuje postać Henry'ego;
 samotna sekwencja \' jest wyświetlana jako znak apostrofu.

Wywołanie sygnału dzwonka systemowego


Uruchamiając ten program, od razu zauważysz coś nowego. Usłyszysz krótki dźwięk!
Następna instrukcja w programie:
print("\a")

wywołuje sygnał dzwonka systemowego Twojego komputera. W tym celu


wykorzystuje sekwencję specjalną \a, która reprezentuje znak alarmu. Ile razy ją
wypiszesz, tyle razy zadźwięczy dzwonek. Możesz użyć łańcucha zawierającego tylko tę
sekwencję, tak jak ja zrobiłem, lub też umieścić ją wewnątrz dłuższego łańcucha. Możesz
nawet użyć tej sekwencji wiele razy, aby sygnał dzwonka zabrzmiał wielokrotnie.
Niektóre z sekwencji specjalnych działają zgodnie z założeniem tylko wtedy, gdy
uruchamiasz swój program bezpośrednio z systemu operacyjnego, a nie poprzez IDLE.
Dobrym przykładem jest tu sekwencja \a. Powiedzmy, że mam program, który po prostu
wypisuje sekwencję specjalną \a. Jeśli uruchamiam go poprzez IDLE, na ekranie
wyświetla mi się mały kwadratowy znaczek — to nie to, czego oczekiwałem. Ale jeśli
uruchamiam ten sam program bezpośrednio z systemu Windows, poprzez podwójne
kliknięcie ikony programu, zgodnie z moim zamierzeniem odzywa się dzwonek
systemowy mojego komputera.
Sekwencje specjalne okazują się nie takie złe, kiedy zobaczy się je w działaniu.
A mogą być całkiem przydatne. W tabeli 2.1 znajduje się podsumowanie kilku
najbardziej użytecznych.
40 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Tabela 2.1. Wybrane sekwencje specjalne


Sekwencja Opis
\\ Lewy ukośnik. Powoduje wyświetlenie jednego lewego ukośnika.
\' Pojedynczy cudzysłów. Powoduje wyświetlenie znaku pojedynczego
cudzysłowu.
\' Podwójny cudzysłów. Powoduje wyświetlenie znaku podwójnego cudzysłowu.
\a Alarm. Wywołuje sygnał dzwonka systemowego.
\n Nowy wiersz. Przenosi kursor na początek następnego wiersza.
\t Tabulator poziomy. Przesuwa kursor w prawo do najbliższego punktu tabulacji.

Konkatenacja i powielanie łańcuchów


Dowiedziałeś się, jak można wstawiać do łańcucha znaki specjalne, ale są takie rzeczy,
które możesz robić z całymi łańcuchami. Możesz na przykład połączyć dwa oddzielne
łańcuch w jeden większy. A nawet możesz powielać pojedynczy łańcuch tyle razy, ile Ci
się podoba.

Prezentacja programu Głupie łańcuchy


Program Głupie łańcuchy wyświetla na ekranie szereg łańcuchów. Wyniki zostały
pokazane na rysunku 2.4.

Rysunek 2.4. Łańcuchy na ekranie wyglądają inaczej niż w kodzie programu

Chociaż już zobaczyłeś łańcuchy wyświetlone na ekranie komputera, sposób ich


utworzenia będzie dla Ciebie całkowitą nowością. Kod tego programu znajdziesz na
Konkatenacja i powielanie łańcuchów 41

stronie internetowej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze


rozdziału 2.; nazwa pliku to glupie_lancuchy.py.
# Głupie łańcuchy
# Demonstruje konkatenację i powielanie łańcuchów

print("Możesz dokonać konkatenacji dwóch " + "łańcuchów za pomocą operatora '+'.")

print("\nTen łańcuch " + "może nie " + "sprawiać wiel" + "kiego wrażenia. " \
+ "Ale " + "pewnie nie wiesz," + " że jest\n" + "to jeden napraw" \
+ "d" + "ę" + " długi łańcuch, utworzony przez konkatenację " \
+ "aż " + "dwudziestu dwu\n" + "różnych łańcuchów i rozbity na " \
+ "sześć wierszy." + " Jesteś pod" + " wrażeniem tego faktu?\n" \
+ "Dobrze, ten " + "jeden " + "długi" + " łańcuch właśnie się skończył!")

print("\nJeśli jakiś łańcuch naprawdę Ci się podoba, możesz go powtórzyć.")


print("Kto na przykład nie lubi lodów? Masz rację, nikt. Ale jeśli naprawdę ")
print("je lubisz, powinieneś to wyrazić w adekwatny sposób:")
print("Lody!" * 10)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Konkatenacja łańcuchów
Konkatenacja łańcuchów oznacza ich połączenie w celu utworzenia jednego nowego
łańcucha. Prostego przykładu dostarcza pierwsza instrukcja print:
print("Możesz dokonać konkatenacji dwóch " + "łańcuchów za pomocą operatora '+'.")

Operator + łączy dwa łańcuchy "Możesz dokonać konkatenacji dwóch " i "łańcuchów
za pomocą operatora '+'." w jedną całość, tworząc nowy, dłuższy łańcuch. Jest to dość
intuicyjna operacja. Jest to jakby dodawanie łańcuchów przy użyciu takiego samego
symbolu, z jakiego się zawsze korzysta przy dodawaniu liczb.
Kiedy łączy się dwa łańcuchy, ich właściwe wartości zostają ze sobą zespolone
bez wstawiania między nie odstępu czy też innego separatora. Więc jeśli połączysz
dwa łańcuchy "dobra" i "noc", otrzymasz "dobranoc", a nie "dobra noc". W większości
przypadków będziesz chciał, aby łączone łańcuchy oddzielała spacja, więc nie zapomnij
o jej wstawieniu.
Kolejna instrukcja print pokazuje, że możesz łączyć łańcuchy bez żadnych
ograniczeń:
print("\nTen łańcuch " + "może nie " + "sprawiać wiel" + "kiego wrażenia. " \
+ "Ale " + "pewnie nie wiesz," + " że jest\n" + "to jeden napraw" \
+ "d" + "ę" + " długi łańcuch, utworzony przez konkatenację " \
+ "aż " + "dwudziestu dwu\n" + "różnych łańcuchów i rozbity na " \
+ "sześć wierszy." + " Jesteś pod" + " wrażeniem tego faktu?\n" \
+ "Dobrze, ten " + "jeden " + "długi" + " łańcuch właśnie się skończył!")

Komputer wyświetla jeden długi łańcuch, który został utworzony przez konkatenację
22 osobnych łańcuchów.
42 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Używanie znaku kontynuacji wiersza


Na ogół umieszczasz jedną instrukcję w każdym wierszu kodu. Ale nie jest to konieczne.
Możesz rozciągnąć pojedynczą instrukcję na kilka wierszy. Wszystko, co musisz zrobić,
to użyć znaku kontynuacji wiersza \ (który jest właśnie lewym ukośnikiem), tak jak ja
zrobiłem w powyższym kodzie. Możesz wstawić go wszędzie, gdzie normalnie
zastosowałbyś odstęp (choć nie wewnątrz łańcucha), by kontynuować pisanie instrukcji
w następnym wierszu. Komputer będzie działał tak, jakby to był jeden długi wiersz kodu.
Z punktu widzenia komputera długość wiersza programu jest nieistotna, ale jest
ważna dla ludzi. Jeśli jakiś wiersz kodu wydaje Ci się zbyt długi lub uważasz, że byłby
bardziej czytelny w postaci kilku wierszy, użyj znaku \, aby go podzielić na części.

Powielanie łańcuchów
Kolejna nowa koncepcja zaprezentowana w omawianym programie została zilustrowana
w poniższym wierszu:
print("Lody!" * 10

W tym wierszu tworzony jest nowy łańcuch, "Lody!Lody!Lody!Lody!Lody!Lody!


Lody!Lody!Lody!Lody!", który zostaje wyświetlony na ekranie. Notabene jest to łańcuch
"Lody!" powtórzony 10 razy.
Podobnie jak operator konkatenacji, operator powielania (*) jest dość intuicyjny.
Jest to taki sam symbol, jaki jest używany do mnożenia liczb w komputerze, więc
zastosowanie go do powielania łańcucha ma swój sens. To tak, jakbyś mnożył łańcuch.
Aby powielić łańcuch, wystarczy umieścić między nim a liczbą powtórzeń operator *.

Operacje na liczbach
Do tej pory używałeś łańcuchów do reprezentowania tekstu. To tylko jeden typ wartości.
Komputery pozwalają na przedstawianie informacji także na inne sposoby. Jedną z najbardziej
podstawowych i zarazem najważniejszych form informacji są liczby. Liczby są
wykorzystywane w prawie każdym programie. Czy piszesz grę, np. kosmiczną strzelankę,
czy pakiet do zarządzania domowymi finansami, musisz dysponować jakimś sposobem
reprezentowania liczb. Jakby nie było, musisz się zajmować obsługą listy najlepszych
wyników lub sprawdzaniem sald rachunków. Na szczęście Python oferuje kilka różnych
typów liczb, które mogą zaspokoić potrzeby związane z programowaniem gier lub innych
aplikacji.

Prezentacja programu Zadania tekstowe


Kolejny program wykorzystuje te okropne zadania tekstowe. Jak się domyślasz, chodzi
o ten ich rodzaj, który zdaje się zawsze dotyczyć dwóch pociągów wyruszających z różnych
miast w tym samym czasie, które jadą naprzeciw siebie … i budzi na nowo koszmar
Operacje na liczbach 43

z gimnazjalnej algebry — pociągi niechybnie się zderzą. Nie obawiaj się jednak. Nie
będziesz musiał rozwiązywać ani jednego zadania tekstowego, ani nawet wykonywać
jakichkolwiek matematycznych obliczeń — całą pracę wykona komputer. Program
Zadania tekstowe jest tylko zabawnym (mam nadzieję) sposobem eksploracji działań
na liczbach. Sprawdź na rysunku 2.5, jak wygląda jego przykładowe uruchomienie.

Rysunek 2.5. W Pythonie możesz dodawać, odejmować, mnożyć, dzielić


oraz prowadzić rejestr wagi ciężarnych hipopotamic

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 2.; nazwa pliku
to zadania_tekstowe.py.
# Zadania tekstowe
# Liczby i działania matematyczne

print("Ciężarna hipopotamica ważąca 1500 kg rodzi 45-kilogramowe młode,")


print("ale potem zjada 25 kg paszy. Ile wynosi jej nowa waga?")
input("Aby się dowiedzieć, naciśnij klawisz Enter.")
print("1500 - 45 + 25 =", 1500 - 45 + 25)

print("Poszukiwacz przygód wraca z udanej wyprawy i kupuje każdemu ze swoich")


print("6 towarzyszy 3 butelki piwa. Ile butelek zostało zakupionych?")
input("Aby się dowiedzieć, naciśnij klawisz Enter.")
print("6 * 3 =", 6 * 3)

print("Należność za obiad w restauracji wynosi razem z napiwkiem 159 zł, a Ty")


print("postanawiasz ze swoimi przyjaciółmi podzielić ją na 4 równe części. Ile")
print("każde z Was będzie musiało zapłacić?")
input("Aby się dowiedzieć, naciśnij klawisz Enter.")
print("159 / 4 =", 159 / 4)
44 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

print("\nGrupa 4 piratów znajduje skrzynię, a w niej 107 złotych monet, i")


print("postanawia podzielić zdobycz po równo. Ile monet otrzyma każdy z nich?")
input("Aby się dowiedzieć, naciśnij klawisz Enter.")
print("107 // 4 =", 107 // 4)

print("\nTa sama grupa 4 piratów dzieli między siebie po równo 107 złotych")
print("monet ze znalezionej skrzyni. Ile monet zostanie po podziale?")
input("Aby się dowiedzieć, naciśnij klawisz Enter.")
print("107 % 4 =", 107 % 4)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Typy liczbowe
W programie Zadania tekstowe używane są liczby. To oczywiste. Ale mniej oczywisty
może być fakt, że w programie wykorzystano dwa różne typy liczb. Python umożliwia
programistom wykorzystywanie kilku różnych typów liczb. Dwa typy używane w tym
programie, prawdopodobnie występujące najczęściej, to liczby całkowite (ang. integers)
oraz liczby zmiennoprzecinkowe (ang. floating-point numbers, floats). Liczby całkowite
to liczby bez części ułamkowej. Można je opisać inaczej jako liczby, które można zapisać
bez kropki dziesiętnej. Przykładowe liczby całkowite to 1, 27, -100 i 0. Liczby
zmiennoprzecinkowe zawierają kropkę dziesiętną; ich przykładami są 2.376, -99.1 i 1.0.

Stosowanie operatorów matematycznych


Dzięki operatorom matematycznym możesz przekształcić swój komputer w kosztowny
kalkulator. Operatory te powinny wyglądać całkiem znajomo. Na przykład kod 1500 - 45 + 25
oznacza odjęcie wartości 45 od 1500, a potem dodanie liczby 25, zanim zostanie wyświetlony
wynik 1480. Używając języka technicznego, powiemy, że obliczana jest wartość wyrażenia
1500 - 45 + 25, która wynosi 1480. Wyrażenie to nic innego jak ciąg wartości połączonych
operatorami, który można uprościć do innej wartości. Kod 6 * 3 powoduje pomnożenie
liczby 6 przez 3 i wyświetlenie wyniku 18. A kod 159 / 4 oznacza podzielenie liczby 159
przez 4 i wypisanie wyniku 39.75 w postaci liczby zmiennoprzecinkowej.
Wszystkie omówione do tej pory operatory matematyczne są zapewne dobrze Ci znane —
ale popatrz na kolejne obliczenie, 107 // 4. Użycie // jako operatora matematycznego jest dla
Ciebie prawdopodobnie czymś nowym. Występujące w wyrażeniu dwa ukośniki (//)
reprezentują dzielenie całkowite, w którym wynik jest zawsze liczbą całkowitą (ewentualna
część ułamkowa jest ignorowana). Różni się ono od dzielenia zmiennoprzecinkowego
z operatorem /, którego przykład poznałeś w poprzednim akapicie (przy którym część
ułamkowa wyniku nie jest ignorowana). Tak więc wynik wyrażenia 107 // 4 to 26.
Następne obliczenie, 107 % 4, może również sprawić, że podrapiesz się po głowie.
Użyty w nim symbol % to operator modulo, który wyznacza resztę z dzielenia
całkowitego. Tak więc wartością wyrażenia 107 % 4 jest liczba 3, czyli część ułamkowa
wyniku dzielenia 107 / 4 pomnożona przez 4.
Tabela 2.2 podsumowuje niektóre użyteczne operatory matematyczne.
Pojęcie zmiennych 45

Tabela 2.2. Przydatne operatory matematyczne


Operator Opis Przykład użycia Wartość wyrażenia
+ Dodawanie 7 + 3 10
- Odejmowanie 7 - 3 4
* Mnożenie 7 * 3 21
/ Dzielenie 7 / 3 2.3333333333333335
(zmiennoprzecinkowe)
// Dzielenie (całkowite) 7 // 3 2
% Modulo 7 % 3 1

Zwróć uwagę na pozycję w tabeli 2.2 odnoszącą się do dzielenia zmiennoprzecinkowego.


Wynika z niej, że 7 podzielone przez 2 równa się 2.3333333333333335. Choć wynik został
obliczony z dość dobrym przybliżeniem, to nie jest jednak całkowicie dokładny. Trzeba
o tym pamiętać, kiedy używa się liczb zmiennoprzecinkowych, chociaż takie przybliżenia
są zupełnie wystarczające w większości zastosowań.

Wskazówka
Moduł decimal zapewnia obsługę dokładnej dziesiętnej arytmetyki
zmiennoprzecinkowej. Aby dowiedzieć się więcej, zajrzyj do dokumentacji Pythona.

Pojęcie zmiennych
Dzięki zmiennym, które stanowią fundamentalny aspekt programowania, możesz
przechowywać informacje oraz nimi manipulować. Python pozwala na tworzenie
zmiennych w celu organizowania informacji i uzyskiwania do nich dostępu.

Prezentacja programu Pozdrawiacz


Sprawdź na rysunku 2.6, jak wyglądają wyniki programu Pozdrawiacz.
Na zrzucie ekranu przedstawionym na rysunku 2.6 ten program wygląda
jak coś, co już kiedyś pisałeś. Ale wewnątrz kodu czai się nowa i potężna koncepcja
zmiennych. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 2.; nazwa pliku
to pozdrawiacz.py.
# Pozdrawiacz
# Demonstruje użycie zmiennej

name = "Ludwik"

print(name)

print("Cześć,", name)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


46 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Rysunek 2.6. Pozdrowienia dla wszystkich Ludwików na świecie

Tworzenie zmiennych
Zmienna stanowi sposób na przypisanie informacji nazwy i przez to umożliwienie
dostępu do niej. Nie musisz dokładnie wiedzieć, gdzie w pamięci komputera jakaś
informacja jest przechowywana, albowiem możesz się do niej dostać dzięki użyciu
zmiennej. Jest to tak, jakbyś dzwonił do swojego przyjaciela, wybierając numer jego
telefonu komórkowego. Nie musisz wiedzieć, w jakim miejscu miasta przebywa Twój
przyjaciel, aby się z nim skontaktować. Wystarczy, że naciśniesz przycisk, i już go masz.
Lecz zanim użyjesz zmiennej, musisz ją utworzyć, tak jak w wierszu poniżej:
name = "Ludwik"

Ten wiersz zawiera instrukcję przypisania. Tworzy ona zmienną o nazwie name
i przypisuje jej wartość — poprzez tę zmienną odwołujemy się do łańcucha "Ludwik".
Generalnie instrukcje przypisania służą do nadania zmiennej wartości. Jeśli jakaś
zmienna jeszcze nie istnieje, co miało miejsce w przypadku zmiennej name, jest tworzona,
a następnie zostaje jej przypisana wartość.

Pułapka
Z technicznego punktu widzenia instrukcja przypisania zapamiętuje wartość
znajdującą się po prawej stronie znaku równości w pamięci komputera, a zmienna
po lewej stronie tylko odwołuje się do tej wartości (i nie przechowuje jej
bezpośrednio). Dlatego pythonowi puryści powiedzieliby, że zmienna otrzymuje
wartość, a nie że wartość zostaje jej przypisana. Ja jednak używam sformułowań
„otrzymuje” i „zostaje jej przypisana” zamiennie w zależności od tego, co w danym
kontekście wydaje się najbardziej klarowne.
Dowiesz się więcej o implikacjach sytuacji, gdy zmienne odwołują się do wartości
(zamiast je przechowywać), w rozdziale 5., w podrozdziale „Referencje
współdzielone”
Pojęcie zmiennych 47

Wykorzystywanie zmiennych
Kiedy zmienna zostaje utworzona, odwołuje się do pewnej wartości. Wygoda posługiwania się
zmienną polega na tym, że może być ona używana dokładnie tak samo jak wartość,
do której się odwołuje. Więc wykonanie instrukcji zawartej w wierszu:
print(name)

skutkuje wyświetleniem łańcucha "Ludwik" dokładnie tak, jak wykonanie instrukcji


print("Ludwik"). A wiersz
print("Cześć,", name)

wyświetla wartość łańcucha "Cześć,", potem spację i wartość łańcucha "Ludwik". W tym
przypadku używam zamiast łańcucha "Ludwik" zmiennej name, otrzymując taki sam wynik.

Nazwy zmiennych
Jako dumny rodzic swojego programu wybierasz nazwy występujących w nim zmiennych.
W przypadku tego programu zdecydowałem się nazwać swoją zmienną name (imię),
ale równie dobrze mógłbym użyć nazwy osoba, facet lub alfa7345690876, a program
wykonywałby się dokładnie tak samo. Istnieje kilka reguł, których należy przestrzegać,
aby tworzyć prawidłowe nazwy zmiennych. Jeśli tylko utworzysz nieprawidłową nazwę,
program Cię o tym powiadomi, zgłaszając błąd. Dwie najważniejsze reguły są następujące:
1. Nazwa zmiennej może zawierać tylko cyfry, litery i znaki podkreślenia.
2. Nazwa zmiennej nie może zaczynać się od cyfry.
Oprócz reguł tworzenia prawidłowych nazw zmiennych istnieją pewne zalecenia,
do których stosują się bardziej doświadczeni programiści przy tworzeniu dobrych nazw
zmiennych — jeśli już jakiś czas programowałeś, poznałeś przepaść, jaka dzieli prawidłową
nazwę zmiennej od dobrej. (Jedną radę dam Ci natychmiast: nigdy nie nazywaj zmiennej
alfa7345690876).
 Wybieraj nazwy opisowe. Nazwy zmiennych powinny być na tyle klarowne,
aby inny programista po spojrzeniu na nazwę miał dobre wyobrażenie o tym,
co ona reprezentuje. Więc na przykład używaj nazwy wynik zamiast w. (Jedyny
wyjątek od tej reguły dotyczy zmiennych używanych przez krótki okres.
Programiści często nadają tym zmiennym krótkie nazwy, takie jak x. Ale to jest
w porządku, bo poprzez użycie nazwy x programista daje jasno do zrozumienia,
że zmienna reprezentuje chwilowe miejsce przechowywania wartości).
 Bądź konsekwentny. Istnieją różne szkoły sposobu zapisywania
wielowyrazowych nazw zmiennych. Czy używać nazwy wysoki_wynik, czy też
wysokiWynik? Ja używam stylu ze znakami podkreślenia. Lecz nie jest ważne,
jaką metodę stosujesz, o ile jesteś konsekwentny.
 Przestrzegaj tradycji języka. Pewne konwencje nazewnicze stały się już tradycją.
Na przykład w większości języków (łącznie z Pythonem) nazwy zmiennych
48 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

rozpoczynają się od małej litery. Inną tradycją jest unikanie stosowania


podkreślenia jako pierwszego znaku nazw zmiennych. Nazwy, które
rozpoczynają się od znaku podkreślenia, mają w Pythonie specjalne znaczenie.
 Zachowuj kontrolę nad długością nazw. Wydaje się to przeczyć pierwszemu
wskazaniu: wybieraj nazwy opisowe. Czy osobiste_sprawdzanie_salda_rachunku
nie jest znakomitą nazwą zmiennej? Być może nie. Długie nazwy zmiennych
mogą prowadzić do problemów. Mogą sprawić, że instrukcje będą mało
czytelne. Poza tym im dłuższa nazwa zmiennej, tym większa możliwość
pomyłki. Traktując to jako wskazówkę, staraj się utrzymywać długość nazw
zmiennych poniżej 15 znaków.

Sztuczka
Kod samodokumentujący jest pisany w taki sposób, żeby było łatwo zrozumieć,
co się dzieje w programie niezależnie od ewentualnych komentarzy. Wybór dobrych
nazw zmiennych jest znakomitym krokiem w kierunku tego rodzaju kodu.

Pobieranie danych wprowadzanych


przez użytkownika
Po docenieniu tego wszystkiego, co miał do zaoferowania program Pozdrawiacz, możesz
wciąż myśleć: „No i co z tego”? Tak, mógłbyś napisać program, który robi dokładnie to
samo, co Pozdrawiacz, bez zadawania sobie trudu tworzenia jakiś śmiesznych zmiennych.
Lecz aby robić rzeczy o podstawowym znaczeniu, łącznie z pobieraniem i przechowywaniem
danych wprowadzanych przez użytkownika oraz manipulowaniem nimi, potrzebujesz
zmiennych. Popatrz na kolejny program, który wykorzystuje dane wejściowe do
utworzenia spersonalizowanego pozdrowienia.

Prezentacja programu Osobisty pozdrawiacz


Program Osobisty pozdrawiacz dodaje do programu Pozdrawiacz tylko jeden, ale za to
bardzo fajny element — wprowadzanie danych przez użytkownika. Zamiast wykorzystywać
wartość predefiniowaną, komputer pozwala użytkownikowi wprowadzić swoje imię,
a potem używa go do powiedzenia mu „cześć”. Program ten został przedstawiony na
rysunku 2.7.
Pobieranie danych wprowadzanych przez użytkownika nie jest zbyt trudne. W rezultacie
kod nie różni się aż tak bardzo. Kod tego programu możesz znaleźć na stronie internetowej
tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 2.; nazwa pliku
to osobisty_pozdrawiacz.py.
Pobieranie danych wprowadzanych przez użytkownika 49

Rysunek 2.7. Teraz zmiennej name zostaje przypisany łańcuch znaków na podstawie tego,
co wprowadzi użytkownik; może to być Robert

# Osobisty pozdrawiacz
# Demonstruje pobieranie danych wprowadzanych przez użytkownika

name = input("Cześć. Jak masz na imię? ")

print(name)

print("Cześć,", name)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Używanie instrukcji input()


Jedynym wierszem, który się zmienił, jest instrukcja przypisania:
name = input("Cześć. Jak masz na imię? ")

Lewa strona instrukcji jest dokładnie taka sama jak w programie Pozdrawiacz.
Tak jak przedtem tworzona jest zmienna name i zostaje jej przypisana wartość. Lecz tym
razem prawa strona instrukcji przypisania jest wywołaniem funkcji input(). Funkcja
input() pobiera pewien tekst od użytkownika. Przyjmuje też argument w postaci
łańcucha znaków, którego używa do poproszenia użytkownika o wprowadzenie tego
tekstu. W tym przypadku argumentem, który przekazałem do funkcji input(), jest
łańcuch "Cześć. Jak masz na imię? ". Jak możesz sprawdzić na rysunku 2.7, funkcja
input() faktycznie używa tego łańcucha, aby zachęcić użytkownika do wprowadzenia
swojego imienia. Funkcja input() czeka, aż użytkownik coś wprowadzi. Po naciśnięciu
przez użytkownika klawisza Enter funkcja input() zwraca wszystko, co użytkownik
wpisał, w postaci łańcucha. Ten łańcuch — wartość zwrotna wywołania funkcji — jest
tym, co otrzymuje zmienna name. Aby sobie lepiej uzmysłowić, jak to działa, wyobraź
50 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

sobie, że w instrukcji przypisania wywołanie funkcji input() zostaje zastąpione


łańcuchem znaków wprowadzonym przez użytkownika. Oczywiście kod w programie nie
ulega zmianie, ale wyobrażenie sobie wartości zwracanej przez funkcję zamiast jej
wywołania pomaga w przyswojeniu sobie sposobu użycia wartości zwrotnych.
Jeśli wywołania funkcji, argumenty i wartości zwrotne nie są nadal dla Ciebie
całkowicie jasne, jest jeszcze jeden sposób ich zilustrowania: użycie funkcji input() jest
jak zamówienie pizzy. Sama funkcja input() to jakby pizzeria. Dzwonisz do pizzerii, żeby
złożyć zamówienie i wywołujesz funkcję input(), aby ją uruchomić. Kiedy dzwonisz do
pizzerii, podajesz jakąś informację w rodzaju „pepperoni”. Kiedy wywołujesz funkcję
input(), przekazujesz jej argument w rodzaju "Cześć. Jak masz na imię? ". Po zakończeniu
Twojej telefonicznej rozmowy z pizzerią jej pracownicy dostarczają pizzę pepperoni pod
drzwi Twojego domu. Analogicznie, kiedy wykonasz wywołanie funkcji input(), funkcja
zwróci taki łańcuch znaków, jaki wprowadził użytkownik.
Pozostała część programu Osobisty pozdrawiacz działa dokładnie tak samo
jak program Pozdrawiacz. To, w jaki sposób zmienna name otrzymuje swoją wartość,
jest komputerowi obojętne. Tak więc wiersz:
print(name)

wyświetla wartość zmiennej name. Podczas gdy wiersz:


print("Cześć,", name)

wyświetla łańcuch "Cześć,", za nim spację, a na końcu wartość zmiennej name.


W tym momencie wiesz już wystarczająco dużo, aby zrozumieć ostatni wiersz w tych
wszystkich konsolowych programach. Zadaniem ostatniego wiersza jest czekanie,
aż użytkownik naciśnie klawisz Enter:
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

I to dokładnie robi poprzez funkcję input(). Jako że nie dbam o to, co wprowadzi
użytkownik — wystarczy, że naciśnie klawisz Enter — inaczej niż poprzednio, nie przypisuję
wartości zwróconej przez funkcję input() do żadnej zmiennej. Może się komuś wydawać
dziwne, że otrzymuję wartość zwrotną i nic z nią nie robię, ale taki jest mój wybór.
Jeśli nie przypiszę wartości zwrotnej do zmiennej, komputer ją po prostu zignoruje.
Więc jak tylko użytkownik naciśnie klawisz Enter, kończy się wywołanie funkcji input()
oraz program, a okno konsoli się zamyka.

Używanie metod łańcucha


Python ma bogaty zestaw narzędzi do obsługi łańcuchów. Jednym z typów tych narzędzi
są metody łańcucha. Umożliwiają one tworzenie nowych łańcuchów ze starych. Możesz
zrobić wszystko — od prostych czynności, takich jak utworzenie łańcucha, który jest
tylko wersją oryginalnego łańcucha składającą się z samych wielkich liter, do złożonych
operacji, takich jak utworzenie nowego łańcucha, który jest wynikiem całego ciągu
zawiłych podstawień liter.
Używanie metod łańcucha 51

Prezentacja programu Manipulacje cytatami


Według Marka Twaina „sztuka prorokowania jest bardzo trudna, szczególnie w odniesieniu
do przyszłości”. Chociaż jest trudno przepowiedzieć dokładnie przyszłość, to wciąż
zabawne jest czytanie przewidywań ekspertów dotyczących przyszłości techniki. Dobrym
przykładem jest wypowiedź: „Myślę, że istnieje światowy rynek dla może pięciu komputerów”.
Powiedział to w 1943 r. prezes IBM, Thomas Watson. Program Manipulacje cytatami,
który napisałem, wypisuje powyższy cytat na różne sposoby, wykorzystując metody
łańcucha. (Na szczęście mogłem napisać ten program, ponieważ przypadkiem posiadam
komputer numer 3). Popatrz na przykładowe jego uruchomienie przedstawione na
rysunku 2.8.

Rysunek 2.8. Ta nieco nietrafna prognoza została wyświetlona na różne sposoby


za pomocą metod łańcucha

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 2.; nazwa pliku to
manipulacje_cytatami.py.
# Manipulacje cytatami
# Demonstruje metody łańcucha

# Cytat z wypowiedzi prezesa IBM, Thomasa Watsona, z 1943 r.


quote = "Myślę, że istnieje światowy rynek dla może pięciu komputerów."

print("Oryginalny cytat w tłumaczeniu na język polski:")


print(quote)

print("\nDużymi literami:")
print(quote.upper())

print("\nMałymi literami:")
52 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

print(quote.lower())

print("\nWszystkie wyrazy od dużej litery:")


print(quote.title())

print("\nZ drobną zamianą:")


print(quote.replace("pięciu", "milionów"))

print("\nOryginalny cytat pozostał bez zmian:")


print(quote)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Tworzenie nowych łańcuchów


za pomocą metod łańcucha
Chociaż wchodzi tu w grę nowe pojęcie, kod jest nadal dość zrozumiały. Spójrz na
poniższy wiersz:
print(quote.upper)

Prawdopodobnie odgadłeś, co on robi — wyświetla wersję łańcucha quote zawierającą


same duże litery. Robi to dzięki użyciu metody łańcucha upper(). Metoda łańcucha jest
jakby zdolnością, którą łańcuch dysponuje. Tak więc dzięki swojej metodzie upper()
łańcuch quote posiada zdolność tworzenia nowego łańcucha — swojej wersji, w której
wszystkie małe litery są zastąpione dużymi. Kiedy ten nowy łańcuch utworzy, zwraca jego
wartość, a omawiany wiersz kodu staje się równoważny następującemu:
print("MYŚLĘ, ŻE ISTNIEJE ŚWIATOWY RYNEK DLA MOŻE PIĘCIU KOMPUTERÓW.")

Oczywiście właściwy wiersz kodu tak nie wygląda, ale możesz go sobie w ten sposób
wyobrazić, co może Ci pomóc w zrozumieniu działania metody.
Prawdopodobnie zwróciłeś uwagę na nawiasy występujące w wywołaniu tej metody.
Powinno Ci to przypominać funkcje. Metody są podobne do funkcji. Główna różnica
polega na tym, że wbudowana funkcja, taka jak input(), może być wywołana
samodzielnie. Natomiast metoda łańcucha musi zostać wywołana w kontekście
konkretnego łańcucha. To, co pokazuje poniższy wiersz, nie ma sensu:
print(upper())

Uruchamiasz metodę, czyli wywołujesz ją, dopisując kolejno kropkę, nazwę metody
i parę nawiasów po elemencie reprezentującym wartość łańcucha. Nawiasy nie są tylko
na pokaz. Tak jak w przypadku funkcji możesz wewnątrz nich przekazywać argumenty.
Metoda upper() nie przyjmuje żadnych argumentów, ale poznasz przykład metody
łańcucha, która to robi, w postaci replace().
Wiersz:
print(quote.lower())
Używanie metod łańcucha 53

wywołuje metodę lower() łańcucha quote w celu utworzenia i zwrócenia wersji tego
łańcucha zawierającej same małe litery. Następnie ten nowy, złożony z małych liter
łańcuch jest wypisywany.
Wiersz:
print(quote.title())

wyświetla wersję łańcucha quote, która przypomina niektóre nazwy wielowyrazowe.


Metoda title() zwraca łańcuch, w którym każde słowo rozpoczyna się od dużej litery,
a pozostałe litery są małe.
Wiersz:
print(quote.replace("pięciu", "milionów"))

wyświetla nowy łańcuch, w którym każde wystąpienie słowa "pięciu" w łańcuchu quote
zostaje zamienione przez "milionów".
Metoda replace() potrzebuje przynajmniej dwóch informacji: starego tekstu, który
ma być zastąpiony, i nowego tekstu, który go zastąpi. Te dwa argumenty należy oddzielić
przecinkiem. Możesz dodać opcjonalny trzeci argument, liczbę całkowitą, który informuje
metodę, ile razy maksymalnie może wykonać zastąpienie.
W końcu program wyświetla ponownie łańcuch quote:
print("\nOryginalny cytat pozostał bez zmian:")
print(quote)

Jak widać na rysunku 2.8, łańcuch quote nie uległ zmianie. Zapamiętaj, że metody
łańcucha tworzą nowy łańcuch i nie wpływają na wartość oryginalnego. W tabeli 2.3
znajduje się podsumowanie metod łańcucha, które właśnie poznałeś, oraz kilku innych.

Tabela 2.3. Przydatne metody łańcucha


Metoda Opis
upper() Zwraca wersję łańcucha, w której wszystkie małe litery zostały
zamienione na duże.
lower() Zwraca wersję łańcucha, w której wszystkie duże litery zostały
zamienione na małe.
swapcase() Zwraca nowy łańcuch po odwróceniu wielkości liter. Wszystkie
małe litery zostały zamienione na duże, a duże na małe.
capitalize() Zwraca nowy łańcuch, w którym pierwsza litera została
zamieniona na dużą, a pozostałe litery są małe.
title() Zwraca nowy łańcuch, w którym pierwsza litera każdego słowa
została zamieniona na dużą, a wszystkie pozostałe są małe.
strip() Zwraca łańcuch, w którym wszystkie białe znaki (tabulatory, spacje
i znaki nowego wiersza) znajdujące się na początku i na końcu
zostały usunięte.
replace(stary, Zwraca łańcuch, w którym wszystkie wystąpienia łańcucha stary
nowy, [, max]) zostały zastąpione łańcuchem nowy. Opcjonalny parametr max
ogranicza liczbę zamian.
54 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Stosowanie właściwych typów


Do tej pory używałeś trzech różnych typów danych: łańcuchów, liczb całkowitych oraz
liczb zmiennoprzecinkowych. Ważne jest, aby nie tylko wiedzieć, jakie typy danych są
dostępne, ale także umieć z nich odpowiednio korzystać. W przeciwnym razie możesz
pisać programy, które generują niezamierzone wyniki.

Prezentacja programu Uczestnik funduszu


powierniczego — niepoprawna wersja
Pomysł na następny program to utworzenie narzędzia dla tych lekkoduchów, którzy
bawią się cały dzień, żyjąc z hojnego funduszu powierniczego. Program powinien
obliczyć ogólną sumę miesięcznych wydatków na podstawie danych wprowadzonych
przez użytkownika. Ta ogólna suma ma pomóc tym, którzy żyją na poziomie luksusu
przekraczającym wszelkie rozsądne granice, utrzymać się w granicach budżetu, żeby
nigdy nie musieli myśleć o podjęciu prawdziwej pracy. Ale jak mogłeś wywnioskować
z tytułu, program Uczestnik funduszu powierniczego — niepoprawna wersja nie działa
zgodnie z zamierzeniem programisty. Rysunek 2.9 pokazuje jego przykładowe
uruchomienie.

Rysunek 2.9. Suma miesięczna powinna być wysoka, ale nie aż tak wysoka.
Coś działa nieprawidłowo

Dobrze, program w oczywisty sposób nie działa prawidłowo. Zawiera jakiś błąd. Ale
nie taki, który by spowodował awarię programu. Kiedy program generuje niezamierzone
wyniki, ale nie kończy awaryjnie swojego działania, zawiera błąd logiczny. Na podstawie
tego, co już wiesz, mógłbyś domyślić się, co się stało, patrząc na kod. Kod tego programu
możesz znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 2.; nazwa pliku to fundusz_powierniczy_zly.py.
Stosowanie właściwych typów 55

# Uczestnik funduszu powierniczego - niepoprawna wersja


# Demonstruje błąd logiczny

print(
"""
Uczestnik funduszu powierniczego

Sumuje Twoje miesięczne wydatki, żeby Twój fundusz powierniczy się nie wyczerpał
(bo wtedy byłbyś zmuszony do podjęcia prawdziwej pracy).

Wprowadź swoje wymagane miesięczne koszty. Ponieważ jesteś bogaty, zignoruj


grosze i swoje kwoty podaj w pełnych złotych.

"""
)

car = input("Serwis Mercedesa: ")


rent = input("Apartament w Śródmieściu: ")
jet = input("Wynajem prywatnego samolotu: ")
gifts = input("Podarunki: ")
food = input("Obiady w restauracjach: ")
staff = input("Personel (służba domowa, kucharz, kierowca, asystent): ")
guru = input("Osobisty guru i coach: ")
games = input("Gry komputerowe: ")

total = car + rent + jet + gifts + food + staff + guru + games

print("\nOgółem:", total)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Jeśli nie widzisz problemu od razu, to też nie dzieje się nic złego. Udzielę Ci jednak
dalszych wskazówek. Jeszcze raz popatrz na dane wyjściowe na rysunku 2.9. Przyjrzyj się
uważnie długiej liczbie, którą program wyświetlił jako sumę ogólną. Następnie przypatrz
się wszystkim liczbom, które wprowadził użytkownik. Zauważyłeś jakiś związek? Dobrze,
niezależnie od tego, czy go zauważyłeś, czy też nie, czytaj dalej.

Znajdowanie błędów logicznych


Błędy logiczne mogą być najtrudniejsze do poprawienia. Ponieważ program nie kończy
się awarią, nie możesz wykorzystać komunikatu o błędzie, który byłby dla Ciebie
podpowiedzią. Musisz obserwować zachowanie programu i badać jego kod.
W tym przypadku najwięcej informacji kryje się w danych wyjściowych programu.
Olbrzymia liczba wyraźnie nie jest sumą wszystkich liczb, które wprowadził użytkownik.
Ale patrząc na te liczby, możesz zauważyć, że wyświetlona suma jest konkatenacją tych
wszystkich liczb. Jak się to mogło stać? Cóż, jak pamiętasz, funkcja input() zwraca
łańcuch. Więc każda liczba wprowadzona przez użytkownika jest traktowana jak łańcuch
znaków. To oznacza, że każda zmienna w tym programie ma przypisaną wartość
łańcuchową. Tak więc w wierszu:
56 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

total = car + rent + jet + gifts + food + staff + guru + games

nie ma dodawania liczb. To konkatenacja łańcuchów!


Teraz, kiedy już wiesz, na czym polega problem, jak go rozwiążesz? Te wszystkie
wartości łańcuchowe muszą być jakoś przekształcone na liczby. Wtedy program będzie
działał zgodnie z przeznaczeniem. Żeby tylko był jakiś sposób na wykonanie tego
przekształcenia. Cóż, jak mogłeś zgadnąć, taki sposób istnieje.

W świecie rzeczywistym
Symbol + może oznaczać działanie zarówno na parze łańcuchów, jak i na parze
liczb całkowitych. Używanie tego samego operatora do wartości różnych typów
jest nazywane przeciążaniem operatora. Chociaż słowo „przeciążanie” może się
kojarzyć z czymś złym, ale w rzeczywistości może być dobrą rzeczą. Czy to, że
łańcuchy są łączone przy użyciu znaku plusa, nie jest sensowne? Natychmiast
rozumiesz, co on oznacza. Dobrze zaimplementowane przeciążanie operatorów
może się przyczynić do tworzenia bardziej przejrzystego i eleganckiego kodu.

Konwersja wartości
Dobrym rozwiązaniem problemu występującego w programie Uczestnik funduszu
powierniczego — niepoprawna wersja jest przekształcenie wartości łańcuchowych
zwróconych przez funkcję input() na wartości liczbowe. Ponieważ program operuje
kwotami wyrażonymi w pełnych złotych, sensowne jest przeprowadzenie konwersji
każdego łańcucha na liczbę całkowitą przed jej użyciem w obliczeniach.

Prezentacja programu Uczestnik funduszu


powierniczego — poprawna wersja
W programie Uczestnik funduszu powierniczego — poprawna wersja został poprawiony
błąd logiczny, który występuje w programie Uczestnik funduszu powierniczego —
niepoprawna wersja. Rzuć okiem na dane wyjściowe nowego programu przedstawione
na rysunku 2.10.
Teraz programowi udało się obliczyć poprawną sumę. Kod tego programu możesz
znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 2.; nazwa pliku to uczestnik_funduszu_powierniczego_dobry.py.
# Uczestnik funduszu powierniczego - poprawna wersja
# Demonstruje konwersję typów

print(
"""
Uczestnik funduszu powierniczego

Sumuje Twoje miesięczne wydatki, żeby Twój fundusz powierniczy się nie wyczerpał
(bo wtedy byłbyś zmuszony do podjęcia prawdziwej pracy).
Konwersja wartości 57

Rysunek 2.10. Aha, 151 300 miesięcznie wygląda rozsądniej

Wprowadź swoje wymagane miesięczne koszty. Ponieważ jesteś bogaty, zignoruj


grosze i swoje kwoty podaj w pełnych złotych.

"""
)

car = input("Serwis Mercedesa: ")


car = int(car)

rent = int(input("Apartament w Śródmieściu: "))


jet = int(input("Wynajem prywatnego samolotu: "))
gifts = int(input("Podarunki: "))
food = int(input("Obiady w restauracjach: "))
staff = int(input("Personel (służba domowa, kucharz, kierowca, asystent): "))
guru = int(input("Osobisty guru i coach: "))
games = int(input("Gry komputerowe: "))

total = car + rent + jet + gifts + food + staff + guru + games

print("\nOgółem:", total)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Konwersja łańcuchów na liczby całkowite


Istnieje szereg funkcji, które przeprowadzają konwersję typów danych. Funkcja
przekształcająca wartość na liczbę całkowitą została pokazana w następujących
wierszach kodu:
car = input("Serwis Mercedesa: ")
car = int(car)
58 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Pierwszy wiersz pobiera dane wejściowe od użytkownika w postaci łańcucha znaków


i przypisuje tę wartość zmiennej car. Drugi wiersz wykonuje konwersję. Funkcja int()
pobiera łańcuch, do którego odwołuje się zmienna car, i zwraca jego interpretację
w postaci liczby całkowitej. Następnie zmienna car otrzymuje nową wartość, która
jest tą liczbą całkowitą.
Siedem kolejnych wierszy programu pobiera pozostałe kategorie wydatków
i przeprowadza ich konwersję:
rent = int(input("Apartament w Śródmieściu: "))
jet = int(input("Wynajem prywatnego samolotu: "))
gifts = int(input("Podarunki: "))
food = int(input("Obiady w restauracjach: "))
staff = int(input("Personel (służba domowa, kucharz, kierowca, asystent): "))
guru = int(input("Osobisty guru i coach: "))
games = int(input("Gry komputerowe: "))

Zauważ, że przypisania są wykonywane w pojedynczych wierszach. Jest to możliwe


dlatego, że wywołania dwóch funkcji input() i int() są zagnieżdżone. Zagnieżdżanie
wywołań funkcji oznacza umieszczanie jednego wewnątrz drugiego. Działa to doskonale,
o ile wartość zwrotna funkcji wewnętrznej może zostać wykorzystana jako wartość argumentu
funkcji zewnętrznej. W tym przypadku wartością zwrotną funkcji input() jest łańcuch
znaków, a typ łańcuchowy nadaje się doskonale do konwersji za pomocą funkcji int().
W instrukcji przypisującej wartość zmiennej rent funkcja input() pyta użytkownika,
ile wynosi czynsz. Użytkownik wprowadza jakiś tekst, który zostaje zwrócony jako łańcuch.
Następnie program wywołuje funkcję int() z tym łańcuchem w roli argumentu. Funkcja
int() zwraca liczbę całkowitą reprezentowaną przez łańcuch. Wreszcie ta liczba całkowita
zostaje przypisana zmiennej rent. Pozostałe sześć instrukcji przypisania działa w taki
sam sposób.
Istnieją inne funkcje, które przekształcają wartości do określonego typu. Kilka z nich
wymieniono w tabeli 2.4.

Tabela 2.4. Wybrane funkcje konwersji typów


Funkcja Opis Przykład Zwraca
float(x) Zwraca liczbę zmiennoprzecinkową po float("10.0") 10.0
przeprowadzeniu konwersji argumentu x.
int(x) Zwraca liczbę całkowitą po int("10") 10
przeprowadzeniu konwersji argumentu x.
str(x) Zwraca łańcuch znaków po str(10) '10'
przeprowadzeniu konwersji argumentu x.

Używanie operatorów rozszerzonego przypisania


Operatorów rozszerzonego przypisania jest niemało. Ale sama koncepcja jest prosta.
Powiedzmy, że chcesz się dowiedzieć, ile użytkownik wydaje rocznie na żywność. Aby
obliczyć i przypisać do zmiennej roczną sumę wydatków, mógłbyś użyć instrukcji:
Powrót do programu Nieistotne fakty 59

food = food * 52

W tej instrukcji wartość zmiennej food jest mnożona przez 52, a następnie otrzymany
wynik jest przypisywany z powrotem do zmiennej food. To samo można osiągnąć za
pomocą następującego wiersza kodu:
food *= 52

Operator *= jest przykładem operatora przypisania rozszerzonego. W tym wierszu


mamy również do czynienia z mnożeniem wartości zmiennej food przez 52 i przypisaniem
wyniku do tej samej zmiennej, ale instrukcja jest krótsza niż jej poprzednia wersja.
Ponieważ przypisywanie zmiennej wyniku operacji wykonywanej na jej poprzedniej
wartości jest czymś, co często występuje w programowaniu, tego typu operatory są
dobrym sposobem na skrócenie kodu. Istnieją też inne operatory rozszerzonego
przypisania. Tabela 2.5 zawiera podsumowanie kilku najbardziej użytecznych.

Tabela 2.5. Przydatne operatory rozszerzonego przypisania


Operator Przykład instrukcji Instrukcja równoważna
*= x *= 5 x = x * 5
/= x /= 5 x = x / 5
%= x %= 5 x = x % 5
+= x += 5 x = x + 5
-= x -= 5 x = x - 5
*= x *= 5 x = x * 5

Powrót do programu Nieistotne fakty


Teraz wiesz już wszystko, czego potrzebujesz do napisania programu do projektu
Nieistotne fakty przedstawionego na początku tego rozdziału. Zaprezentuję ten program
w nieco inny sposób niż pozostałe. Zamiast wylistowania kodu w całości omówię ten
program fragment po fragmencie. Kod tego programu możesz znaleźć na stronie
internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału
2.; nazwa pliku to nieistotne_fakty.py.

Utworzenie komentarzy początkowych


Chociaż komentarze nie mają żadnego wpływu na działanie programu, stanowią ważną
część każdego projektu. Jak zawsze rozpoczynam od kilku:
# Nieistotne fakty
#
# Uzyskuje dane osobiste od użytkownika, a potem
# wypisuje prawdziwe, lecz bezużyteczne informacje o nim
60 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Wskazówka
Doświadczeni programiści wykorzystują także obszar komentarzy początkowych
do wstawiania opisów wszystkich modyfikacji wprowadzanych w kodzie w przeciągu
czasu. Dzięki temu cała historia programu jest udostępniona na samym jego
początku. Ta praktyka jest szczególnie użyteczna, kiedy kilku programistów
zajmuje się tym samym kodem.

Wczytanie danych wprowadzonych przez użytkownika


Wykorzystując funkcję input(), program wczytuje imię użytkownika, jego wiek i wagę:
name = input("Cześć! Jak masz na imię? ")

age = input("Ile masz lat? ")


age = int(age)

weight = int(input("Dobrze, ostatnie pytanie. Ile kilogramów ważysz? "))

Pamiętaj, funkcja input() zawsze zwraca łańcuch. Ponieważ zmienne age i weight
będą traktowane jak liczby, ich wartości muszą zostać przekształcone. W przypadku
zmiennej age rozbiłem ten proces na dwa wiersze. Najpierw przypisałem do zmiennej
łańcuch zwrócony przez funkcję input(). Następnie przekształciłem ten łańcuch na
liczbę całkowitą i przypisałem ją ponownie do tej zmiennej. Natomiast w przypadku
zmiennej weight mój kod wykonujący przypisanie zmieścił się w jednym wierszu dzięki
zagnieżdżeniu wywołań funkcji. Zrealizowałem przypisania na dwa różne sposoby,
aby przypomnieć Ci obydwa. Jednak w praktyce wybrałbym jedno podejście, aby być
konsekwentnym.

Wyświetlanie imienia przy użyciu samych małych


i samych dużych liter
Poniższe wiersze kodu wyświetlają wartość zmiennej name w dwu wersjach: najpierw
małymi literami, a następnie dużymi za pomocą metod łańcucha:
print("\nJeśli poeta ee cummings wysłałby do Ciebie wiadomość e-mail,\nzwróciłby się
do Ciebie",
name.lower())
print("Ale jeśli byłby wściekły, nazwałby Cię", name.upper())

Nawiasem mówiąc, ee cummings był amerykańskim poetą awangardowym, który nie


używał dużych liter. Więc gdyby żył i wysłał do Ciebie wiadomość e-mail, pisząc Twoje
imię, użyłby prawdopodobnie samych małych liter. Ale gdyby był wściekły, zrobiłby zapewne
wyjątek i nakrzyczałby na Ciebie poprzez użycie w swojej wiadomości dużych liter.

Pięciokrotne wypisanie imienia


Program wyświetla imię użytkownika pięć razy z rzędu przy użyciu powielania łańcuchów:
Podsumowanie 61

called = name * 5
print("\nJeśli małe dziecko próbowałoby zwrócić na siebie Twoją uwagę,",)
print("Twoje imię przybrałoby formę:")
print(called)

Zmiennej called zostaje przypisana powtórzona pięć razy wartość zmiennej name.
Następnie zostaje wyświetlony komunikat, a po nim wartość zmiennej called.

Obliczanie liczby sekund


W dwóch kolejnych wierszach programu zostaje obliczony i wyświetlony wiek
użytkownika wyrażony w sekundach:
seconds = age * 365 * 24 * 60 * 60
print("\nŻyjesz już ponad", seconds, "sekund.")

Ponieważ rok ma 365 dni, dzień 24 godziny, godzina 60 minut, a minuta 60 sekund,
liczba lat przypisana do zmiennej age zostaje pomnożona przez iloczyn 365 * 24 * 60 * 60.
Wynik zostaje przypisany do zmiennej second. Kolejny wiersz kodu wyświetla jego wartość.

Obliczanie wagi na Księżycu i na Słońcu


Kolejne cztery wiersze kodu obliczają i wyświetlają wagę użytkownika na Księżycu
i na Słońcu:
moon_weight = weight / 6
print("\nCzy wiesz, że na Księżycu Twoja waga wynosiłaby",
moon_weight, "kg?")

sun_weight = weight * 27.1


print("Na Słońcu ważyłbyś (ważyłabyś)", sun_weight, "kg (ale niestety niedługo).

Ponieważ na Księżycu przyciąganie grawitacyjne jest sześciokrotnie mniejsze niż na


Ziemi, zmiennej moon_weight zostaje przypisana wartość zmiennej weight podzielona
przez 6. A ponieważ siła grawitacji na Słońcu jest 27.1 razy większa niż na Ziemi, mnożę
wartość zmiennej weight przez 27.1 i przypisuję wynik do zmiennej sun_weight.

Czekanie na użytkownika
Ostatnia instrukcja oznacza czekanie, aż użytkownik naciśnie klawisz Enter:
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale zobaczyłeś, jak tworzyć łańcuch przy użyciu pojedynczych, podwójnych
i potrójnych cudzysłowów. Dowiedziałeś się, jak umieszczać w nich znaki niegraficzne
za pomocą sekwencji specjalnych. Zobaczyłeś, jak łączyć i powielać łańcuchy znaków.
62 Rozdział 2. Typy, zmienne i proste operacje wejścia-wyjścia. Program Nieistotne fakty

Poznałeś dwa różne typy numeryczne — liczby całkowite i zmiennoprzecinkowe —


i dowiedziałeś się, jak się nimi posługiwać. Wiesz także, jak przeprowadzać konwersje
między łańcuchami i liczbami. Poznałeś zmienne i zobaczyłeś, jak ich można używać do
przechowywania i pobierania informacji. Na koniec dowiedziałeś się, jak wczytywać dane
wprowadzane przez użytkownika, aby sprawić, że programy będą interaktywne.

Sprawdź swoje umiejętności


1. Utwórz listę prawidłowych i nieprawidłowych nazw zmiennych. Przy każdej
nazwie umieść wyjaśnienie, dlaczego jest prawidłowa albo nieprawidłowa.
Następnie sporządź listę „dobrych” i „złych” prawidłowych nazw zmiennych.
Do każdej z nich dołącz wyjaśnienie, dlaczego została dobrze albo źle wybrana.
2. Napisz program, który umożliwi użytkownikowi wprowadzenie nazw jego
dwóch ulubionych przysmaków. Program powinien następnie wypisać nazwę
nowego przysmaku utworzoną poprzez połączenie nazw podanych przez
użytkownika.
3. Napisz program Kalkulator napiwku, w którym użytkownik wprowadza sumę
ogólną z rachunku wystawionego przez restaurację. Program powinien potem
wyświetlić dwie kwoty napiwku — w wysokości 15 i 20 procent.
4. Napisz program Sprzedawca samochodów, w którym użytkownik wprowadza
podstawową cenę samochodu. Program powinien dodać szereg dodatkowych
opłat, takich jak podatek, opłatę rejestracyjną, prowizję przygotowawczą
dealera, opłatę za dostarczenie. Oblicz podatek i opłatę rejestracyjną jako
pewien procent ceny podstawowej. Pozostałe opłaty powinny mieć stałe
wartości. Wyświetl faktyczną cenę samochodu po doliczeniu wszystkich
dodatków.
3
Rozgałęzianie kodu,
pętle while,
projektowanie programu.
Gra Odgadnij moją liczbę

D o tej pory programy, które napisałeś, miały prostą sekwencyjną strukturę —


wszystkie instrukcje są wykonywane raz, po kolei i przy każdym uruchomieniu.
Gdybyś był ograniczony do tylko tego typu programowania, pisanie złożonych aplikacji
byłoby bardzo trudne, jeśli nie niemożliwe. Ale w tym rozdziale dowiesz się, jak selektywnie
wykonywać pewne porcje kodu oraz powtarzać fragmenty programu. W szczególności
nauczysz się:
 generować liczby losowe przy użyciu funkcji randint() i randrange(),
 używać instrukcji if do warunkowego wykonywania kodu,
 wykorzystywać klauzulę else do dokonywania wyboru opartego na warunku,
 korzystać z klauzuli elif do dokonywania wyboru opartego na kilku warunkach,
 używać pętli while do powtarzania fragmentów programu,
 sporządzać szkice programów przy użyciu pseudokodu.

Wprowadzenie do gry Jaka to liczba?


Program, który utworzysz w tym rozdziale, jest klasyczną grą polegającą na zgadywaniu
liczb. Tych, których ta gra ominęła w dzieciństwie, informuję, na czym ona polega:
komputer wybiera losowo liczbę z zakresu od 1 do 100, a gracz stara się ją odgadnąć
w jak najmniejszej liczbie prób. Za każdym razem, gdy gracz wprowadza nową liczbę,
komputer go informuje, czy liczba jest za duża, za mała, czy dokładnie taka jak trzeba.
64 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Kiedy graczowi udaje się odgadnąć wygenerowaną przez komputer liczbę, gra się kończy.
Rysunek 3.1 pokazuje grę Jaka to liczba? w działaniu.

Rysunek 3.1. Odgadnięta w tylko trzech próbach! Spróbuj to pobić

Generowanie liczb losowych


Chociaż na ogół użytkownicy oczekują od swoich programów spójnych i przewidywalnych
wyników, czasem nieprzewidywalność jest tym, co sprawia, że programy są ekscytujące
— nagła zmiana w strategii komputerowego przeciwnika czy pozaziemska istota
wypadająca z dowolnych drzwi. Liczby losowe mogą dostarczyć tego elementu
przypadku i zaskoczenia, a Python udostępnia łatwy sposób ich generowania.

Pułapka
Python generuje liczby losowe na podstawie wzoru matematycznego,
więc nie są one prawdziwie losowe. Ta metoda nazywa się generowaniem liczb
pseudolosowych i jest satysfakcjonująca w przypadku większości aplikacji
(tylko nie próbuj przy jej zastosowaniu uruchamiać internetowego kasyna).
Jeśli faktycznie potrzebujesz prawdziwie losowych liczb, odwiedź stronę
http://www.fourmilab.ch/hotbits/. Ten portal generuje liczby losowe,
bazując na naturalnym i nieprzewidywalnym procesie rozpadu radioaktywnego.

Prezentacja programu Rzut kośćmi


Program Rzut kośćmi naśladuje rzucanie kostkami stanowiące istotny element szybkiej,
hazardowej gry w kości. Lecz nie musisz nic wiedzieć o grze w kości, aby docenić ten
program. Symuluje on rzut dwoma sześciennymi kostkami. W celu ustalenia liczby
wyrzuconych oczek program używa funkcji, które generują liczby losowe. Na rysunku 3.2
można zobaczyć ten program w działaniu.
Generowanie liczb losowych 65

Rysunek 3.2. Ojej! Uzyskałem sumę 8 w moim pierwszym rzucie,


co oznacza, że przegrywam

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to rzut_koscmi.py.
# Rzut kośćmi
# Demonstruje generowanie liczb losowych

import random

# generuj liczby losowe z zakresu 1 - 6


die1 = random.randint(1, 6)
die2 = random.randrange(6) + 1

total = die1 + die2

print("Wyrzuciłeś", die1, "oraz", die2, "i uzyskałeś sumę", total)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Import modułu random


W pierwszym wierszu kodu powyższego programu wprowadziłem instrukcję import.
Instrukcja ta pozwala Ci importować, czyli ładować moduły — w tym przypadku
moduł random:
import random

Moduły to pliki, które zawierają kod przeznaczony do wykorzystania w innych


programach. Zazwyczaj grupują elementy kodu w kolekcje powiązane z jednym obszarem
zadań. Moduł random zawiera funkcje związane z generowaniem liczb losowych
i produkowaniem przypadkowych wyników.
66 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Jeśli wyobrazisz sobie program jako projekt jakiejś konstrukcji, moduły możesz
potraktować jak specjalistyczne zestawy narzędzi, które możesz wyciągnąć z warsztatu,
gdy ich potrzebujesz. W tym jednak przypadku zamiast ściągać z półki piłę tarczową,
zaimportowałem moduł random.

Używanie funkcji randint()


Moduł random zawiera funkcję randint(), która generuje losową liczbę całkowitą. Program
Rzut kośćmi uzyskuje dostęp do funkcji randint() poprzez następujące jej wywołanie:
random.randint(1, 6)

Łatwo zauważyć, że program nie wywołuje funkcji randint() bezpośrednio. Zamiast


tego program wywołuje tę funkcję przy użyciu polecenia random.randint(), uzyskując
dostęp do funkcji randint() poprzez jej moduł random. W ogólności możesz wywoływać
funkcję z zaimportowanego modułu przez podanie nazwy modułu, umieszczenie za nią
kropki oraz samego wywołania funkcji. Ta metoda dostępu nazywa się notacją z kropką.
Tym, którzy znają język angielski (albo się uczą tego języka), przypomina on dopełniacz
saksoński. W języku angielskim „Mike’s Ferrari” oznacza, że chodzi to o Ferrari, którego
właścicielem jest Mike. Wykorzystujące notację z kropką wyrażenie random.randint()
oznacza funkcję randint(), która należy do modułu random. Notacja z kropką może być
użyta w celu uzyskania dostępu do różnych elementów zaimportowanych modułów.
Funkcja randint() wymaga podania dwóch argumentów w postaci liczb całkowitych
i zwraca losową liczbę całkowitą o wartości mieszczącej się w przedziale wyznaczonym
przez argumenty, do którego one same też należą. Tak więc przekazując do funkcji wartości
1 i 6, mam gwarancję, że otrzymam jako wartość zwrotną jedną z liczb: 1, 2, 3, 4, 5 lub 6.
Jako że symuluję rzut kostką o sześciu ścianach, w pełni mnie to zadowala.

Używanie funkcji randrange()


Moduł random zawiera również funkcję randrange(), która generuje losową liczbę
całkowitą. Istnieje kilka sposobów wywołania funkcji randrange(), ale najprostszym jest
użycie pojedynczego argumentu w postaci dodatniej liczby całkowitej. Wywołana w ten
sposób funkcja zwraca losową liczbę całkowitą z przedziału od 0 do liczby użytej w jej
wywołaniu, z włączeniem dolnej, lecz wyłączeniem górnej granicy tego przedziału.
Tak więc wynikiem wywołania random.randrange(6) jest jedna z liczb: 0, 1, 2, 3, 4 lub 5.
W porządku, a gdzie jest 6? Cóż, funkcja randrange() wybiera losowo jedną z grupy
sześciu liczb, a lista liczb zaczyna się od 0. Właśnie dlatego dodałem do wyniku 1,
aby otrzymać prawidłową wartość dla drugiej kostki:
die2 = random.randrange(6) + 1

W rezultacie zmienna die2 otrzymuje jedną z wartości: 1, 2, 3, 4, 5 lub 6.


Używanie instrukcji if 67

Pułapka
Użyłem w programie Rzut kośćmi obu funkcji — randint() i randrange() — żeby
pokazać dwie różne funkcje służące do generowania liczb losowych. W ogólnym
przypadku będziesz musiał wybrać funkcję, która będzie najlepiej pasowała do
Twoich potrzeb.

Używanie instrukcji if
Rozgałęzianie kodu stanowi fundamentalną część programowania komputerów.
Zasadniczo oznacza ona podjęcie decyzji, czy pójść jedną ścieżką, czy drugą. Dzięki
instrukcji if programy mogą wykonywać określony fragment kodu lub go omijać.
Wszystko zależy od organizacji programu.

Prezentacja programu Hasło


Program Hasło wykorzystuje instrukcję if do symulacji procedury logowania w systemie
komputerowym o wysokim poziomie zabezpieczeń. Program udziela użytkownikowi
dostępu tylko po wprowadzeniu prawidłowego hasła. Na rysunkach 3.3 i 3.4 pokazano
kilka przykładowych uruchomień.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku to haslo.py.

Rysunek 3.3. Ha, ha! Nigdy nie złamiesz tego hasła


68 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Rysunek 3.4. Zgadłeś! Powinienem był wybrać lepsze hasło niż „sekret”

# Hasło
# Demonstruje instrukcję if

print("Witaj w systemie firmy Bezpieczny Komputer SA")


print("-- bezpieczeństwo to podstawa naszego działania\n")

password = input("Wprowadź hasło: ")

if password == "sekret":
print("Dostęp został udzielony")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

W świecie rzeczywistym
Chociaż program Hasło realizuje swoje zadanie, demonstrując instrukcję if,
nie jest jednak dobrym przykładem tego, jak należy implementować bezpieczeństwo
komputerów. W gruncie rzeczy każdy mógłby po prostu zbadać kod źródłowy
i odkryć hasło „sekret”.
Aby stworzyć system walidacji hasła, programista musiałby najprawdopodobniej
wykorzystać jakąś formę kryptografii. Kryptografia — starożytna idea sięgająca
tysiące lat wstecz — jest stosowana do kodowania informacji, tak aby tylko
zamierzeni odbiorcy mogli je zrozumieć. Kryptografia jest samodzielną dziedziną
nauki i niektórzy informatycy poświęcają jej całą swoją karierę.

Instrukcja if
Kluczową rolę w programie Hasło odgrywa instrukcja if:
if password == "sekret":
print("Dostęp został udzielony")
Używanie instrukcji if 69

Do tego, jaka jest jej funkcja, możesz prawdopodobnie dojść sam, czytając kod. Jeśli
hasło jest równe "sekret", wyświetlany jest tekst Dostęp został udzielony i program
przechodzi do wykonywania następnej instrukcji. Gdy nie jest równe "sekret", program
nie wyświetla tego komunikatu i przechodzi bezpośrednio do pierwszej instrukcji za
instrukcją if.

Tworzenie warunków
Wszystkie instrukcje if zawierają warunek. Warunek to takie wyrażenie, które jest albo
prawdziwe, albo fałszywe. Warunki są Ci już dobrze znane. Występują dość często w życiu
codziennym. W gruncie rzeczy prawie każde wypowiadane zdanie może być postrzegane
jako warunek. Na przykład zdanie „Na zewnątrz jest 37 stopni” mogłoby być potraktowane
jak warunek. Jest albo prawdziwe, albo fałszywe.
Python ma swoje własne, wbudowane wartości reprezentujące prawdę i fałsz. True
reprezentuje prawdę, a False reprezentuje fałsz. Warunek zawsze przyjmuje jedną z tych
wartości. W programie Hasło wykorzystywany w instrukcji if warunek to password ==
"sekret". Oznacza on, że wartość zmiennej password jest równa "sekret" (czyli zmienna
password odwołuje się do wartości "sekret"). Ten warunek przyjmuje wartość True lub
False, w zależności od wartości zmiennej password. Jeśli wartość zmiennej password jest
równa "sekret", warunek ma wartość True. W przeciwnym wypadku wartością warunku
jest False.

Omówienie operatorów porównania


Warunki są często tworzone przez porównywanie wartości. Wartości można porównywać
przy użyciu operatorów porównania. Jeden z operatorów porównania miałeś okazję
spotkać w programie Hasło. Jest to operator równości zapisywany jako ==.

Pułapka
Operator równości tworzą dwa kolejne znaki równości. Użycie w warunku tylko
jednego znaku równości skutkuje pojawieniem się błędu syntaktycznego, ponieważ
pojedynczy znak równości reprezentuje operator przypisania. Tak więc wyrażenie
password = "sekret" jest instrukcją przypisania, a password == "sekret" to
warunek. Jego wartością jest albo True, albo False. Mimo że operator przypisania
i operator równości wyglądają podobnie, są to dwie różne rzeczy.

Oprócz operatora równości istnieją inne operatory porównania. W tabeli 3.1


przedstawiono w skrócie kilka najbardziej użytecznych.
Przy użyciu operatorów porównania można porównywać liczby całkowite z liczbami
całkowitymi, liczby zmiennoprzecinkowe z liczbami zmiennoprzecinkowymi, a także
liczby całkowite z liczbami zmiennoprzecinkowymi. Możesz nawet porównywać łańcuchy
znaków — wynik jest oparty na porządku alfabetycznym. Na przykład "jabłko" <
"pomarańcza", ponieważ zgodnie z porządkiem alfabetycznym łańcuch "jabłko" ma
mniejszą wartość niż łańcuch "pomarańcza" (poprzedza go w słowniku).
70 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Tabela 3.1. Operatory porównania


Operator Znaczenie Przykładowy warunek Przyjmuje wartość
== równa się 5 == 5 True
!= nie równa się 8 != 5 True
> większe niż 3 > 10 False
< mniejsze niż 5 < 8 True
>= większe lub równe 5 >= 10 False
<= mniejsze lub równe 5 <= 5 True

Pułapka
Python nie pozwala na wykonywanie pewnych porównań. Obiekty różnych typów,
które nie mają ustalonej definicji porządku, nie mogą być porównywane przy użyciu
operatorów <, <=, > czy też >=. Na przykład Python nie pozwoli Ci użyć tych
operatorów do porównywania łańcuchów z liczbami. Śmiało! Spróbuj użyć w swoim
programie warunku 10 > "pięć" — najwyżej wygenerujesz sążnisty komunikat
o błędzie.

Używanie wcięć do tworzenia bloków kodu


Być może zauważyłeś, że drugi wiersz instrukcji if, print("Dostęp został udzielony")
został wcięty. Dzięki wcięciu ten wiersz staje się blokiem kodu. Blok kodu to jeden lub
kilka kolejnych wierszy z taką samą wielkością wcięcia. Stosowanie wcięć wyróżnia
wiersze nie tylko wizualnie, ale także pod względem logicznym. Tworzą one jedną całość.
Blok kodu może zostać między innymi użyty jako część instrukcji if. Stanowi on tę
instrukcję lub grupę instrukcji, która zostaje wykonana, gdy warunek ma wartość True.
W programie Hasło ten blok jest pojedynczą instrukcją print("Dostęp został
udzielony").
Ponieważ bloki mogą zawierać tyle instrukcji, ile tylko chcesz, mógłbyś dodać
specjalne powitanie skierowane do użytkowników, którzy wprowadzą właściwe hasło,
poprzez zmodyfikowanie bloku instrukcji if w podobny sposób jak poniżej:
if password == "sekret":
print("Dostęp został udzielony")
print("Witaj! Musisz być kimś bardzo ważnym.")

Teraz użytkownicy, którzy poprawnie wprowadzą tajne hasło, zobaczą oprócz


komunikatu Dostęp został udzielony tekst Witaj! Musisz być kimś bardzo ważnym.
A jeśli użytkownik wprowadzi coś innego niż sekret, nie zobaczy żadnego z tych
komunikatów.
Używanie klauzuli else 71

Wskazówka
W obrębie społeczności Pythona toczy się zawzięta dyskusja na temat tego,
czy do tworzenia wcięć należy używać tabulatorów, czy spacji (a jeśli spacji, to
w jakiej liczbie). Jest to naprawdę kwestia indywidualnego stylu. Lecz istnieją dwie
wskazówki, do których warto się stosować. Po pierwsze, bądź konsekwentny. Jeśli
wcinasz bloki za pomocą dwóch spacji, używaj dwóch spacji zawsze. Po drugie,
nie łącz spacji i tabulatorów. Jeśli nawet uda Ci się poustawiać w jednej linii bloki
kodu przy użyciu kombinacji obu tych znaków, może to doprowadzić do dużego
bólu głowy w przyszłości. Do powszechnie stosowanych stylów wcięć należą:
pojedynczy znak tabulacji, dwie spacje i (styl, którego używa twórca języka
Python) cztery spacje. Wybór należy do Ciebie.

Konstruowanie własnej instrukcji if


Zobaczyłeś pełny przykład instrukcji if, ale chciałbym zakończyć ten temat krótkim
opisem, jak należy tworzyć własne instrukcje if. Po słowie if należy umieścić warunek,
za nim dwukropek, a po dwukropku blok złożony z co najmniej jednej instrukcji. Jeśli
warunek przyjmuje wartość True, są wykonywane instrukcje tworzące ten blok. Jeśli zaś
warunek przyjmuje wartość False, w programie następuje przejście do pierwszej
instrukcji po instrukcji if.

Używanie klauzuli else


Czasem chcesz, aby Twój program „dokonał wyboru” na podstawie warunku — zrobił
jedną rzecz, gdy warunek będzie prawdziwy, i zrobił coś innego, gdy warunek będzie
fałszywy. Użycie klauzuli else w instrukcji if da Ci tę możliwość.

Prezentacja programu Udzielony-odmówiony


Program Hasło wykonywał dobrą robotę, witając użytkownika, który wprowadził
prawidłowe hasło, lecz nie robił niczego, jeśli wprowadzone hasło okazywało się błędne.
Program Udzielony-odmówiony rozwiązuje ten problem przy użyciu klauzuli else.
Rysunki 3.5 i 3.6 pokazują tę nową, poprawioną wersję.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to udzielony_odmowiony.py.
# Udzielony-odmówiony
# Demonstruje klauzulę else

print("Witaj w systemie firmy Bezpieczny Komputer SA")


print("-- bezpieczeństwo to podstawa naszego działania\n")

password = input("Wprowadź hasło: " )


72 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Rysunek 3.5. Po wprowadzeniu poprawnego hasła użytkownik uzyskuje, tak jak poprzednio,
dostęp do systemu

Rysunek 3.6. Tym razem wprowadzenie niepoprawnego hasła sprawia,


że system generuje nieprzyjemny komunikat Odmowa dostępu

if password == "sekret":
print("Dostęp został udzielony")
else:
print("Odmowa dostępu")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


Używanie klauzuli elif 73

Klauzula else
W stosunku do programu Hasło wprowadziłem tylko jedną zmianę. Do instrukcji if
dodałem klauzulę else:
if password == "sekret":
print("Dostęp został udzielony")
else:
print("Odmowa dostępu")

Jeśli wartość zmiennej password jest równa "sekret", program, dokładnie tak jak
poprzednio, wypisuje komunikat Dostęp został udzielony. Lecz w przeciwnym
wypadku wyświetla tekst Odmowa dostępu dzięki klauzuli else.
W instrukcji if z klauzulą else zostanie wykonany dokładnie jeden z bloków kodu.
Jeśli warunek jest spełniony (jego wartością jest True), wykonywany jest blok kodu
znajdujący się bezpośrednio za nim. Jeśli warunek nie jest spełniony (przyjmuje wartość
False), wykonywany jest blok umieszczony bezpośrednio za klauzulą else.
Klauzulę else tworzy się poprzez umieszczenie bezpośrednio za blokiem if kolejno:
słowa else, dwukropka i bloku instrukcji. Klauzula else musi być umieszczona w tym
samym bloku kodu co odpowiadająca jej klauzula if. To oznacza, że else i if muszą
mieć takie samo wcięcie.

Używanie klauzuli elif


Program może w instrukcji if dokonywać wyboru spośród kilku możliwości dzięki
wykorzystaniu klauzuli elif. Ta klauzula okazuje się całkiem przydatna w sytuacji,
gdy masz jedną zmienną, którą chcesz porównać z szeregiem różnych wartości.

Prezentacja programu Komputer nastrojów


W środku lat 70. ubiegłego stulecia szalony sukces na fali chwilowej mody odniósł
pewien produkt o nazwie pierścionek nastrojów. Pierścionek pokazywał nastrój osoby,
która go nosiła, dzięki zmieniającemu kolor kamieniowi. Cóż, program Komputer
nastrojów dźwiga technikę na kolejny stopień zaawansowania poprzez zaglądanie
w psychikę użytkownika i wyświetlanie obrazu jego nastroju na ekranie komputera.
Rysunek 3.7 odsłania nastrój, jaki mi towarzyszył przy pisaniu tego rozdziału.
W porządku, program tak naprawdę nie sonduje głębin emocji użytkownika poprzez
transmisję impulsów elektroskórnych za pośrednictwem klawiatury. Zamiast tego komputer
generuje liczbę losową, aby wybrać jedną z trzech twarzy do wyświetlenia za pomocą
instrukcji if z klauzulami elif. (Nawiasem mówiąc, pierścionek nastrojów w rzeczywistości
również nie pokazywał emocji noszącej go osoby. Był to tylko wyświetlacz LCD, który
zmieniał kolor w zależności od temperatury ciała).
74 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Rysunek 3.7. Wydaje się, że byłem we wspaniałym humorze,


kiedy pisałem program Komputer nastrojów

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to komputer_nastrojow.py.
# Komputer nastrojów
# Demonstruje klauzulę elif

import random

print("Wyczuwam Twoją energię. Twoje prawdziwe emocje znajdują odbicie na moim


ekranie.")
print("Jesteś...")

mood = random.randint(1, 3)

if mood == 1:
# szczęśliwy
print( \
"""
-----------
| |
| O O |
| < |
| |
| . . |
| `...` |
-----------
""")
elif mood == 2:
# obojętny
print( \
Używanie klauzuli elif 75

"""
-----------
| |
| O O |
| < |
| |
| ------ |
| |
-----------
""")
elif mood == 3:
# smutny
print( \
"""
-----------
| |
| O O |
| < |
| |
| .'. |
| ' ' |
-----------
""")
else:
print("Nieprawidłowa wartość nastroju! (Musisz być naprawdę w złym humorze).")

print("...dzisiaj.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Klauzula elif
Instrukcja if z klauzulami elif może zawierać ciąg warunków do sprawdzenia przez
program. W programie Komputer nastrojów wierszami zawierającymi różne warunki są:
 if mood == 1:
 elif mood == 2:
 elif mood == 3:

Warto zauważyć, że pierwszy warunek zapisuje się przy użyciu if, ale pozostałe
warunki sprawdza się przy użyciu klauzul elif (skrót od „else if”). W instrukcji if można
umieścić dowolną liczbę klauzul elif.
Dzięki wyizolowaniu tych warunków można zobaczyć cel całej struktury: sprawdzenie,
która z trzech różnych wartości została przypisana do zmiennej mood. Najpierw program
sprawdza, czy wartość zmiennej mood jest równa 1. Jeśli jest równa, wyświetlana jest
uśmiechnięta buzia. Jeśli nie jest, program przechodzi do następnego warunku i sprawdza,
czy wartość zmiennej mood jest równa 2. Jeśli ten warunek jest spełniony, wyświetlana jest
twarz, która nie wyraża żadnych emocji. Jeśli nie jest, program sprawdza, czy wartość
zmiennej mood jest równa 3. Jeśli tak jest, wyświetlane jest smutne oblicze.
76 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Pułapka
Ważną cechą instrukcji if zawierającej klauzule elif jest fakt, że gdy tylko
okaże się, że jakiś warunek ma wartość True, komputer wykona odpowiadający
mu blok kodu i nastąpi wyjście z instrukcji. To oznacza, że zostanie wykonany
co najwyżej jeden blok kodu, nawet gdyby kilka warunków miało wartość True.
W programie Komputer nastrojów nie ma to dużego znaczenia. Wartość zmiennej
mood może być równa tylko jednej liczbie, więc tylko jeden z warunków może
zostać spełniony. Ale ważne, by mieć świadomość takiego zachowania, ponieważ
istnieje możliwość tworzenia instrukcji, w których może być jednocześnie spełniony
więcej niż jeden warunek. W takim przypadku wykonywany jest tylko blok kodu
związany z pierwszym spełnionym warunkiem.

Jeśli żaden z wcześniejszych warunków dotyczących wartości zmiennej mood nie okaże
się spełniony, wykonywany jest blok kończącej instrukcję klauzuli else i na ekranie
pojawia się komunikat Nieprawidłowa wartość nastroju! (Musisz być naprawdę w złym
humorze). Nie powinno się to nigdy zdarzyć, ponieważ zmienna mood zawsze przyjmuje
jedną z wartości: 1, 2 lub 3. Ale wstawiłem tę klauzulę tylko na wszelki wypadek.
Nie musiałem jednak tego robić, ponieważ końcowa klauzula else jest opcjonalna.

Pułapka
Chociaż stosowanie końcowej klauzuli else nie jest konieczne, jest jednak
dobrym pomysłem. Działa na zasadzie ostatniej instancji, na wypadek, gdyby
żaden z warunków zawartych w instrukcji nie był spełniony. Nawet jeśli uważasz,
że zawsze będzie spełniony przynajmniej jeden z Twoich warunków, to i tak
możesz użyć końcowej klauzuli else do wyłapania przypadku „niemożliwego”,
tak jak ja to zrobiłem.

Poznałeś stopniowo trzy podobne, lecz coraz silniejsze struktury tworzące


rozgałęzienia. Ich krótki przegląd znajdziesz w tabeli 3.2.

Tworzenie pętli while


Pętle są wszędzie wokół nas. Nawet na Twojej butelce szamponu umieszczono instrukcje
tworzące pętlę: „Dopóki Twoje włosy są brudne: Nanieś szampon. Wetrzyj go we włosy.
Spłucz głowę. Powtórz wymienione czynności”. Może się to wydawać całkiem prostą
koncepcją — dopóki pewien warunek jest spełniony, powtarzaj pewne czynności — ale
jest to potężne narzędzie w programowaniu. Mogłoby się okazać całkiem przydatne na
przykład przy tworzeniu quizu. Mógłbyś zechcieć polecić swojemu programowi: dopóki
pozostały jakieś pytania, kontynuuj prowadzenie gry. Lub też w aplikacji bankowej
mógłbyś chcieć zaprogramować coś takiego: dopóki użytkownik nie wprowadził
prawidłowego numeru konta, kontynuuj pytanie użytkownika o numer konta. Pętla
while pozwala Ci robić dokładnie takie rzeczy.
Tworzenie pętli while 77

Tabela 3.2. Podsumowanie instrukcji służących do rozgałęziania kodu


Instrukcja Opis
if <warunek>: Instrukcja if. Jeśli <warunek> jest spełniony, <blok kodu> jest
<blok kodu> wykonywany; w przeciwnym wypadku jest pomijany.
if <warunek>: Instrukcja if z klauzulą else. Jeśli <warunek> jest spełniony,
<blok kodu 1> wykonywany jest <blok kodu 1>; w przeciwnym wypadku
wykonywany jest <blok kodu 2>.
else:
<blok kodu 2>
if <warunek 1>: Instrukcja if z klauzulami elif i opcjonalną klauzulą else.
<blok kodu 1> Wykonywany jest blok pierwszego spełnionego warunku.
Jeśli żaden warunek nie jest spełniony, wykonywany jest blok
elif <warunek 2>: opcjonalnej klauzuli else.
<blok kodu 2>
.
.
.
elif <warunek N>:
<blok kodu N>
else:
<blok kodu N+1>

Prezentacja programu Symulator trzylatka


W dzisiejszym zabieganym świecie wielu ludziom nigdy nie udaje się wygospodarować
czasu, jaki by chcieli spędzić ze swoim dzieckiem. Zapracowany prawnik może utkwić
w swoim biurze i nie zobaczyć swojego małego synka. Handlowiec może być ciągle
w podróży i nie widzieć swojej małej siostrzenicy. Symulator trzylatka rozwiązuje ten
problem poprzez imitację rozmowy z trzyletnim dzieckiem.
Jak się okazuje, kluczem do naśladowania trzylatka jest pętla while. Na rysunku 3.8
pokazano przykładowe uruchomienie programu.
Jak widzisz, program nie przestaje zadawać pytania Dlaczego?, dopóki nie zostanie
wprowadzona odpowiedź Dlatego. Kod tego programu możesz znaleźć na stronie
internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.;
nazwa pliku to trzylatek.py.
# Symulator trzylatka
# Demonstruje pętlę while

print("\tWitaj w 'Symulatorze trzylatka'\n")


print("Ten program symuluje rozmowę z trzyletnim dzieckiem.")
print("Spróbuj przerwać to szaleństwo.\n")
78 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Rysunek 3.8. Jeśli kiedykolwiek miałeś pod opieką trzylatka, ten dialog powinien Ci
przywrócić ciepłe wspomnienia

response = ""
while response != "Dlatego.":
response = input("Dlaczego?\n")

print("Aha, już wiem.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Pętla while
Pętla w programie Symulator trzylatka składa się tylko z dwóch wierszy:
while response != "Dlatego.":
response = input("Dlaczego?\n")

Jeśli format pętli while wydaje się znajomy, nie dzieje się to bez przyczyny.
Charakteryzuje ją uderzające podobieństwo do jej kuzynki — instrukcji if. Jedyna
różnica polega na tym, że słowo if jest zastąpione przez while. A te podobieństwa
nie są powierzchowne. W obu typach instrukcji, jeśli warunek jest spełniony, blok kodu
(w przypadku pętli nazywany czasem ciałem pętli) jest wykonywany. Lecz w instrukcji
while komputer sprawdza warunek i wykonuje blok kodu raz po raz, dopóki warunek nie
okaże się fałszywy. Właśnie dlatego nazywamy ją pętlą.
W tym przypadku ciałem pętli jest tylko jedna instrukcja response =
input("Dlaczego?\n"), której wykonywanie jest kontynuowane, dopóki użytkownik
nie wprowadzi odpowiedzi Dlatego.. W tym momencie warunek response != "Dlatego."
staje się fałszywy, a pętla się litościwie kończy. Wtedy program wykonuje następną
instrukcję, print("Aha, już wiem.").
Tworzenie pętli while 79

Inicjalizacja zmiennej odgrywającej rolę wartownika


Pętle while są często kontrolowane przez wartownika — zmienną używaną w warunku
i porównywaną z jakąś inną wartością lub wartościami. Możesz myśleć o swojej zmiennej
w roli wartownika jak o strażniku pomagającym utworzyć barierę wokół bloku pętli
while. W programie Symulator trzylatka wartownikiem jest zmienna response. Jest
używana w warunku i porównywana z łańcuchem "Dlatego." przed każdym
wykonaniem bloku kodu.
Ważne, aby zainicjalizować swojego wartownika. W większości przypadków
zmienne odgrywające rolę wartowników są inicjalizowane bezpośrednio przed samą
pętlą. To właśnie zrobiłem:
response = ""

Pułapka
Jeśli zmienna (wartownik) nie będzie miała przypisanej wartości w momencie
określania wartości warunku, Twój program wygeneruje błąd.

Zwykle dobrym pomysłem jest inicjalizowanie zmiennych (wartowników) poprzez


nadanie im pewnego typu pustej wartości. Ja przypisałem do zmiennej response pusty
łańcuch: "". Chociaż mógłbym przypisać łańcuch "abakus" i program działałby
dokładnie tak samo, ale kod stałby się niepotrzebnie zagmatwany.

Sprawdzanie wartości zmiennej (wartownika)


Upewnij się, że istnieje możliwość, iż warunek pętli while przyjmie w pewnym momencie
wartość True; w przeciwnym wypadku blok kodu nie wykona się nigdy. Dla przykładu
dokonaj drobnej zmiany w pętli, której działanie analizowałeś:
response = "Dlatego."
while response != "Dlatego.":
response = input("Dlaczego?\n")

Ponieważ wartość zmiennej response jest równa "Dlatego." bezpośrednio przed


początkiem pętli, blok nigdy się nie wykona.

Aktualizacja zmiennej (wartownika)


Gdy już ustaliłeś swój warunek, zainicjowałeś swoją zmienną (wartownika) i jesteś
pewien, że w pewnych warunkach blok pętli zostanie wykonany, skonstruowałeś własną
działającą pętlę. W następnej kolejności upewnij się, że pętla w pewnym momencie się
zakończy.
Jeśli napisałeś pętlę, która nigdy się nie zatrzymuje, utworzyłeś pętlę nieskończoną.
Witaj w klubie. Prędzej czy później wszyscy programiści utworzyli przypadkowo pętlę
nieskończoną, a potem przyglądali się, jak ich program utknął, robiąc to samo bez końca.
Lub też widzieli, jak ich programy zostają po prostu zamrożone.
80 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Oto prosty przykład pętli nieskończonej:


licznik = 0
while licznik <= 10
print(licznik)

Intencją programisty było prawdopodobnie wyświetlenie przy użyciu pętli liczb od 0


do 10. Niestety, wszystko, co robi ten program, to wyświetlanie bez końca wartości 0.
Programista zapomniał zmienić wewnątrz ciała pętli wartość zmiennej licznik
odgrywającej rolę wartownika. Więc pamiętaj — wartości występujące w warunku muszą
mieć zapewnioną możliwość zmiany w wyniku działania kodu znajdującego się wewnątrz
ciała pętli. Jeśli nie mogą się zmienić, wykonywanie pętli nie zostanie nigdy przerwane
i będziesz miał do czynienia z przypadkiem pętli nieskończonej.

Unikanie pętli nieskończonych


Jeden typ pętli nieskończonej dotyczy sytuacji, gdy zmienna odgrywająca rolę wartownika
nigdy nie jest aktualizowana, i taki przypadek miałeś okazję już poznać. Ale istnieją bardziej
podstępne formy nigdy niekończącej się pętli. Sprawdź to w następnym programie.
Zmienia on wartość zmiennej — wartownika w ciele pętli. Ale coś jest nie tak, ponieważ
pętla nigdy się nie kończy. Przekonaj się, czy potrafisz zauważyć, na czym polega
problem, zanim sam wyjaśnię, co takiego się dzieje.

Prezentacja programu Przegrana bitwa


Program Przegrana bitwa opisuje ostatnią dzielną walkę bohatera osaczonego przez
armię trolli — scenariusz, który można znaleźć w grach z podziałem na role. Program
opowiada akcję bitwy. Opisuje cios po ciosie zmaganie, w którym bohater pokonuje
trolla, ale potem odnosi nowe obrażenia. Ostatecznie program kończy się śmiercią
bohatera. Ale czy na pewno? Kod tego programu możesz znaleźć na stronie internetowej
tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to przegrana_bitwa-zly.py.
# Przegrana bitwa
# Demonstruje przerażającą pętlę nieskończoną

print("Twój samotny bohater jest otoczony przez ogromną armię trolli.")


print("Wielka masa ich zgniłozielonych ciał rozciąga się aż po horyzont.")
print("Bohater wyjmuje miecz z pochwy, gotów do stoczenia swej ostatniej walki.\n")

health = 10
trolls = 0
damage = 3

while health != 0:
trolls += 1
health -= damage
Unikanie pętli nieskończonych 81

print("Bohater pokonuje złego trolla, " \


"ale odnosi obrażenia i traci", damage, "punkty zdrowia.\n")

print("Twój bohater walczył dzielnie i pokonał", trolls, "trolli.")


print("Lecz niestety Twój bohater już nie żyje.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Na rysunku 3.9 pokazano uruchomienie programu. Jego efektem była pętla


nieskończona, więc musiałem zatrzymać proces przez naciśnięcie kombinacji klawiszy
Ctrl+C, gdyż inaczej program nigdy by nie przestał się wykonywać.

Rysunek 3.9. Wydaje się, że Twój bohater jest nieśmiertelny. Jedynym sposobem
zakończenia programu było zatrzymanie procesu

No więc co się dzieje?

Śledzenie programu
Wygląda to tak, jakby program zawierał błąd logiczny. Dobrym sposobem na odnalezienie
tego rodzaju błędu jest prześledzenie wykonania programu. Śledzenie polega na tym,
że symuluje się pracę programu i robi się dokładnie to, co program, podążając śladem
każdej instrukcji oraz rejestrując wartości przypisywane zmiennym. W ten sposób
możesz przejrzeć program krok po kroku, dokładnie zrozumieć, co się dzieje w każdym
jego miejscu, i wykryć okoliczności, które w ukryty sposób przyczyniły się do pojawienia
się błędu w Twoim kodzie.
Najprostszym sposobem śledzenia programu jest stara metoda z ołówkiem i kartką
papieru. Utworzyłem kolumny, po jednej dla każdej zmiennej i warunku. Więc na
początku moja kartka wygląda następująco:
health trolls damage health != 0
82 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Bezpośrednio po określeniu wartości warunku pętli while moja kartka wygląda


jak poniżej:
health trolls damage health != 0
10 0 3 True

Ponieważ warunek ma wartość True, pętla jest wykonywana po raz pierwszy.


Po wykonaniu jednego pełnego przejścia pętli i ponownym sprawdzeniu warunku
mój ślad przedstawia się następująco:
health trolls damage health != 0
10 0 3 True
7 1 3 True

Po kilku kolejnych przejściach przez pętlę mój ślad wygląda jak poniżej:
health trolls damage health != 0
10 0 3 True
7 1 3 True
4 2 3 True
1 3 3 True
-2 4 3 True
-5 5 3 True
-8 6 3 True

Zaprzestałem dalszego tworzenia śladu, ponieważ stało się widoczne, że znalazłem się
w pętli, która się nigdy nie zakończy. Ponieważ wartość zmiennej health jest ujemna
(i nie jest równa 0) w ostatnich trzech wierszach śladu, warunek zachowuje wartość True.
Problem polega na tym, że wartość zmiennej health nigdy nie stanie się równa 0. Jej
wartość będzie oddalać się od zera w kierunku ujemnym po każdym wykonaniu pętli.
W rezultacie warunek nigdy nie przyjmie wartości False i pętla nigdy się nie zakończy.

Tworzenie warunków,
które mogą przyjąć wartość False
Oprócz upewnienia się, że wartości występujące w warunku pętli while się zmieniają,
powinieneś mieć pewność, że warunek osiągnie w końcu wartość False; w przeciwnym
razie nadal będziesz mieć do czynienia z pętlą nieskończoną. W przypadku programu
Przegrana bitwa naprawienie błędu jest łatwe. Wystarczy, że wiersz zawierający warunek
przybierze postać:
while health > 0:

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to przegrana_bitwa-dobry.py.
Tym razem, jeśli wartość zmiennej health stanie się równa 0 lub ujemna, warunek
przyjmie wartość False i pętla się zakończy. Aby mieć pewność, możesz prześledzić
działanie programu przy użyciu tego nowego warunku:
Traktowanie wartości jako warunków 83

health trolls damage health > 0


10 0 3 True
7 1 3 True
4 2 3 True
1 3 3 True
-2 4 3 False

I program kończy się tak, jak powinien. Na rysunku 3.10 pokazano działanie
poprawionego programu.

Rysunek 3.10. Teraz program działa poprawnie, unikając pętli nieskończonej.


Los Twojego bohatera nie jest jednak taki szczęśliwy

Traktowanie wartości jako warunków


Jeśli poprosiłbym Cię o obliczenie wartości wyrażenia 35 + 2, szybko odpowiedziałbyś,
że wynik wynosi 37. Ale jeśli zapytałbym Cię, czy 37 oznacza prawdę, czy fałsz,
odpowiedziałbyś pewnie „Hę?”. Ale koncepcja patrzenia na każdą wartość jako na
prawdę lub fałsz jest w języku Python uprawniona. Każda wartość dowolnego typu może
być traktowana w ten sposób. Tak więc każda z wartości 2749, 8.6, "banan", 0 czy "" może
być zinterpretowana jako True albo False. Może się to podejście wydawać dziwaczne,
ale nie ma w nim nic trudnego. Reguły pozwalające ustalić, czy jakaś wartość jest prawdą,
czy fałszem, są proste. Co ważniejsze, interpretowanie wartości w ten sposób może
przyczynić się do tworzenia bardziej eleganckich warunków.

Prezentacja programu Maitre D’


Jeśli nie zostałeś ostatnio zlekceważony w wykwintnej, francuskiej restauracji, mam
program akurat dla Ciebie. Maitre D’ wita Cię w wytwornej jadłodajni, a potem pyta, jaką
kwotę wsuniesz do kieszeni swojego gospodarza. Jeśli dasz mu zero złotych, zostaniesz
84 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

słusznie zignorowany. Jeśli dasz jakąś inną sumę, stolik na Ciebie czeka. Na rysunkach
3.11 i 3.12 pokazano ten program w działaniu.

Rysunek 3.11. Kiedy nie dasz maitre d’ napiwku, nie da się znaleźć wolnego stolika

Rysunek 3.12. Tym razem pieniądze pomogły wyleczyć maitre d’ z amnezji

Samo działanie programu może nie sprawić na Tobie dużego wrażenia. Przypomina
coś, co już robiłeś. Różnica polega na tym, że w tym programie nie jest wykorzystywany
żaden operator porównania. Zamiast tego wartość (kwota pieniędzy) jest traktowana
jako warunek. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to maitre_d.py.
Traktowanie wartości jako warunków 85

# Maitre d'
# Demonstruje traktowanie wartości jako warunku

print("Witaj w Chateau D' Smakosz")


print("Wydaje się, że tego wieczoru mamy prawie komplet gości.\n")

money = int(input("Ile złotych wsuniesz do kieszeni maitre d'? "))

if money:
print("Och, przypomniałem sobie o wolnym stoliku. Proszę tędy.")
else:
print("Proszę zaczekać. To może trochę potrwać.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Interpretowanie dowolnej wartości


jako prawdy lub fałszu
Nowa koncepcja została zademonstrowana w wierszu:
if money:

Zwróć uwagę, że wartość zmiennej money nie jest porównywana z jakąkolwiek inną
wartością — money to warunek. Kiedy dochodzi do wyznaczania wartości liczby
traktowanej jako warunek, 0 oznacza False, a dowolna inna wartość — True. Tak więc
powyższy wiersz kodu jest równoważny następującemu:
if money != 0:

Pierwsza wersja jest prostsza, bardziej elegancka i bardziej intuicyjna. Wygląda


naturalnie i można by ją przetłumaczyć na „jeśli są pieniądze”.
Reguły dotyczące interpretowania jakiejś wartości jako True lub False są proste.
Główna zasada jest następująca: każda wartość pusta lub zerowa jest traktowana jako
False, wszystkie inne wartości są interpretowane jako True. Tak więc 0 znaczy tyle, co
False, ale wartość logiczna dowolnej innej liczby to True. Pustemu łańcuchowi znaków
odpowiada wartość False, a każdemu innemu — wartość True. Jak widać, prawie każda
wartość to True. Jedynie wartość pusta i zero są traktowane jako False. Przekonasz się,
że sprawdzanie, czy jakaś wartość jest pusta, jest nagminnym zadaniem, więc ten sposób
traktowania wartości może często występować w programach.
Ostatnią rzeczą wartą zauważenia w kontekście naszego przykładu jest to, że jeśli
wprowadzisz ujemną kwotę pieniędzy, maitre d’ też znajdzie dla Ciebie miejsce. Pamiętaj,
w przypadku liczb tylko 0 to False. Więc wszystkim ujemnym liczbom odpowiada
wartość logiczna True, identycznie jak dodatnim.
86 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Tworzenie umyślnych pętli nieskończonych


Wkrótce po przeczytaniu podrozdziału zatytułowanego „Unikanie pętli nieskończonych”
możesz być bardziej niż trochę zaskoczony, widząc podrozdział o tworzeniu pętli
nieskończonych. Czyż pętle nieskończone nie są zawsze wynikiem pomyłki? Cóż, jeśli
pętla byłaby naprawdę nieskończona — to znaczy nie mogłaby się nigdy zakończyć —
tak, byłby to błąd logiczny. Ale konstrukcje, które nazywam umyślnymi pętlami
nieskończonymi, to pętle nieskończone z warunkiem wyjścia wbudowanym w ich ciało.
Najlepszym sposobem na zrozumienie umyślnej pętli nieskończonej jest zapoznanie się
z jej przykładem.

Prezentacja programu Wybredny licznik


Program Wybredny licznik wypisuje liczby od 1 do 10, wykorzystując umyślną pętlę
nieskończoną. Jest wybredny, ponieważ nie lubi liczby 5 i pomija ją. Na rysunku 3.13
pokazano działanie tego programu.

Rysunek 3.13. Liczba 5 zostaje pominięta za pomocą instrukcji continue,


a pętla kończy się w wyniku użycia instrukcji break

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to wybredny_licznik.py.
# Wybredny licznik
# Demonstruje instrukcje break i continue

count = 0
while True:
count += 1
# zakończ pętlę, jeśli wartość zmiennej count jest większa niż 10
Tworzenie umyślnych pętli nieskończonych 87

if count > 10:


break
# pomiń liczbę 5
if count == 5:
continue
print(count)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Wykorzystanie instrukcji break do wyjścia z pętli


Utworzyłem pętlę za pomocą konstrukcji:
while True:

W sensie technicznym to oznacza, że pętla jest wykonywana bez końca, chyba że


w ciele pętli znajduje się warunek wyjścia. Na szczęście wstawiłem taki jeden:
# zakończ pętlę, jeśli wartość zmiennej count jest większa niż 10
if count > 10:
break

Ponieważ wartość zmiennej count jest zwiększana o 1 za każdym razem, gdy


rozpoczyna się wykonywanie ciała pętli, osiągnie w końcu 11. Kiedy to nastąpi,
wykonywana jest instrukcja break, która w tym kontekście oznacza „przerwij
wykonywanie pętli”, i pętla się kończy.

Wykorzystanie instrukcji continue do powrotu


do początku pętli
Tuż przed wyświetleniem wartości zmiennej count umieściłem następujące wiersze kodu:
# pomiń liczbę 5
if count == 5:
continue

Instrukcja continue oznacza „skocz z powrotem do początku pętli”. Na początku pętli


sprawdzany jest warunek while i jeśli okaże się, że ma wartość True, nastąpi ponowne
wejście do pętli. Więc w sytuacji, gdy licznik jest równy 5, program nie dociera do instrukcji
print(count). Zamiast tego wraca natychmiast do początku pętli, więc liczba 5 nie zostanie
nigdy wyświetlona.

Kiedy należy stosować instrukcje break i continue?


Możesz używać instrukcji break i continue w dowolnej pętli, jaką utworzysz. Ich użycie
nie ogranicza się do umyślnych pętli nieskończonych. Ale powinny być używane oszczędnie.
Zarówno break, jak i continue mogą komuś (także i Tobie!) utrudnić śledzenie porządku,
w jakim są wykonywane instrukcje pętli, oraz zrozumienie okoliczności, w jakich pętla
88 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

się kończy. Poza tym instrukcje break i continue nie są Ci właściwie potrzebne. Każda
pętla, którą możesz napisać przy ich użyciu, może być napisana bez ich zastosowania.
W Pythonie zdarzają się przypadki, gdy umyślna pętla nieskończona może być bardziej
przejrzysta niż pętla tradycyjna. W takich sytuacji, gdy tworzenie pętli ze standardowym
warunkiem jest naprawdę niezręczne, niektórzy programiści korzystają z umyślnych pętli
nieskończonych.

Korzystanie z warunków złożonych


Do tej pory spotkałeś tylko porównania, w których uczestniczą dokładnie dwie wartości.
Nazywają się warunkami prostymi. Ale możesz się znaleźć w sytuacji, w której chciałbyś
mieć więcej możliwości. Na szczęście możesz łączyć ze sobą proste warunki za pomocą
operatorów logicznych. W taki sposób połączone te proste warunki stają się warunkami
złożonymi. Wykorzystując warunki złożone, Twoje programy mogą podejmować
decyzje oparte na porównywaniu wielu grup wartości.

Prezentacja programu Ekskluzywna sieć


Ekskluzywne kluby to żadna przyjemność, chyba że jesteś ich członkiem. Utworzyłem
więc program Ekskluzywna sieć. Symuluje on elitarną sieć komputerową, której członkowie
to kilka wybranych osób. Lista członków składa się ze mnie i kilku aktualnie najlepszych
na świecie projektantów gier (niezłe towarzystwo).
Podobnie jak w przypadku systemów komputerowych ze świata realnego, każda
osoba musi wprowadzić nazwę użytkownika i hasło. Członek musi wprowadzić zarówno
swoją nazwę użytkownika, jak i hasło, bo inaczej nie będzie mógł się zalogować. Po udanym
zalogowaniu członek jest osobiście pozdrawiany. Również jak w systemach ze świata
rzeczywistego, każdy ma określony poziom uprawnień.
Ponieważ nie jestem totalnym elitarystą, goście również mogą się logować. Mają
jednak najniższy poziom uprawnień. Na rysunkach od 3.14 do 3.16 pokazano działanie
tego programu.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to udzielony_odmowiony.py.
# Ekskluzywna sieć
# Demonstruje operatory logiczne i warunki złożone

print("\tEkskluzywna Sieć Komputerowa")


print("\t Tylko dla członków!\n")

security = 0
Korzystanie z warunków złożonych 89

Rysunek 3.14. Jeśli nie jesteś członkiem ani gościem, nie możesz wejść do sieci

Rysunek 3.15. Gość może się zalogować, ale jego poziom uprawnień będzie dość niski

Rysunek 3.16. Wygląda na to, że jeden z kompanów dziś się zalogował


90 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

username = ""
while not username:
username = input("Użytkownik: ")

password = ""
while not password:
password = input("Hasło: ")

if username == "M.Dawson" and password == "sekret":


print("Cześć, Mike!")
security = 5
elif username == "S.Meier" and password == "cywilizacja":
print("Hej, Sid!")
security = 3
elif username == "S.Miyamoto" and password == "mariobros":
print("Co u Ciebie, Shigeru?")
security = 3
elif username == "W.Wright" and password == "simsowie":
print("Jak leci, Will?")
security = 3
elif username == "gość" or password == "gość":
print("Witaj, Gościu!")
security = 1
else:
print("Nieudana próba logowania. Nie jesteś taki wyjątkowy.\n")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

W świecie rzeczywistym
Gdybyś faktycznie chciał zaimplementować sieć prywatną, nie umieszczałbyś
nazw użytkowników i haseł bezpośrednio w swoim kodzie. Prawdopodobnie
skorzystałbyś z pewnego typu systemu zarządzania bazą danych (ang.
Database Management System — DBMS). Systemy zarządzania bazą danych
umożliwiają organizowanie wzajemnie powiązanych informacji, dostęp do nich
oraz ich aktualizację. Te systemy mają wielkie możliwości i mogłyby obsługiwać
szybko i bezpiecznie tysiące lub nawet miliony par złożonych z nazwy
użytkownika i hasła.

Operator logiczny not


Chciałem uzyskać pewność, że użytkownik zapytany o swoją nazwę i hasło wprowadzi
jakieś dane. Samo naciśnięcie klawisza Enter, którego efektem jest pusty łańcuch,
nie może zostać zaakceptowane. Potrzebowałem pętli, która dotąd pyta o nazwę
użytkownika, aż użytkownik coś wprowadzi:
username = ""
while not username:
username = input("Użytkownik: ")

W warunku while użyłem operatora logicznego not. Funkcjonuje on tak samo jak
słowo „nie”. W języku polskim umieszczenie przed czymś słowa „nie” tworzy nowe
Korzystanie z warunków złożonych 91

wyrażenie, które jest zaprzeczeniem oryginału. W Pythonie umieszczenie not przed


warunkiem tworzy nowy warunek, którego wartość logiczna jest przeciwna do wartości
logicznej oryginału.
To oznacza, że warunek not username ma wartość True, kiedy wartością wyrażenia
username jest False, oraz że warunek not username ma wartość False, kiedy wartością
wyrażenia username jest True. Oto inny sposób przedstawienia działania operatora not:
username not username
True False
False True

Ponieważ w tym programie zmienna username została zainicjalizowana poprzez


przypisanie pustego łańcucha, jej początkowa wartość logiczna to False. To sprawia,
że wartością warunku not username jest True i pętla jest wykonywana po raz pierwszy.
Następnie program przypisuje zmiennej username wartość wprowadzoną przez użytkownika.
Jeśli użytkownik naciśnie tylko klawisz Enter, wartością zmiennej username będzie, jak
przedtem, pusty łańcuch. Więc jak poprzednio, warunek not username będzie miał
wartość True i pętla wykona się ponownie. Dlatego też dopóki użytkownik tylko naciska
klawisz Enter, pętla nie przestaje się wykonywać i użytkownik jest nieustannie proszony
o wprowadzenie swojej nazwy.
Lecz kiedy użytkownik w końcu wprowadzi jakiś tekst, wartością zmiennej username
stanie się coś innego niż pusty łańcuch. To sprawi, że warunek username przyjmie wartość
True, a not username wartość False. W rezultacie pętla przestaje się wykonywać,
dokładnie tak jak chciałem.
Program w taki sam sposób obsługuje zmienną password.

Operator logiczny and


Jeśli członek chce się zalogować do tej ekskluzywnej sieci, musi wprowadzić nazwę
użytkownika i hasło, które są rozpoznawane łącznie. Jeśli na przykład Sid Meier chce się
zalogować, musi wprowadzić tekst S.Meier jako nazwę użytkownika i cywilizacja jako
hasło. Jeśli Sid nie wprowadzi obu elementów w dokładnie taki sposób, nie będzie mógł
się zalogować. Kombinacja S.Meier i mariobros nie zadziała. Ani M.Dawson i cywilizacja.
Zestawienie cywilizacja i S.Meier również zawiedzie. Program sprawdza, czy Sid wprowadził
nazwę użytkownika S.Meier i hasło cywilizacja za pomocą następującego kodu:
elif username == "S.Meier" and password == "cywilizacja":

Powyższy wiersz zawiera jeden warunek złożony utworzony z dwóch warunków


prostych. Warunki proste to username == "S.Meier" oraz password == "cywilizacja".
Są to takie same warunki, jakie już poznałeś, lecz zostały połączone operatorem
logicznym and, tworząc większy, złożony warunek username == "S.Meier" and
password == "cywilizacja". Ten warunek złożony, choć dłuższy od tych, do których się
przyzwyczaiłeś, jest wciąż tylko warunkiem, co oznacza, że może mieć wartość True lub
False. Więc kiedy warunek username == "S.Meier" and password == "cywilizacja"
92 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

ma wartość True, a kiedy False? Cóż, podobnie jak „i” w języku polskim, „and” oznacza
wymaganie, aby były spełnione obydwa warunki składowe. Więc nasz warunek ma
wartość True, jeśli zarówno username == "S.Meier", jak i password == "cywilizacja"
mają wartość True; w przeciwnym wypadku jego wartość to False. Oto inny sposób
przedstawienia, jak funkcjonuje operator and:
username == "S.Meier" password == "cywilizacja" username == "S.Meier" and
password == "cywilizacja"
True True True
True False False
False True False
False False False

Wskazówka
Umieść operator and między dwoma warunkami, kiedy chcesz utworzyć nowy
warunek, który jest spełniony (ma wartość True) wtedy i tylko wtedy, gdy są
spełnione obydwa prostsze warunki.

Więc jeśli Sid wprowadzi S.Meier jako swoją nazwę użytkownika i cywilizacja jako
hasło, warunek złożony będzie spełniony. Sid zostaje wtedy pozdrowiony i program
przypisuje mu odpowiedni poziom uprawnień.
Oczywiście oprócz Sida Meiera program obsługuje także innych użytkowników.
Wykorzystując strukturę if-elif-else, sprawdza cztery różne pary danych złożone
z nazwy użytkownika i hasła. Jeśli użytkownik wprowadzi parę, która zostanie rozpoznana,
jest pozdrawiany w indywidualny sposób i zostaje mu przypisany określony poziom
uprawnień.
Jeśli członek sieci lub gość nie zaloguje się poprawnie, komputer wyświetli komunikat
o „nieudanej próbie logowania” i informuje tę osobę, że nie jest taka wyjątkowa.

Operator logiczny or
Goście także mogą korzystać z sieci, lecz z ograniczonym poziomem uprawnień.
Aby ułatwić gościowi wypróbowanie sieci, wymaga się od niego, aby wprowadził słowo
gość albo jako nazwę użytkownika, albo jako hasło. Obsługa logowania gościa jest
zawarta w następujących wierszach kodu;
elif username == "gość" or password == "gość":
print("Witaj, Gościu!")
security = 1

Warunek klauzuli elif postaci username == "gość" or password == "gość" w dużym


stopniu przypomina pozostałe warunki, które były użyte w kodzie obsługi członków.
Ale jest jedna poważna różnica. Warunek dotyczący gościa został utworzony przy użyciu
operatora or.
Warunek złożony utworzony za pomocą or ma wartość True, o ile przynajmniej
jeden z warunków składowych ma wartość True. Operator or funkcjonuje tak jak „lub”
Projektowanie programów 93

w języku polskim i oznacza „którykolwiek”. Więc jeśli którykolwiek z warunków prostych


ma wartość True, warunek złożony ma również wartość True. W tym szczególnym
przypadku, jeśli warunek username == "gość" ma wartość True lub jeśli warunek
password == "gość" ma wartość True, lub jeśli nawet obydwa mają wartość True,
to warunek username == "gość" or password == "gość" przyjmuje wartość True;
w przeciwnym wypadku jego wartością jest False. Oto inny sposób spojrzenia
na działanie operatora or:
username == "gość" password == "gość" username == "gość" or
password == "gość"
True True True
True False True
False True True
False False False

Projektowanie programów
Do tej pory wszystkie programy, które poznałeś, były dość proste. Pomysł sporządzania
na papierze formalnego projektu któregokolwiek z nich wydaje się zapewne przesadą.
Ale nie jest. Projektowanie programów, nawet tych małych, prawie zawsze przynosi efekt
w postaci zaoszczędzonego czasu (a często pomaga uniknąć frustracji).
Programowanie w dużym stopniu przypomina budowę. Więc wyobraź sobie
kontrahenta budującego dla Ciebie dom bez wcześniejszego planu. Prawdziwy horror!
Grozi Ci, że wylądujesz z domem, który ma 12 łazienek, nie ma żadnych okien, a drzwi
frontowe zostały umieszczone na piętrze. Ponadto koszt jego budowy okaże się 10 razy
wyższy od przewidywanego.
Podobnie rzecz ma się z programowaniem. Bez projektu będziesz się szamotać
w trakcie pisania programu, tracąc tylko czas. Możesz nawet skończyć z programem,
który nie całkiem działa.
Projektowanie programów jest aż tak ważne, że powstała cała dziedzina inżynierii
oprogramowania poświęcona temu zadaniu. Lecz nawet początkujący programista może
skorzystać z paru prostych narzędzi i technik projektowania.

Tworzenie algorytmów przy użyciu pseudokodu


Algorytm to zestaw jasnych, łatwych do wykonania instrukcji służących do zrealizowania
pewnego zadania. Algorytm jest czymś w rodzaju szkicu programu. Jest czymś, co
zaprojektowałeś przed rozpoczęciem programowania, aby kierowało Twoimi krokami
w trakcie tworzenia kodu.
Algorytm nie jest tylko specyfikacją celu — jest konkretną listą kroków, jakie kolejno
należy wykonać. Więc na przykład instrukcja „Zostań milionerem” tak naprawdę nie jest
algorytmem. Bardziej przypomina sformułowanie zadania, choć nie byle jakiego.
Więc napisałem algorytm „Zarób milion złotych”. Oto on:
94 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

jeśli potrafisz wymyślić nowy i pożyteczny produkt


wtedy masz już swój produkt
w przeciwnym wypadku
zmień opakowanie istniejącego produktu i potraktuj go jako swój produkt
przygotuj reklamę informacyjną promującą Twój produkt
pokaż reklamę informacyjną w telewizji
bierz 100 zł za jednostkę swojego produktu
sprzedaj 10 000 jednostek swojego produktu

O to właśnie chodzi. Mamy klarowny ciąg określonych kroków, które można


wykonać, aby osiągnąć cel.
Algorytmy są na ogół zapisywane w czymś, co nazywa się pseudokodem, i mój nie
stanowi wyjątku. Pseudokod plasuje się gdzieś pomiędzy językiem naturalnym i językiem
programowania. Każdy, kto zna język polski, może zrozumieć mój algorytm. Ale jednocześnie
mój algorytm powinien sprawiać nieco wrażenie programu. Pierwsze cztery wiersze
przypominają strukturę if-else i nie jest to przypadek.

Stopniowe udoskonalanie algorytmów


Tak jak każdy szkic czy plan Twój algorytm może nie osiągnąć ostatecznej formy po
pierwszym jego sporządzeniu. Często algorytmy wymagają wielu rewizji, zanim mogą
zostać zaimplementowane w kodzie. Stopniowe udoskonalanie oznacza całościowy
proces przerabiania algorytmów, tak aby nadawały się do implementacji. Zasadniczo
oznacza to ich uszczegółowienie. Poprzez analizę każdego kroku algorytmu i rozbicie go
na ciąg prostszych kroków sprawiasz, że algorytm coraz bardziej się zbliża do kodu programu.
Stosując metodę stopniowego udoskonalania, kontynuujesz rozbijanie każdego kroku na
mniejsze, dopóki nie uznasz, że cały algorytm może być dość łatwo przetłumaczony na
program. Jako przykład weź pod uwagę następujący krok algorytmu Zarób milion złotych:
przygotuj reklamę informacyjną promującą Twój produkt

Może to robić wrażenie zadania sformułowanego zbyt niejasno. W jaki sposób tworzy
się reklamę informacyjną? Przy użyciu metody stopniowego udoskonalania pojedynczy
krok może być rozbity na kilka drobniejszych. W efekcie otrzymujesz coś takiego:
napisz scenariusz reklamy informacyjnej promującej Twój produkt
wynajmij na jeden dzień studio telewizyjne
zaangażuj ekipę produkcyjną
wynajmij entuzjastyczną widownię
nakręć reklamę

Jeśli uważasz, że każdy z tych pięciu kroków jest zrozumiały i możliwy do wykonania,
ta część algorytmu została w pełni udoskonalona. Jeśli jakiś krok nadal jest dla Ciebie
niejasny, przedstaw go jeszcze bardziej szczegółowo. Kontynuuj ten proces, a będziesz
miał kompletny algorytm i milion złotych.
Powrót do gry Jaka to liczba? 95

Powrót do gry Jaka to liczba?


Program Jaka to liczba? łączy w sobie wiele koncepcji, które poznałeś w tym rozdziale.
Ale co ważniejsze, reprezentuje pierwszą kompletną grę, którą możesz popisać się przed
swoimi przyjaciółmi, rodziną i osobami płci przeciwnej.

Projektowanie programu
W celu zaprojektowania gry napisałem najpierw kilka wierszy pseudokodu:
wybierz losowo jakąś liczbę
dopóki gracz nie odgadł wybranej liczby
daj graczowi szansę jej odgadnięcia
pogratuluj graczowi

Jest to niezła pierwsza wersja, ale brakuje jej pewnych istotnych elementów.
Po pierwsze, program musi poinformować użytkownika, czy podana przez niego liczba
jest za duża, czy za mała. Po drugie, program powinien zarejestrować, ile razy gracz
próbował odgadnąć wybraną liczbę, oraz poinformować go o liczbie wykonanych
prób pod koniec gry.

Wskazówka
Jest w porządku, jeśli Twój pierwszy projekt programu nie jest kompletny.
Zaczynaj projektowanie od głównych idei, a potem wypełniaj luki, dopóki
nie uznasz, że wszystko zostało zrobione.

Dobrze, oto udoskonalona wersja mojego algorytmu:


przywitaj gracza i wyjaśnij zasady gry
wybierz losowo liczbę z zakresu od 1 do 100
poproś gracza o odgadnięcie wybranej liczby
ustaw liczbę prób odgadnięcia na 1
dopóki liczba wprowadzona przez gracza nie jest równa wybranej liczbie
jeśli wprowadzona liczba jest większa niż wybrana liczba
poinformuj gracza, że podana przez niego liczba jest za duża
w przeciwnym wypadku
poinformuj gracza, że podana przez niego liczba jest za mała
pobierz od gracza nową liczbę
zwiększ liczbę prób odgadnięcia o 1
pogratuluj graczowi odgadnięcia wybranej liczby
poinformuj gracza, ile prób potrzebował na odgadnięcie wybranej liczby

Teraz jestem gotów do pisania programu. Przejrzyj kolejne kilka punktów


i przekonaj się, jak prosto pseudokod może zostać przetłumaczony na instrukcje
języka Python. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 3.; nazwa pliku
to jaka_to_liczba.py.
96 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

Tworzenie początkowego bloku komentarzy


Podobnie jak wszystkie dobre programy, ten również rozpoczyna się od bloku komentarzy:
# Jaka to liczba?
#
# Komputer wybiera losowo liczbę z zakresu od 1 do 100.
# Gracz próbuje ją odgadnąć, a komputer go informuje,
# czy podana przez niego liczba jest: za duża, za mała,
# prawidłowa.

Import modułu random


Aby dostarczyć dobrej zabawy, program musi wygenerować liczbę losową.
Zaimportowałem więc moduł random:
import random

Objaśnienie gry
Gra jest prosta, ale trochę wyjaśnień nie zaszkodzi:
print("\tWitaj w grze 'Jaka to liczba'!")
print("\nMam na myśli pewną liczbę z zakresu od 1 do 100.")
print("Spróbuj ją odgadnąć w jak najmniejszej liczbie prób.\n")

Ustawienie wartości początkowych


W następnej kolejności nadaję wszystkim zmiennym wartości początkowe:
# ustaw wartości początkowe
the_number = random.randint(1, 100)
guess = int(input("Ta liczba to: "))
tries = 1

Zmienna the_number reprezentuje liczbę, którą gracz powinien odgadnąć. Przypisuję


jej liczbę całkowitą wybraną losowo spośród liczb od 1 do 100 poprzez wywołanie funkcji
random.randint(). Następnie funkcja input() odczytuje pierwszą odpowiedź gracza.
Funkcja int() przekształca tę odpowiedź na liczbę całkowitą. Przypisuję tę liczbę do
zmiennej guess. Zmiennej tries, która reprezentuje dotychczasową liczbę odpowiedzi
gracza, przypisuję wartość 1.

Utworzenie pętli zgadywania


Jest to rdzeń programu. Pętla jest wykonywana tak długo, aż gracz prawidłowo odgadnie
liczbę wybraną przez komputer. W ciele pętli odpowiedź gracza jest porównywana z liczbą
komputera. Jeśli liczba podana przez gracza jest większa od liczby wybranej przez
komputer, wyświetlana jest informacja Za duża; w przeciwnym razie wyświetlany jest
tekst Za mała. Gracz wprowadza kolejną odpowiedź i licznik prób odgadnięcia wybranej
liczby jest inkrementowany.
Podsumowanie 97

# pętla zgadywania
while guess != the_number:
if guess > the_number:
print("Za duża...")
else:
print("Za mała...")

guess = int(input("Ta liczba to: "))


tries += 1

Pogratulowanie graczowi
Kiedy gracz odgadnie wybraną liczbę, wartość zmiennej guess jest równa wartości
zmiennej the_number, co oznacza, że warunek pętli guess != the_number ma wartość
False i pętla się kończy. W tym momencie graczowi należą się gratulacje:
print("Odgadłeś! Ta liczba to", the_number)
print("Do osiągnięcia sukcesu potrzebowałeś tylko", tries, "prób!\n")

Komputer potwierdza odgadnięcie przez gracza tajemnej liczby i informuje go,


ile prób potrzebował do osiągnięcia sukcesu.

Czekanie, aż gracz zakończy program


Jak zawsze, ostatni wiersz oznacza cierpliwe oczekiwanie na naciśnięcie przez gracza
klawisza Enter:
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale zobaczyłeś, jak zmieniać przepływ programu. Dowiedziałeś się,
że kluczem do zmieniania tego przepływu jest zdolność komputera do określania wartości
warunków. Zobaczyłeś, jak tworzyć warunki proste i złożone. Poznałeś instrukcje if,
if-else oraz if-elif-else, które umożliwiają programom podejmowanie decyzji.
Zetknąłeś się z pętlą while, która przydaje się przy powtarzaniu fragmentów kodu.
Dowiedziałeś się, jakie jest znaczenie projektowania programu. Zobaczyłeś, jak można
projektować program poprzez tworzenie algorytmu w pseudokodzie. Dowiedziałeś się
również, jak generować liczby losowe, aby wprowadzić do programów nieco ożywienia.

Sprawdź swoje umiejętności


1. Napisz program, który symuluje ciasteczko z wróżbą. Program powinien
wyświetlić jedną z pięciu niepowtarzalnych przepowiedni, dokonując losowego
wyboru przy każdym uruchomieniu.
98 Rozdział 3. Rozgałęzianie kodu, pętle while, projektowanie programu

2. Napisz program, który „rzuca” 100 razy monetą, a następnie podaje


użytkownikowi liczbę orzełków i reszek.
3. Zmodyfikuj program Jaka to liczba? tak, aby gracz dysponował ograniczoną
liczbą prób odgadnięcia wybranej przez komputer liczby. Gdy graczowi nie uda
się odgadnąć tej liczby w wyznaczonej liczbie prób, program powinien
wyświetlić odpowiedni komunikat z reprymendą.
4. Tym razem trudniejsze wyzwanie. Napisz pseudokod do programu, w którym
gracz i komputer zamienią się rolami w grze z odgadywaniem liczby. To znaczy
gracz wybiera losowo liczbę z przedziału od 1 do 100, a komputer ma ją
odgadnąć. Zanim rozpoczniesz tworzenie algorytmu, pomyśl, w jaki sposób
sam byś zgadywał. Jeśli wszystko się uda, spróbuj napisać kod gry.
4
Pętle for,
łańcuchy znaków i krotki.
Gra Wymieszane litery

M iałeś okazję się przekonać, jaki wspaniały sposób dostępu do informacji stanowią
zmienne, ale tak jak Twoje programy stają się coraz dłuższe i bardziej skomplikowane,
może rosnąć liczba używanych przez Ciebie zmiennych. Pamiętanie o nich wszystkich
może stać się bardzo uciążliwe. Dlatego w tym rozdziale poznasz pojęcie sekwencji
i spotkasz się z nowym typem danych o nazwie krotka, który umożliwi Ci organizowanie
informacji w uporządkowane grupy, abyś mógł nimi łatwiej manipulować. Przekonasz się
również, że typ, z którym się już spotkałeś — łańcuch znaków — jest w istocie także
sekwencją. Poznasz nowy rodzaj pętli, który został skonstruowany specjalnie do pracy
z sekwencjami. W szczególności nauczysz się, jak:
 tworzyć pętle for, aby przechodzić przez sekwencje,
 używać funkcji range() do tworzenia sekwencji liczb,
 traktować łańcuchy znaków jako sekwencje,
 używać krotek w celu wykorzystania potencjału sekwencji,
 używać funkcji i operatorów działających na sekwencjach,
 indeksować i wycinać sekwencje.

Wprowadzenie do programu Wymieszane litery


Gra Wymieszane litery przedstawiona na rysunku 4.1 wykorzystuje wiele spośród
nowych koncepcji, które poznasz w tym rozdziale.
100 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Rysunek 4.1. Gra Wymieszane litery. Nie jest tak łatwo rozwiązać anagram

Ta gra odtwarza popularną łamigłówkę, jaką mógłbyś znaleźć w niedzielnym


wydaniu gazety (musisz wiedzieć, że ludzie mieli zwyczaj czytać takie coś przed
nastaniem ery internetu). Komputer wybiera losowo słowo z pewnej grupy, a potem
tworzy jego pomieszaną wersję, w której litery występują w przypadkowej kolejności.
Gracz, aby wygrać, musi odgadnąć oryginalne słowo.

Używanie pętli for


W poprzednim rozdziale poznałeś jeden rodzaj pętli — pętlę while, która powtarza
fragment kodu, bazując na warunku. Dopóki warunek jest spełniony, wykonywanie
pewnego kodu jest powtarzane. W pętli for również ma miejsce powtarzanie kodu,
lecz nie jest ono oparte na warunku. Zamiast tego pętla for powtarza fragment programu,
bazując na sekwencji — uporządkowanej liście pewnych elementów. Jeśli kiedykolwiek
sporządziłeś listę, powiedzmy, swoich dziesięciu najbardziej ulubionych filmów, to znaczy,
że utworzyłeś sekwencję.
Pętla for powtarza wykonywanie instrukcji tworzących jej ciało dla każdego elementu
sekwencji po kolei. Kiedy zostanie osiągnięty koniec sekwencji, pętla się kończy. Jako
przykład rozważ ponownie sekwencję tytułów filmów. Dzięki pętli for mógłbyś przejść
przez tę sekwencję od tytułu do tytułu i każdy z nich wyświetlić. Lecz najlepszym
sposobem na zrozumienie pętli for jest zobaczenie jej w działaniu.

Prezentacja programu Zwariowany łańcuch


Ten program pobiera słowo od użytkownika i wypisuje kolejno tworzące go litery
po jednej w wierszu. Popatrz na jego przykładowe uruchomienie przedstawione
na rysunku 4.2.
Wprowadzenie do programu Wymieszane litery 101

Rysunek 4.2. Pętla for przechodzi kolejno przez wszystkie litery słowa wprowadzonego
przez użytkownika

Ten prosty program stanowi dobry przykład użycia pętli for. Kod tego programu możesz
znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 4.; nazwa pliku to zwariowany_lancuch.py.
# Zwariowany łańcuch
# Demonstruje użycie pętli for z łańcuchem znaków
word = input("Wprowadź jakieś słowo: ")

print("\nOto poszczególne litery Twojego słowa:")


for letter in word:
print(letter)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Działanie pętli for


Nową koncepcją w tym programie jest pętla for, która składa się z następujących dwóch
krótkich wierszy:
for letter in word:
print(letter)

Kod jest dość jasny nawet dla kogoś, kto nic jeszcze nie wie o pętlach for, ale wyjaśnię
dokładnie, jak działa. Wszystkie sekwencje składają się z elementów. Łańcuch to sekwencja,
w której każdy element to jeden znak. W przypadku łańcucha "Pętla" pierwszym
elementem jest litera "P", drugim "ę" itd.
Pętla for maszeruje przez sekwencję od elementu do elementu (iteruje po elementach
sekwencji). W moim programie pętla iteruje po znakach łańcucha "Pętla". Pętla for
wykorzystuje zmienną, która udostępnia każdy kolejny znak łańcucha "Pętla".
102 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Wtedy pętla może z każdym kolejnym elementem coś zrobić wewnątrz swojego ciała.
W ciele mojej pętli wypisuję po prostu wartość zmiennej letter. Zmienna, której
używasz do udostępniania poszczególnych elementów sekwencji, nie różni się od
dowolnej innej zmiennej — a jeśli nie istniała przed pętlą, zostaje utworzona.
Więc kiedy moja pętla się zaczyna, jest tworzona zmienna letter, a jej wartością staje
się pierwszy znak łańcucha przypisanego do zmiennej word, którym jest litera "P". Następnie
w ciele pętli instrukcja print wyświetla P. Po zakończeniu wykonywania ciała pętli
sterowanie wraca do początku pętli i zmienna letter udostępnia kolejny znak łańcucha
reprezentowanego przez zmienną word, którym jest "ę". Komputer wyświetla literę ę
i pętla kontynuuje swoje działanie, dopóki nie zostaną wyświetlone wszystkie litery
łańcucha "Pętla".

W świecie rzeczywistym
Większość współczesnych języków oferuje jakąś formę pętli for. Te pętle są
jednak zwykle bardziej restrykcyjne. Pętle na ogół udostępniają jedynie zmienną
odgrywającą rolę licznika, której wartością musi być liczba. Wówczas licznik
zmienia się o taką samą wartość przy każdym wykonaniu pętli. Możliwość
bezpośredniej iteracji po elementach sekwencji sprawia, że pętla for
w Pythonie jest bardziej elastyczna niż ten inny, bardziej tradycyjny typ pętli.

Tworzenie pętli for


W celu utworzenia pętli for możesz posłużyć się przykładem z programu. Zacznij od
słowa for; po nim wprowadź kolejno: nazwę zmiennej reprezentującej bieżący element
sekwencji, słowo in, sekwencję, po której chcesz iterować, dwukropek i na koniec ciało
pętli. I to wszystko.

Liczenie za pomocą pętli for


Gdy piszesz program, może się okazać, że chcesz coś policzyć. Do liczenia na wszelkie
możliwe sposoby możesz użyć funkcji Pythona range() w połączeniu z pętlą for.

Prezentacja programu Licznik


Program Licznik nie zawiera nic specjalnego, ale pokazuje, jak można wykorzystać
funkcję range() i pętlę for do liczenia w przód i w tył czy nawet z przeskakiwaniem liczb,
jeśli tak Ci się spodoba. Rzuć okiem na rysunek 4.3, aby zobaczyć wyniki programu.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 4.; nazwa pliku
to licznik.py.
Liczenie za pomocą pętli for 103

Rysunek 4.3. Funkcja range() i pętla for umożliwiają liczenie do przodu, co pięć i do tyłu

# Licznik
# Demonstruje funkcję range()

print("Liczenie:")
for i in range(10):
print(i, end=" ")

print("\n\nLiczenie co pięć:")
for i in range(0, 50, 5):
print(i, end=" ")

print("\n\nLiczenie do tyłu:")
for i in range(10, 0, -1):
print(i, end=" ")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

W świecie rzeczywistym
Zmienne odgrywające rolę licznika lub używane do sterowania pętlą są
tradycyjnie nazywane i, j lub k. Zwykle powinno się jednak tworzyć opisowe,
zrozumiałe nazwy zmiennych. Możesz w to wierzyć lub nie, ale nazwy i, j i k
są jasne dla doświadczonych programistów, którzy czytając Twój kod, wiedzą,
że potrzebujesz tylko licznika na krótki użytek.

Liczenie do przodu
Pierwsza pętla w programie liczy do przodu:
for i in range(10):
print(i, end=" ")
104 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Ta pętla for działa tak samo jak pętla for, którą widziałeś w programie Zwariowany
łańcuch. Ale tym razem sekwencja, po której iteruje pętla, jest generowana przez wartość
zwrotną funkcji range(). Możesz sobie wyobrazić, że funkcja range() zwraca sekwencję
liczb. Możesz więc sobie wyobrazić, że jeśli w wywołaniu przekażesz jej dodatnią liczbę
całkowitą, zwróci sekwencję zaczynającą się od 0 i zawierającą kolejne liczby całkowite,
aż do liczby stanowiącej argument wywołania z wyłączeniem tej ostatniej. Zatem możesz
sobie wyobrazić, że kod range() zwraca sekwencję [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].
To powinno pomóc Ci w wizualizacji tego, co się dzieje w pętli: w pierwszej iteracji pętli
zmienna i otrzymuje wartość 0, która jest następnie wypisywana, po czym w kolejnej
iteracji i otrzymuje wartość 1, która też zostaje wypisana; ta sama procedura powtarza się
w kolejnych iteracjach, aż do ostatniej, w której zmienna i otrzymuje wartość 9, jej nowa
wartość zostaje wypisana i pętla się kończy.

Pułapka
Chociaż wyobrażenie sobie wyniku funkcji range() jako sekwencji liczb całkowitych
może być pomocne, to nie jest jednak to, co funkcja tworzy. W rzeczywistości
funkcja zwraca w odpowiednim momencie kolejną liczbę z sekwencji. Wyobrażenie
sobie całej sekwencji liczb zwróconej od razu w całości jest do naszych celów
całkiem wystarczające. Jeśli chcesz się dowiedzieć więcej o wewnętrznych
mechanizmach funkcji range(), zajrzyj do dokumentacji na stronie
http://www.python.org.

Wskazówka
Możesz utworzyć swoją własną sekwencję wartości, zwaną listą, poprzez zamknięcie
tych wartości, oddzielonych przecinkami, w nawiasach. Ale nie przystępuj od razu
do tworzenia list. Obiecuję, że wszystkiego o listach dowiesz się w rozdziale 5.,
„Listy i słowniki. Gra Szubienica”.

Liczenie co pięć
Następna pętla liczy co pięć:
for i in range(0, 50, 5):
print(i, end=" ")

Jeśli wywołasz funkcję range() z trzema argumentami, zostaną one potraktowane


jako punkt początkowy, punkt końcowy i liczba, wskazująca, co ile należy liczyć. Punkt
początkowy oznacza zawsze pierwszą wartość w naszej wyimaginowanej sekwencji,
podczas gdy punkt końcowy nigdy się w niej nie zawiera. Więc w tym przypadku nasza
wyimaginowana sekwencja to [0, 5, 10, 15, 20, 25, 30, 35, 40, 45]. Zwróć uwagę,
że sekwencja kończy się liczbą 45. Pamiętaj, że 50 to punkt końcowy, więc sekwencja go
nie obejmuje. Jeśli chcesz do niej dołączyć liczbę 50, musisz wybrać punkt końcowy
większy niż 50. Więc na przykład wywołanie o postaci range(0, 51, 5) byłoby dobrym
na to sposobem.
Stosowanie funkcji i operatorów sekwencji do łańcuchów znaków 105

Liczenie do tyłu
Ostatnia pętla w programie liczy do tyłu:
for i in range(10, 0, -1):
print(i, end=" ")

Robi tak, ponieważ ostatnim argumentem w wywołaniu funkcji range() jest -1.
To sprawia, że funkcja przechodzi od punktu początkowego do końcowego dzięki
dodawaniu w każdym kroku liczby –1 do poprzedniej wartości. To jest to samo
co odejmowanie liczby 1. Możesz sobie wyobrazić, że wywołanie funkcji range()
tworzy sekwencję [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]. Tak więc pętla liczy w dół od 10
do 1 i nie obejmuje liczby 0.

Sztuczka
Nie istnieje żadne prawo, które nakazywałoby używanie zmiennej pętli wewnątrz
pętli for. Mogłoby się zdarzyć, że chcesz powtórzyć pewną czynność określoną
liczbę razy. Aby to zrobić, wystarczy, że utworzysz pętlę for i po prostu zignorujesz
zmienną pętli w jej ciele. Powiedzmy na przykład, że chciałbym wyświetlić "Hej!"
10 razy. Wszystko, czego bym potrzebował, zawiera się w następujących dwóch
wierszach kodu:
for i in range(10):
print("Hej!")

Stosowanie funkcji i operatorów sekwencji


do łańcuchów znaków
Jak się nieco wcześniej dowiedziałeś, łańcuchy stanowią jeden z typów sekwencji,
składającej się z pojedynczych znaków. Python oferuje pewną liczbę użytecznych funkcji
i operatorów, które działają na sekwencjach dowolnego rodzaju, nie wyłączając
łańcuchów. Te operatory i funkcje mogą dostarczać podstawowe, niemniej ważne
informacje o sekwencji, takie jak jej długość i to, czy zawiera określony element.

Prezentacja programu Analizator komunikatów


Kolejny program analizuje dowolny komunikat, który wprowadzisz. Podaje długość
wprowadzonego komunikatu oraz informację, czy zawiera on literę, która występuje
w języku polskim najczęściej (literę „a”). Program wykonuje to zadanie za pomocą nowej
funkcji i nowego operatora, które działają na sekwencjach. Uruchomienie tego programu
pokazano na rysunku 4.4.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 4.; nazwa pliku
to analizator_komunikatow.py.
106 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Rysunek 4.4. Ten program używa funkcji len() oraz operatora in do wyświetlenia pewnych
informacji o Twoim komunikacie

# Analizator komunikatów
# Demonstruje funkcję len() i operator in

message = input("Wprowadź komunikat: ")

print("\nDługość Twojego komunikatu wynosi:", len(message))

print("\nNajczęściej używana litera w języku polskim, 'a',")


if "a" in message:
print("wystąpiła w Twoim komunikacie.")
else:
print("nie wystąpiła w Twoim komunikacie.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Użycie funkcji len()


Po wczytaniu komunikatu użytkownika program wyświetla informację o długości
komunikatu za pomocą instrukcji:
print("\nDługość Twojego komunikatu wynosi:", len(message))

Można przekazać do funkcji len() dowolną sekwencję, a funkcja zwróci jej długość.
Długość sekwencji to liczba jej elementów. Ponieważ komunikat przypisany do zmiennej
message zawiera 11 znaków (licząc każdy znak, łącznie ze spacją i wykrzyknikiem), jego
długość wynosi 11.
Indeksowanie łańcuchów 107

Użycie operatora in
Litera „a” jest najczęściej używaną literą w języku polskim. Do sprawdzenia, czy „a” jest
zawarte w komunikacie wprowadzonym przez użytkownika, program wykorzystuje kod
zawarty w następujących wierszach:
if "a" in message:
print("wystąpiła w Twoim komunikacie.")
else:
print("nie wystąpiła w Twoim komunikacie.")

Warunek w instrukcji if ma postać "a" in message. Jeśli zmienna message zawiera


znak "a", warunek jest prawdziwy. Jeśli zmienna message nie zawiera "a", jest fałszywy.
W przykładowym uruchomieniu programu wartością zmiennej message jest łańcuch
"Koniec gry!", który nie zawiera litery "a". Więc warunek "a" in message przyjął
wartość False i komputer wypisał zawartość łańcucha "nie wystąpiła w Twoim
komunikacie.". Gdyby warunek był fałszywy (gdyby na przykład wartością zmiennej
message był łańcuch "Programowanie w Pythonie"), komputer wyświetliłby tekst
wystąpiła w Twoim komunikacie. Jeśli jakiś element jest zawarty w sekwencji, nazywany
jest składnikiem tej sekwencji.
Operatora in możesz użyć we własnych programach do sprawdzenia, czy jakiś
element jest składnikiem sekwencji. Wystarczy po elemencie, który chcesz zbadać,
umieścić operator in, a po nim samą sekwencję. W ten sposób utworzysz warunek.
Jeżeli ten element jest składnikiem sekwencji, warunek jest prawdziwy; w przeciwnym
wypadku jest fałszywy.

Indeksowanie łańcuchów
Dzięki użyciu pętli for możesz przemieszczać się wzdłuż całego łańcucha znak po znaku,
zgodnie z ich kolejnością. Nazywa się to dostępem sekwencyjnym, który oznacza,
że musisz przechodzić przez sekwencję element po elemencie. Dostęp sekwencyjny
przypomina postępowanie ze stosem skrzynek tak ciężkich, że można podnieść
tylko jedną naraz. Aby się dostać do skrzynki znajdującej się na samym dole stosu
zawierającego pięć skrzynek, musiałbyś zdjąć górną skrzynkę, potem kolejną, po niej
następną i jeszcze jedną, by w końcu dotrzeć do ostatniej. Czyż nie byłoby przyjemnie
chwycić ostatnią skrzynkę bez oglądania się na wszystkie pozostałe? Ten rodzaj
bezpośredniego dostępu nazywa się dostępem swobodnym. Pozwala on dostać się
bezpośrednio do dowolnego elementu sekwencji. Na szczęście istnieje sposób na
swobodny dostęp do elementów sekwencji. Nazywa się indeksowaniem. Poprzez
indeksowanie specyfikujesz numer pozycji (czyli indeks) w sekwencji i uzyskujesz dostęp
do elementu znajdującego się na tej pozycji. W przykładzie ze skrzynkami mógłbyś
dostać się do dolnej skrzynki bezpośrednio poprzez zażądanie skrzynki numer pięć.
108 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Prezentacja programu Dostęp swobodny


Program Dostęp swobodny wykorzystuje indeksowanie sekwencji w celu uzyskania
bezpośredniego dostępu do losowo wybranych znaków łańcucha. Wybiera losowo
pozycję w łańcuchu "indeks" i wypisuje literę o tym numerze pozycji. Robi to 10 razy,
aby uzyskać dobrą próbę losową pozycji. Na rysunku 4.5 pokazano ten program
w działaniu.

Rysunek 4.5. Dzięki indeksowaniu można uzyskać bezpośredni dostęp do dowolnego znaku
w łańcuchu

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 4.; nazwa pliku
to dostep_swobodny.py.
# Dostęp swobodny
# Demonstruje indeksowanie łańcucha znaków

import random

word = "indeks"
print("Wartość zmiennej word to: ", word, "\n")

high = len(word)
low = -len(word)

for i in range(10):
position = random.randrange(low, high)
print("word[", position, "]\t", word[position])

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


Indeksowanie łańcuchów 109

Używanie dodatnich numerów pozycji


Jedną z pierwszych rzeczy, które robię w tym programie, jest przypisanie wartości
łańcuchowej do zmiennej:
word = "indeks"

Nie ma w tym nic nowego. Ale robiąc to, tworzę sekwencję (podobnie jak za każdym
razem, gdy tworzę łańcuch), w której każdy znak zajmuje pozycję o określonym numerze.
Pierwsza litera, „i”, zajmuje pozycję 0. (Pamiętaj, że komputery zwykle rozpoczynają
liczenie od 0). Druga litera, „n”, zajmuje pozycję 1. Trzecia litera, „d”, pozycję 2 itd.
Uzyskanie dostępu do pojedynczego znaku łańcucha jest proste. Aby uzyskać dostęp
do litery zajmującej pozycję 0, wystarczy napisać word[0]. W przypadku dowolnej innej
pozycji powinieneś podstawić jej numer w miejsce 0. Pomóc Ci w utrwaleniu tej
koncepcji powinno przyjrzenie się fragmentowi mojej interaktywnej sesji:
>>> word = "indeks"
>>> print(word[0])
i
>>> print(word[1])
n
>>> print(word[2])
d
>>> print(word[3])
e
>>> print(word[4])
k
>>> print(word[5])
s

Pułapka
Ponieważ w łańcuchu "indeks" występuje sześć liter, mógłbyś pomyśleć,
że ostatnia litera, „s”, zajmuje pozycję 6. Ale nie miałbyś racji. W tym łańcuchu
nie ma pozycji 6, ponieważ komputer zaczyna liczenie od 0. Prawidłowe dodatnie
pozycje to 0, 1, 2, 3, 4 i 5. Każda próba dostępu do pozycji 6 spowoduje
wystąpienie błędu. Aby uzyskać potwierdzenie tego faktu, spójrz na poniższą
sesję interaktywną:
>>> word = "indeks"
>>> print(word[6])
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
print(word[6])
IndexError: string index out of range

Trochę niegrzecznie komputer komunikuje, że pozycja 6 nie istnieje. Więc


zapamiętaj — ostatni element w sekwencji zajmuje pozycję o numerze
mniejszym o jeden od jej długości.
110 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Używanie ujemnych numerów pozycji


Z wyjątkiem koncepcji, że pierwsza litera łańcucha zajmuje pozycję 0, a nie 1, korzystanie
z dodatnich numerów pozycji wydaje się dość naturalne. Lecz istnieje również sposób
dostępu do elementów sekwencji poprzez ujemne numery pozycji. Przy dodatnich
numerach pozycji punktem odniesienia jest początek sekwencji. W przypadku łańcuchów
to oznacza, że liczenie pozycji rozpoczyna się od pierwszego znaku. Lecz przy ujemnych
numerach pozycji liczenie rozpoczyna się od końca. W przypadku łańcuchów oznacza to,
że liczenie należy rozpocząć od ostatniego znaku i posuwać się do tyłu.
Najlepszym sposobem na zrozumienie, jak funkcjonują ujemne numery pozycji, jest
zobaczenie przykładu. Przyjrzyj się jeszcze jednej sesji interaktywnej w moim wykonaniu,
wykorzystującej ponownie łańcuch "indeks":
>>> word = "indeks"
>>> print(word[-1])
s
>>> print(word[-2])
k
>>> print(word[-3])
e
>>> print(word[-4])
d
>>> print(word[-5])
n
>>> print(word[-6])
i

Możesz zaobserwować w tej sesji, że wyrażenie word[-1] oznacza dostęp do ostatniej


litery łańcucha "indeks", którą jest „s”. Kiedy używa się ujemnych numerów pozycji, –1
wskazuje ostatni element, indeks –2 — przedostatni element, indeks –3 — trzeci element
od końca itd. Czasem sensowniejszy jest wybór końca sekwencji jako punktu odniesienia.
W takich sytuacjach możesz wykorzystać ujemne numery pozycji.
Rysunek 4.6 przedstawia w klarowny sposób łańcuch "indeks" rozbity na pojedyncze
znaki, którym odpowiadają numery pozycji, zarówno dodatnie, jak i ujemne.

Rysunek 4.6. Możesz uzyskać dostęp do dowolnej litery łańcucha "indeks",


wykorzystując dodatni lub ujemny numer pozycji

Dostęp do wybranego losowo elementu łańcucha


Pora na powrót do programu Dostęp swobodny. Aby uzyskiwać dostęp do wybranej
losowo litery z łańcucha "indeks", muszę generować liczby losowe. Dlatego pierwszą
rzeczą, jaką zrobiłem w programie, był import modułu random:
import random
Niemutowalność łańcuchów 111

Następnie potrzebowałem sposobu na wybieranie dowolnego prawidłowego numeru


pozycji w łańcuchu przypisanym do zmiennej word — ujemnego lub dodatniego.
Chciałem, aby mój program potrafił wygenerować liczbę losową z zakresu od –6 do 4
włącznie, ponieważ są to wszystkie możliwe wartości pozycji w łańcuchu word. Na
szczęście funkcja random.randrange() może przyjąć jako argumenty dwa punkty
końcowe i wygenerować liczbę losową mieszczącą się między nimi. Więc utworzyłem
dwa punkty końcowe:
high = len(word)
low = -len(word)

Zmienna high otrzymuje wartość 6, ponieważ łańcuch "indeks" zawiera sześć


znaków. Zmienna low otrzymuje ujemną wartość przeciwną do długości łańcucha word
(wynika to z umieszczenia znaku minusa przed wartością liczbową). Tak więc zmiennej
low zostaje przypisana wartość –6. Te dwie wartości reprezentują przedział, z którego
chcę wybrać liczbę losową.
Właściwie chcę wygenerować liczbę losową mieszczącą się w zakresie od –6 do 6,
z włączeniem pierwszej, lecz wyłączeniem drugiej wartości. I właśnie dokładnie w ten
sposób działa funkcja random.randrange(). Jeśli przekażesz jej dwa argumenty, zwróci
liczbę losową z przedziału przez nie wyznaczonego, który zawiera swój dolny koniec,
lecz nie zawiera górnego. Dlatego w moim przykładowym uruchomieniu wiersz:
position = random.randrange(low, high)

generuje jedną z liczb: –6, –5, –4, –3, –2, –1, 0, 1, 2, 3, 4 lub 5. Dokładnie o to mi chodzi,
ponieważ są to wszystkie możliwe prawidłowe numery pozycji odnoszące się do łańcucha
"indeks".
Na koniec utworzyłem pętlę for, która wykonuje się 10 razy. W ciele pętli program
wybiera losowo numer pozycji oraz wyświetla jego wartość i odpowiadającą mu literę:
for i in range(10):
position = random.randrange(low, high)
print("word[", position, "]\t", word[position])

Niemutowalność łańcuchów
Sekwencje należą do dwóch kategorii — mogą być mutowalne lub niemutowalne.
(Znów kolejne określenia z komputerowego żargonu). Mutowalny oznacza podlegający
zmianom. Tak więc sekwencja, która jest mutowalna, jest taką sekwencją, która może się
zmieniać. Niemutowalny oznacza niezmienny. Dlatego też sekwencja niemutowalna
to taka sekwencja, która nie może się zmieniać. Łańcuchy znaków są sekwencjami
niemutowalnymi, co oznacza, że nie mogą się zmieniać. Więc na przykład łańcuch
"Koniec gry!" zawsze pozostanie łańcuchem "Koniec gry!". Nie można go zmienić.
W rzeczywistości nie możesz zmienić jakiegokolwiek łańcucha, który utworzysz. Teraz
mógłbyś pomyśleć na podstawie swojego doświadczenia z łańcuchami, że w tej kwestii
112 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

całkowicie się mylę. Mógłbyś nawet uruchomić sesję interaktywną, żeby udowodnić,
że możesz zmienić łańcuch. Mogłaby ona przypominać coś takiego:
>>> name = "Jaś"
>>> print(name)
Jaś
>>> name = "Małgosia"
>>> print(name)
Małgosia

Mógłbyś to przedstawić jako dowód na to, że można zmienić łańcuch. Jakkolwiek


by było, zmieniłeś łańcuch "Jaś" na "Małgosia". Lecz nie zmieniłeś ani jednego łańcucha
w tej sesji. Utworzyłeś jedynie dwa różne łańcuchy. Najpierw utworzyłeś łańcuch "Jaś"
i przypisałeś go do zmiennej name. Potem utworzyłeś kolejny łańcuch, "Małgosia",
i przypisałeś go znów do zmiennej name. Otóż zarówno „Jaś”, jak i „Małgosia” to wspaniałe
imiona, ale są to różne imiona i zawsze takimi pozostaną, tak jak są i zawsze będą różnymi
łańcuchami. Popatrz na rysunek 4.7 przedstawiający w wizualnej postaci to, co zdarzyło
się w trakcie sesji interaktywnej.

Rysunek 4.7. Najpierw do zmiennej imię zostaje przypisany łańcuch "Jaś",


potem zmienna otrzymuje nową wartość w postaci łańcucha "Małgosia",
ale same łańcuchy nie ulegają nigdy zmianie

Innym sposobem zobrazowania tej myśli jest wyobrażenie sobie, że łańcuchy zostały
zapisane atramentem na karteczkach papieru. Można wyrzucić karteczkę z zapisanym
na niej łańcuchem lub zamienić ją na inną karteczkę papieru z nowym łańcuchem,
ale nie można zmienić samych słów, kiedy już zostały napisane.
Mógłbyś pomyśleć, że robi się wiele hałasu o nic. Co z tego, że łańcuch jest niemutowalny?
Lecz niemutowalność łańcucha ma swoje konsekwencje. Ponieważ nie można zmienić
łańcucha, nie można przypisać do łańcucha nowego znaku poprzez indeksowanie.
Oto sesja interaktywna, która powinna pokazać, co mam na myśli:
>>> word = "gama"
>>> word[0] = "l"
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
word[0] = "l"
TypeError: 'str' object does not support item assignment

W tej sesji chciałem zamienić łańcuch "gama" na łańcuch "lama". Potrzebowałem


tylko zamienić literę „g” na „l”. Toteż próbowałem przypisać "l" do pierwszej pozycji
Tworzenie nowego łańcucha 113

w łańcuchu, word[0]. Lecz jak widać, skończyło się to wygenerowaniem opasłego komunikatu
o błędzie. Interpreter informuje mnie nawet, że łańcuchy nie obsługują przypisania do
własnego elementu (nie można przypisać nowej wartości znakowi łańcucha).
Ale to, że nie można zmieniać łańcucha, nie oznacza, iż nie można tworzyć nowych
łańcuchów z już istniejących.

Tworzenie nowego łańcucha


Już dowiedziałeś się, jak można dokonać konkatenacji dwóch łańcuchów za pomocą
operatora +. Czasem możesz chcieć skonstruować nowy łańcuch znak po znaku.
Ponieważ łańcuchy są niemutowalne, tym, co naprawdę będziesz robił, jest tworzenie
nowego łańcucha za każdym razem, gdy będziesz używał operatora konkatenacji.

Prezentacja programu Bez samogłosek


Kolejny program, Bez samogłosek, pobiera komunikat od użytkownika i wypisuje go
z pominięciem wszystkich samogłosek. Na rysunku 4.8 pokazano ten program w działaniu.

Rysunek 4.8. Przy użyciu pętli for są tworzone nowe łańcuchy.


Program pomija operację konkatenacji przy wszystkich samogłoskach

Program tworzy na podstawie oryginalnego komunikatu nowy łańcuch pozbawiony


samogłosek. W rzeczywistości tworzy całą serię nowych łańcuchów. Kod tego programu
możesz znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 4.; nazwa pliku to bez_samoglosek.py.
# Bez samogłosek
# Demonstruje tworzenie nowych łańcuchów przy użyciu pętli for
114 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

message = input("Wprowadź komunikat: ")


new_message = ""
VOWELS = "aąeęioóuy"

print()
for letter in message:
if letter.lower() not in VOWELS:
new_message += letter
print("Został utworzony nowy łańcuch: ", new_message)

print("\nTwój komunikat bez samogłosek to:", new_message)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Tworzenie stałych
Po pobraniu komunikatu od użytkownika i utworzeniu pustego nowego komunikatu
program tworzy łańcuch:
VOWELS = "aąeęioóuy"

Do zmiennej VOWELS zostaje przypisany łańcuch wszystkich samogłosek. Prawdopodobnie


zauważyłeś, że nazwa zmiennej składa się z samych dużych liter na przekór temu, czego
się wcześniej dowiedziałeś — nazwy zmiennych są tradycyjnie pisane małymi literami.
Cóż, nie odszedłem w tym przypadku od tradycji. W rzeczywistości nazwom zmiennych
pisanym samymi dużymi literami przypisuje się specjalne znaczenie. Nazywają się stałymi
i odwołują się do wartości, która zgodnie z zamysłem nie powinna się zmienić
(ich wartość jest stała).
Stałe są cenne dla programistów z dwóch względów. Po pierwsze, sprawiają,
że programy są bardziej przejrzyste. W tym programie wykorzystuję nazwę zmiennej
VOWELS wszędzie, gdzie potrzebuję użyć sekwencji wszystkich samogłosek, zamiast
łańcucha "aąeęioóuy". Użycie nazwy zmiennej zamiast łańcucha sprawia, że kod jest
bardziej zrozumiały. Kiedy widzisz nazwę zmiennej, rozumiesz, co ona oznacza, podczas
gdy sam dziwnie wyglądający łańcuch mógłby Cię nieco dezorientować. Po drugie,
stosowanie stałych pozwala uniknąć przepisywania wartości (oraz być może błędów
wynikających z omyłek przy ich przepisywaniu). Stałe są szczególnie użyteczne, kiedy
masz do czynienia z taką wartością, jak bardzo długa liczba lub łańcuch znaków.
Wykorzystuj stałą w programach, w których tej samej, niezmiennej wartości musisz
używać w wielu miejscach.

Pułapka
Musisz być ostrożny, kiedy tworzysz stałe z wykorzystaniem nazwy zmiennej
składającej się z samych dużych liter. Jeśli nawet powiesz sam sobie i innym
programistom, że ta zmienna zawsze będzie się odnosić do tej samej wartości,
to nie istnieje w Pythonie żaden mechanizm, który Cię powstrzyma przed zmianą
jej wartości w Twoim programie. Ta praktyka nazewnicza jest po prostu konwencją.
Więc jeśli już utworzysz zmienną o nazwie złożonej z samych dużych liter,
pamiętaj o traktowaniu jej jako wartości niepodlegającej zmianie.
Tworzenie nowego łańcucha 115

W świecie rzeczywistym
W niektórych językach programowania stałe są dokładnie tym, co oznaczają. Nie
mogą zostać zmienione po ich zdefiniowaniu. Jest to najbezpieczniejszy sposób
tworzenia i używania stałych. Jednak w Pythonie nie istnieje prosty sposób
tworzenia własnych prawdziwych stałych.

Tworzenie nowych łańcuchów z już istniejących


Prawdziwa praca programu wykonuje się w pętli. W trakcie wykonywania pętli program
tworzy nowy komunikat niezawierający żadnych samogłosek. Przy każdym przebiegu
komputer sprawdza kolejną literę w oryginalnym komunikacie. Jeśli nie jest to samogłoska,
dodaje tę literę do nowo tworzonego komunikatu. Jeśli jest to samogłoska, program
przechodzi do następnej litery. Wiesz już, że program nie może w sensie dosłownym
dodać litery do łańcucha, więc dokładniej mówiąc, kiedy program napotyka znak, który
nie jest samogłoską, dokonuje konkatenacji dotychczasowego nowego komunikatu z tym
znakiem, by utworzyć w ten sposób nowy łańcuch. Kod, który to realizuje, wygląda
następująco:
for letter in message:
if letter.lower() not in VOWELS:
new_message += letter
print("Został utworzony nowy łańcuch: ", new_message)

Pętla zawiera dwie nowe koncepcje, więc pozwól, że omówię obydwie. Po pierwsze,
Python jest grymaśny, gdy ma do czynienia z łańcuchami i znakami. Litera "A" to nie to
samo co "a". Ponieważ stałej VOWELS został przypisany łańcuch, który zawiera tylko małe
litery, musiałem uzyskać pewność, że przy użyciu operatora in sprawdzam tylko małe
litery. Właśnie dlatego użyłem metody letter.lower().

Sztuczka
Kiedy porównujesz dwa łańcuchy, często chciałbyś zignorować różnice wynikające
tylko z użycia małych lub dużych liter. Jeśli zapytasz gracza, czy chce kontynuować
grę, łańcuch "Tak" jest równie dobrą odpowiedzią jak łańcuch "tak". Cóż, w tych
przypadkach musisz pamiętać o zamianie w obu łańcuchach wszystkich dużych
liter na małe (możesz postąpić odwrotnie — nie ma to żadnego znaczenia)
przed ich porównaniem.

Oto przykład. Powiedzmy, że chcę porównać dwa łańcuchy dostępne poprzez


zmienne name i winner, aby sprawdzić, czy są równe, i nie interesuje mnie zgodność
wielkości liter. Mógłbym utworzyć warunek:
name.lower() == winner.lower()

Ten warunek jest prawdziwy, jeśli łańcuchy name i winner zawierają ten sam ciąg
znaków przy zignorowaniu wielkości liter. Więc łańcuchy "Marek" i "marek" są zgodne.
Łańcuchy "MAREK" i "marek" — także. Nawet porównanie zgodności łańcuchów "MaReK"
i "mArEk" daje pozytywny wynik.
116 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Po drugie, mogłeś również zauważyć, że użyłem w programie operatora rozszerzonego


przypisania (+=) do konkatenacji łańcuchów. Spotkałeś się z użyciem operatorów
rozszerzonego przypisania w kontekście liczb, ale ich zastosowanie obejmuje także
łańcuchy. Więc wiersz:
new_message += letter

oznacza dokładnie to samo co:


new_message = new_message + letter

Wycinanie łańcuchów
Indeksowanie jest pożyteczną techniką, ale nie jesteś ograniczony do kopiowania za
każdym razem tylko jednego elementu z sekwencji. Możesz wykonywać kopie ciągłych
fragmentów sekwencji (zwanych wycinkami). Możesz skopiować (czyli wyciąć) jeden
element (tak jak przy indeksowaniu) lub część sekwencji (jak na przykład środkowe
trzy elementy). Możesz nawet utworzyć wycinek, który jest kopią całej sekwencji.
Więc w przypadku łańcuchów możesz przechwycić wszystko: od pojedynczego znaku,
poprzez grupę kolejnych znaków, do całego łańcucha.

Prezentacja programu Krajacz pizzy


Program Krajacz pizzy pozwala Ci tworzyć wycinki łańcucha "pizza" w dowolny sposób.
Jest to wspaniała, interaktywna metoda, która może Ci pomóc w zrozumieniu wycinania.
Do Ciebie należy tylko wprowadzenie początkowej i końcowej pozycji wycinka,
a program wyświetli wynik. Program został pokazany na rysunku 4.9.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 4.; nazwa pliku
to krajacz_pizzy.py.
# Krajacz pizzy
# Demonstruje tworzenie wycinków łańcucha

word = "pizza"

print(
"""
'Ściągawka' tworzenia wycinków

0 1 2 3 4 5
+---+---+---+---+---+
| p | i | z | z | a |
+---+---+---+---+---+
-5 -4 -3 -2 -1

"""
)
Wycinanie łańcuchów 117

Rysunek 4.9. Świeże i gorące wycinki łańcucha "pizza" zostały utworzone


zgodnie z Twoim życzeniem. Program dodatkowo oferuje „ściągawkę”,
która pomoże Ci w wizualizacji sposobu tworzenia wycinka

print("Wprowadź początkowy i końcowy indeks wycinka łańcucha 'pizza'.")


print("Aby zakończyć tworzenie wycinków, w odpowiedzi na monit 'Początek:'\n"
+ "naciśnij klawisz Enter.")

start = None
while start != "":
start = (input("\nPoczątek: "))

if start:
start = int(start)

finish = int(input("Koniec: "))

print("word[", start, ":", finish, "] to", end=" ")


print(word[start:finish])

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


118 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Wartość None
Zanim przejdziesz do kodu dotyczącego tworzenia wycinków, popatrz na ten wiersz,
który prezentuje nową koncepcję:
start = None

W wierszu tym zmiennej start zostaje przypisana specjalna wartość o nazwie None.
Jest ona przyjętym w Pythonie sposobem reprezentowania pojęcia „nic” oraz dobrym
symbolem zastępczym. Traktowana jako warunek jest równoważna wartości False.
Użyłem jej w tym miejscu programu, ponieważ chciałem zainicjalizować zmienną start
przed użyciem jej w warunku pętli while.

Wycinanie
Tworzenie wycinka jest podobne do indeksowania. Ale zamiast używać pojedynczego
numeru pozycji, należy podać pozycję początkową i pozycję końcową. Każdy element
położony między tymi dwoma punktami staje się częścią wycinka. Rysunek 4.10 pokazuje
sposób postrzegania numerów punktów granicznych wycinka na przykładzie łańcucha
"pizza". Zwróć uwagę, że jest to nieco inny system numeracji niż indeksowanie
przedstawione na rysunku 4.6.
Aby zdefiniować punkty graniczne wycinka, zamknij obydwa w nawiasach,
oddzielając je dwukropkiem. Oto krótka sesja interaktywna, która powinna pokazać, co
mam na myśli:

Rysunek 4.10. Przykład numerów punktów granicznych wycinania wykorzystujący łańcuch


"pizza". Do zdefiniowania swojego wycinka możesz użyć dowolnej kombinacji dodatnich
i ujemnych punktów granicznych

>>> word = "pizza"


>>> print(word[0:5])
pizza
>>> print(word[1:3])
iz
>>> print(word[-4:-2])
iz
>>> print(word[-4:3])
iz
Wycinanie łańcuchów 119

Wyrażenie word[0:5] zwraca cały łańcuch, ponieważ wszystkie jego znaki znajdują się
między tymi dwoma punktami granicznymi. Natomiast word[1:3] zwraca łańcuch "iz",
ponieważ te dwa znaki znajdują się między punktami granicznymi. Podobnie jak
w przypadku indeksowania, możesz używać ujemnych numerów. Wyrażenie word[-4:-2]
także tworzy łańcuch "iz", ponieważ te właśnie znaki znajdują się między wskazanymi
ujemnymi numerami pozycji. Możesz również dobierać mieszane, dodatnie i ujemne
punkty graniczne. Działa to jak tworzenie dowolnego innego wycinka; znajdą się w nim
elementy położone między tymi dwoma numerami pozycji. Tak więc wyrażenie word[-4:3]
także tworzy łańcuch "iz", ponieważ te dwa znaki znajdują się między tymi dwoma
punktami granicznymi.

Pułapka
Jeśli spróbujesz utworzyć wycinek „niemożliwy”, w którym punkt początkowy jest
większy niż punkt końcowy, taki jak word[2:1], nie spowodujesz błędu. Zamiast
tego Python po cichu zwróci pustą sekwencję. W przypadku łańcuchów oznacza
to, że otrzymasz pusty łańcuch. Więc zachowaj ostrożność, ponieważ nie jest to
prawdopodobnie rodzaj wyniku, który chciałbyś uzyskać.

Tworzenie wycinków
Wewnątrz swojej pętli, program Krajacz pizzy wypisuje składnię tworzenia wycinka
na podstawie wprowadzonych przez użytkownika pozycji — początkowej i końcowej,
używając następującego wiersza kodu:
print("word[", start, ":", finish, "] to", end=" ")

Potem program wypisuje właściwy wycinek przy użyciu zmiennych start i finish:
print(word[start:finish])

Korzystanie ze skrótów przy wycinaniu


Chociaż można otrzymać każdy możliwy wycinek przez podanie dwóch numerów,
istnieje kilka skrótów stosowanych przy wycinaniu, które możesz wykorzystać. Możesz
opuścić punkt początkowy w specyfikacji wycinka, sprawiając, że będzie nim początek
sekwencji. Więc zakładając, że do zmiennej word został przypisany łańcuch "pizza",
wycinek word[:4] jest dokładnie takim samym wycinkiem jak word[0:4]. Możesz opuścić
punkt końcowy, aby wycinek kończył się ostatnim elementem łańcucha word. Więc
word[2:] jest skrótem wyrażenia word[2:5]. Możesz nawet opuścić obydwa numery,
aby otrzymać wycinek, który będzie całą sekwencją. Tak więc word[:] jest skrótem
wyrażenia word[0:5].
Na poparcie tego twierdzenia przedstawiam sesję interaktywną:
>>> word = "pizza"
>>>print(word[0:4])
pizz
>>>print(word[:4])
120 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

pizz
>>>print(word[2:5])
zza
>>>print(word[2:])
zza
>>>print(word[0:5])
pizza
>>> print(word[:])
Pizza

Sztuczka
Jeśli istnieje jeden taki element wiedzy o skrótach stosowanych przy wycinaniu,
który powinieneś zapamiętać, to jest nim fakt, że [:] zwraca całkowitą kopię
sekwencji. Kiedy będziesz programował, może się okazać, że będziesz chciał
wykonać kopię jakiejś sekwencji, a to jest szybki i efektywny sposób realizacji
tego zadania.

Tworzenie krotek
Krotki są typem sekwencji, podobnie jak łańcuchy. W odróżnieniu od łańcuchów, które
mogą zawierać tylko znaki, krotki mogą zawierać elementy dowolnego typu. To oznacza,
że możesz mieć krotkę, która przechowuje pewną liczbę najlepszych wyników uzyskanych
w grze, lub taką, która przechowuje grupę nazwisk pracowników. Ale nie wszystkie
elementy krotki muszą być tego samego typu. Gdybyś chciał, mógłbyś utworzyć krotkę,
która zawiera zarówno łańcuchy znaków, jak i liczby. I nie musisz się zatrzymywać na
samych łańcuchach czy liczbach. Możesz utworzyć krotkę, która zawiera sekwencję
obrazów graficznych, plików dźwiękowych lub nawet grupę przybyszów z kosmosu
(kiedy już dowiesz się, jak tworzyć takie rzeczy, co stanie się w trakcie lektury późniejszych
rozdziałów). Wszystko, co możesz przypisać do zmiennej, możesz zgrupować i przechować
jako sekwencję w krotce.

Prezentacja programu Inwentarz bohatera


Program Inwentarz bohatera obsługuje rejestr wyposażenia bohatera z typowej gry
z podziałem na role. Podobnie jak w większości gier, jakie kiedykolwiek powstały, bohater
pochodzi z małej, mało znaczącej wioski. Jego ojciec został oczywiście zabity przez złego
watażkę. (Co to za misja bez martwego ojca?). A teraz, kiedy bohater doszedł do
pełnoletności, nadszedł dla niego czas szukania zemsty.
W tym programie Inwentarz bohatera jest reprezentowany przez krotkę. Krotka
zawiera łańcuchy znaków odpowiadające poszczególnym elementom dobytku bohatera.
Bohater zaczyna od niczego, ale potem daję mu kilka przedmiotów. Rysunek 4.11
pokazuje skromne początki podróży naszego bohatera.
Wycinanie łańcuchów 121

Rysunek 4.11. Początkowo wykaz wyposażenia bohatera nie zawiera żadnych pozycji.
Potem program tworzy nową krotkę z elementami w postaci łańcuchów znaków
i nasz bohater zostaje wyekwipowany

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 4.; nazwa pliku
to inwentarz_bohatera.py.
# Inwentarz bohatera
# Demonstruje tworzenie krotek

# utwórz pustą krotkę


inventory = ()

# potraktuj krotkę jako warunek


if not inventory:
print("Nie posiadasz niczego.")

input("\nAby kontynuować misję, naciśnij klawisz Enter.")

# utwórz krotkę zawierającą pewne elementy


inventory = ("miecz",
"zbroja",
"tarcza",
"napój uzdrawiający")

# wyświetl krotkę
print("\nWykaz zawartości krotki:")
print(inventory)

# wyświetl każdy element krotki


print("\nElementy Twojego wyposażenia:")
for item in inventory:
print(item)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


122 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Tworzenie pustej krotki


Aby utworzyć krotkę, wystarczy ująć w nawiasy sekwencję wartości oddzielonych
przecinkami. Nawet para samotnych nawiasów oznacza prawidłową (chociaż pustą)
krotkę. Utworzyłem pustą krotkę w pierwszej części programu, która ma odzwierciedlać
fakt, że bohater nie posiada niczego:
inventory = ()

Jest to aż tak proste. Więc w powyższym wierszu zmienna inventory otrzymuje


wartość w postaci pustej krotki.

Traktowanie krotki jako warunku


Kiedy poznawałeś warunki, dowiedziałeś się, że w Pythonie każdą wartość możesz
traktować jako warunek. To oznacza, że jako warunek możesz potraktować także krotkę.
I właśnie to zrobiłem w kolejnych wierszach kodu:
if not inventory:
print("Masz puste ręce.")

Jako warunek pusta krotka ma wartość False. To oznacza, że wyrażenie not inventory
przyjmuje wartość True. Więc komputer wyświetla łańcuch znaków "Masz puste ręce.",
dokładnie tak, jak powinien.

Tworzenie krotki zawierającej elementy


Nieuzbrojony bohater jest nudnym bohaterem. Utworzyłem zatem nową krotkę
z elementami w postaci łańcuchów reprezentujących użyteczne dla naszego bohatera
przedmioty. Przypisałem tę nową krotkę do zmiennej inventory za pomocą następującej
instrukcji:
inventory = ("miecz",
"zbroja",
"tarcza",
"napój uzdrawiający")

Każdy element krotki jest oddzielony od sąsiedniego przecinkiem. To sprawia,


że pierwszym elementem zostaje łańcuch "miecz", drugim "zbroja", kolejnym "tarcza"
i ostatnim "napój uzdrawiający". Więc każdy łańcuch stanowi w tej krotce pojedynczy
element.
Zwróć także uwagę, że krotka obejmuje kilka wierszy. Możesz zapisać krotkę
w jednym wierszu lub, tak jak ja zrobiłem, rozciągnąć ją na wiele wierszy, pod warunkiem
że każdy wiersz (z wyjątkiem ostatniego) zakończysz przecinkiem. Jest to jeden z kilku
przypadków, w których Python pozwala rozbić instrukcję na wiele wierszy.
Wycinanie łańcuchów 123

Sztuczka
Zapisuj krotki w wielu wierszach, aby Twoje programy stały się czytelniejsze.
Jednak nie musisz umieszczać dokładnie jednego elementu w każdym wierszu.
Zapisywanie kilku elementów w jednym wierszu też może okazać się sensowne.
Pamiętaj tylko, żeby każdy wiersz kończył się przecinkiem oddzielającym
elementy, a wszystko będzie dobrze.

Wypisywanie krotki
Chociaż krotka może zawierać wiele elementów, możesz wypisać całą krotkę w taki sam
sposób jak dowolną pojedynczą wartość. To właśnie zrobiłem w kolejnym wierszu kodu:
print("\nWykaz zawartości krotki:")
print(inventory)

Komputer wyświetla wszystkie elementy krotki, ujmując całość w nawiasy.

Przechodzenie w pętli przez elementy krotki


Na koniec napisałem pętlę for, aby maszerować przez kolejne elementy krotki inventory
i wyświetlać każdy z nich osobno:
print("\nElementy Twojego wyposażenia:")
for item in inventory:
print(item)

Ta pętla wypisuje każdy element (każdy łańcuch) krotki inventory w oddzielnym


wierszu. Wygląda ona dokładnie tak samo jak pętle, z którymi się spotkałeś przy obsłudze
łańcuchów. W gruncie rzeczy możesz używać tego rodzaju pętli do przechodzenia przez
elementy dowolnej sekwencji.
Chociaż utworzyłem krotkę, w której wszystkie elementy są tego samego typu (w tym
przypadku łańcuchy znaków), krotki nie muszą być wypełnione elementami tego samego
typu. Pojedyncza krotka może na przykład zawierać i łańcuchy znaków, i liczby
całkowite, i liczby zmiennoprzecinkowe.

Pułapka
Inne języki programowania oferują struktury podobne do krotek. Niektóre noszą
nazwę tablic lub wektorów. Jednak te inne języki ograniczają zwykle elementy
tych sekwencji do jednego typu. Więc na przykład nie mógłbyś łączyć ze sobą
łańcuchów i liczb. Chodzi tylko o to, byś miał świadomość, że te inne struktury
nie oferują zwykle całej elastyczności charakteryzującej sekwencje Pythona.

Wykorzystywanie krotek
Ponieważ krotki są po prostu jeszcze jednym rodzajem sekwencji, wszystko, czego się
dowiedziałeś o sekwencjach na przykładzie łańcuchów, ma zastosowanie do krotek.
Możesz pobrać długość krotki, wypisać jej wszystkie elementy za pomocą pętli for
124 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

oraz użyć operatora in do sprawdzenia, czy dany element jest w niej zawarty. Możesz
także indeksować, wycinać i konkatenować krotki.

Prezentacja programu Inwentarz bohatera 2.0


Podróż naszego bohatera trwa dalej. W tym programie jego inwentarz jest liczony,
sprawdzany, indeksowany i wycinany. Nasz bohater natrafi także na skrzynię zawierającą
pewne przedmioty (reprezentowaną przez jeszcze jedną krotkę). Dzięki konkatenacji
krotek wykaz dobytku naszego bohatera zostanie zastąpiony nowym, zawierającym jego
obecne elementy plus skarb znaleziony w skrzyni. Na rysunku 4.12 zostało pokazane
przykładowe uruchomienie programu.

Rysunek 4.12. Inwentarz bohatera jest krotką, co oznacza, że może być liczony,
indeksowany, poddawany operacji wycinania, a nawet konkatenowany z inną krotką

Ponieważ ten program jest nieco długi, omówię kod fragment po fragmencie,
nie pokazując od razu jego całości. Kod tego programu możesz znaleźć na stronie
internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału
4.; nazwa pliku to inwentarz_bohatera2.py.

Początek programu
Pierwsza część tego programu przypomina swoim działaniem poprzedni program,
Inwentarz bohatera. Poniższe wiersze kodu tworzą krotkę i wypisują każdy jej element:
Wycinanie łańcuchów 125

# Inwentarz bohatera 2.0


# Demonstruje krotki

# utwórz krotkę zawierającą pewne elementy i wyświetl jej zawartość


# za pomocą pętli for
inventory = ("miecz",
"zbroja",
"tarcza",
"napój uzdrawiający")
print("Elementy Twojego inwentarza:")
for item in inventory:
print(item)

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Stosowanie funkcji len() do krotek


Funkcja len() działa w odniesieniu do krotek w dokładnie taki sam sposób jak
w odniesieniu do łańcuchów znaków. Jeśli chcesz poznać długość krotki, umieść ją
wewnątrz nawiasów. Funkcja zwraca liczbę elementów w krotce. Puste krotki, tak jak
wszystkie puste sekwencje, mają długość równą 0. Poniższe wiersze kodu zawierają
użycie funkcji len() w stosunku do krotki:
# wyświetl długość krotki
print("Twój dobytek zawiera", len(inventory), "elementy(-ów).")

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Ponieważ ta krotka zawiera cztery elementy (cztery łańcuchy: "miecz", "zbroja",


"tarcza" i "napój uzdrawiający"), wyświetlony zostaje komunikat Twój dobytek
zawiera 4 elementy(-ów).

Pułapka
Zwróć uwagę, że łańcuch "napój uzdrawiający" w krotce inventory jest liczony
jako jeden element, mimo że zawiera dwa słowa.

Stosowanie operatora in do krotek


Podobnie jak w przypadku łańcuchów, można używać operatora in w kontekście krotek
do sprawdzenia przynależności elementu. I dokładnie jak przedtem operator in jest
zwykle wykorzystywany do tworzenia warunku. Oto w jaki sposób go użyłem
w poniższym kodzie:
# sprawdź, czy element należy do krotki, za pomocą operatora in
if "napój uzdrawiający" in inventory:
print("Dożyjesz dnia, w którym stoczysz walkę.")
126 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Warunek "healing potion" in inventory służy do sprawdzenia, czy cały łańcuch


"healing potion" jest elementem krotki inventory. Ponieważ tak faktycznie jest,
wyświetlany jest komunikat Dożyjesz dnia, w którym stoczysz walkę.

Indeksowanie krotek
Indeksowanie krotek działa podobnie jak indeksowanie łańcuchów. Określasz numer
pozycji, umieszczając go w nawiasach kwadratowych, aby uzyskać dostęp do konkretnego
elementu. W poniższych wierszach kodu pozwalam użytkownikowi wybrać wartość
indeksu, a potem komputer wyświetla odpowiadający jej element:
# wyświetl jeden element wskazany przez indeks
index = int(input("\nWprowadź indeks elementu inwentarza: "))
print("Pod indeksem", index, "znajduje się", inventory[index])

Ta krotka została pokazana na rysunku 4.13 wraz z wartościami indeksu.

Rysunek 4.13. Każdy łańcuch to pojedynczy element krotki

Wycinanie krotek
Wycinanie działa dokładnie tak, jak to widziałeś w przypadku łańcuchów. Podajesz
pozycję początkową i końcową. Wynikiem operacji jest krotka zawierająca wszystkie
elementy znajdujące się między tymi dwoma pozycjami.
Podobnie jak w przypadku programu Krajacz pizzy z wcześniejszej części tego
rozdziału, pozwalam użytkownikowi wybrać numery pozycji początkowej i końcowej.
Następnie, tak jak poprzednio, program wyświetla wycinek:
# wyświetl wycinek
start = int(input("\nWprowadź indeks wyznaczający początek wycinka: "))
finish = int(input("\nWprowadź indeks wyznaczający koniec wycinka: "))
print("inventory[", start, ":", finish, "] to", end=" ")
print(inventory[start:finish])

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Wykorzystując tę krotkę jako przykład, na rysunku 4.14 przedstawiono w wizualny


sposób, jak należy rozumieć tworzenie wycinków krotek.
Wycinanie łańcuchów 127

Rysunek 4.14. W przypadku krotek punkty graniczne wycinków są umiejscowione


między elementami, tak jak to było w przypadku łańcuchów

Niemutowalność krotek
Podobnie jak łańcuchy, krotki są niemutowalne. To oznacza, że krotki nie można
zmienić. Oto sesja interaktywna, która ma dowieść słuszności mojej uwagi:
>>> inventory = ("miecz", "zbroja", "tarcza", "napój uzdrawiający")
>>> print(inventory)
('miecz', 'zbroja', 'tarcza', 'napój uzdrawiający')
>>> inventory[0] = "topór"
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
inventory[0] = "topór"
TypeError: 'tuple' object does not support item assignment

Chociaż nie możesz zmieniać krotek, podobnie jak łańcuchów, możesz tworzyć nowe
krotki z już istniejących.

Konkatenacja krotek
Krotki można konkatenować w ten sam sposób, jak konkatenuje się łańcuchy. Łączysz je
po prostu razem za pomocą operatora konkatenacji:
# dokonaj konkatenacji dwóch krotek
chest = ("złoto", "klejnoty")
print("Znajdujesz skrzynię, która zawiera:")
print(chest)
print("Dodajesz zawartość skrzyni do swojego inwentarza.")
inventory += chest
print("Twój aktualny inwentarz to:")
print(inventory)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Pierwszą rzeczą, którą zrobiłem, było utworzenie nowej krotki, chest, zawierającej
dwa elementy w postaci łańcuchów "złoto" i "klejnoty". Następnie wyświetliłem
krotkę chest, aby pokazać jej elementy. Potem użyłem operatora rozszerzonego
przypisania, dokonując konkatenacji krotek inventory i chest i przypisując wynik
z powrotem do zmiennej inventory. Nie zmodyfikowałem oryginalnej krotki
128 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

przypisanej do zmiennej inventory (ponieważ jest to niemożliwe, bo krotki są


niemutowalne). Zamiast tego operator rozszerzonego przypisania utworzył całkowicie
nową krotkę, zawierającą elementy krotek inventory i chest, która została przypisana
do zmiennej inventory.

Powrót do gry Wymieszane litery


Gra Wymieszane litery łączy kilka nowych koncepcji, które poznałeś w tym rozdziale.
Możesz bez trudu zmodyfikować ten program tak, aby zawierał Twoją własną listę słów
do odgadnięcia. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 4.; nazwa pliku
to wymieszane_litery.py.

Początek programu
Po początkowych komentarzach importuję moduł random:
# Wymieszane litery
# Komputer wybiera losowo słowo, a potem miesza w nim litery
# Gracz powinien odgadnąć pierwotne słowo

import random

Następnie wykorzystałem krotkę do utworzenia sekwencji słów. Zwróć uwagę, że nazwa


zmiennej WORDS zawiera same duże litery, z czego wynika, że będę ją traktował jako stałą.
# utwórz sekwencję słów do wyboru
WORDS = ("python", "anagram", "łatwy", "skomplikowany", "odpowiedź", "ksylofon")

W kolejnym kroku używam nowej funkcji, random.choise(), do wybrania w sposób


losowy słowa z krotki WORDS:
# wybierz losowo jedno słowo z sekwencji
word = random.choice(WORDS)

Ta funkcja jest dla Ciebie nowa, ale jest dość prosta. Komputer wybiera losowo jeden
element z dowolnej sekwencji, która zostanie mu wskazana.
Kiedy komputer już dokona losowego wyboru słowa, przypisuje je do zmiennej word.
Jest to słowo, które gracz będzie miał do odgadnięcia. Na koniec przypisuje wartość
zmiennej word do zmiennej correct, której później użyję do sprawdzenia, czy gracz
odgadł prawidłowo.
# utwórz zmienną, by później użyć jej do sprawdzenia, czy odpowiedź jest poprawna
correct = word
Powrót do gry Wymieszane litery 129

Projektowanie części programu,


w której jest tworzony anagram
Kolejny fragment kodu wykorzystuje nowe koncepcje wprowadzone w tym rozdziale
i stanowi najbardziej interesującą część programu. Jest ten fragment, który faktycznie
tworzy anagram na podstawie oryginalnego, wybranego losowo słowa.
Lecz zanim zacząłem pisać kod, utworzyłem projekt tej części programu w pseudokodzie
(tak, tak, naprawdę używamy tych wszystkich rzeczy, o których piszę). Oto moja
pierwsza wersja algorytmu tworzenia anagramu wybranego słowa:
utwórz pusty anagram
dopóki wybrane słowo zawiera jakieś litery
usuń wylosowaną literę z wybranego słowa
dodaj wylosowaną literę do anagramu

Koncepcyjnie wygląda to dość dobrze, ale muszę przyjrzeć się swojej semantyce.
Ponieważ łańcuchy znaków są niemutowalne, właściwie nie mogę „usunąć wylosowanej
litery” z łańcucha wprowadzonego przez użytkownika. Lecz mogę utworzyć nowy
łańcuch, który nie będzie zawierał wybranej losowo litery. A ponieważ nie mogę także
„dodać wylosowanej litery” do łańcucha z anagramem, muszę utworzyć nowy łańcuch
poprzez konkatenację cząstkowego anagramu w aktualnej postaci z „usuniętą” literą.

Tworzenie pustego łańcucha anagramu


Sam początek algorytmu jest prosty:
# utwórz 'pomieszaną' wersję słowa
jumble =""

Program tworzy pusty łańcuch i przypisuje go do zmiennej jumble, która będzie


stanowić odwołanie do ostatecznej wersji anagramu.

Skonfigurowanie pętli
Procesem tworzenia anagramu steruje pętla while. Warunek pętli jest, jak widzisz, dosyć
prosty:
while word:c

Konfiguruję pętlę w ten sposób, aby jej wykonywanie było kontynuowane, dopóki
wartość zmiennej word nie będzie równa pustemu łańcuchowi. Jest to doskonała metoda,
ponieważ w trakcie każdego wykonania pętli komputer tworzy nową wersję łańcucha
word z „usuniętą” jedną literą i przypisuje ją ponownie do zmiennej word. W końcu word
stanie się pustym łańcuchem i tworzenie anagramu zostanie zakończone.
130 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Generowanie losowej pozycji w łańcuchu word


Pierwszy wiersz ciała pętli generuje losową pozycję w łańcuchu word na podstawie jego
długości:
position = random.randrange(len(word))

Więc litera word[position] jest tą literą, która ma być „usunięta” z łańcucha word
i dodana do łańcucha jumble.

Tworzenie nowej wersji łańcucha jumble


Następny wiersz pętli tworzy nową wersję łańcucha jumble. Nowy łańcuch jest równy
swej poprzedniej wersji z dodaną na końcu literą word[position].
jumble += word[position]

Tworzenie nowej wersji łańcucha word


Kolejny wiersz pętli to:
word = word[:position] + word[(position + 1):]

Tworzy on nową wersję łańcucha word pozbawioną jednej litery, która w oryginalnym
łańcuchu zajmowała pozycję position. Stosując wycinanie, komputer tworzy z
zawartości łańcucha word dwa nowe łańcuchy. Pierwszy wycinek, word[:position],
obejmuje wszystkie litery poprzedzające literę word[position] bez niej samej. Drugi
wycinek, word[(position + 1):], zawiera wszystkie litery występujące w łańcuchu word
po literze word[position]. Te dwa łańcuchy są łączone w jedną całość, która zostaje
przypisana do zmiennej word, która teraz reprezentuje łańcuch równy swej poprzedniej
wartości minus litera word[position].

Przywitanie gracza
Po utworzeniu anagramu kolejny fragment programu wita gracza w grze i wyświetla
słowo z wymieszanymi literami, które powinny zostać uporządkowane:
# rozpocznij grę
print(
"""
Witaj w grze 'Wymieszane litery'!

Uporządkuj litery, aby odtworzyć prawidłowe słowo.


(Aby zakończyć zgadywanie, naciśnij klawisz Enter bez podawania odpowiedzi.)
"""
)
print("Zgadnij, jakie to słowo:", jumble)
Podsumowanie 131

Uzyskanie odpowiedzi gracza


Następnie komputer pobiera odpowiedź gracza. Komputer nie przestaje prosić gracza
o podanie odpowiedzi, dopóki gracz nie wprowadzi poprawnego słowa lub nie naciśnie
od razu klawisza Enter:
guess = input("\nTwoja odpowiedź: ")
while guess != correct and guess != "":
print("Niestety, to nie to słowo.")
guess = input("Twoja odpowiedź: ")

Pogratulowanie graczowi
W tym miejscu programu gracz albo odgadł prawidłowo wybrane przez komputer słowo,
albo zakończył grę. Jeśli gracz odgadł to słowo, komputer składa mu szczere gratulacje:
if guess == correct:
print("Zgadza się! Zgadłeś!\n")

Zakończenie gry
Na koniec program dziękuje graczowi za uczestnictwo w grze i kończy pracę:
print("Dziękuję za udział w grze.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale poznałeś koncepcję sekwencji. Zobaczyłeś, jak tworzyć sekwencję
liczb za pomocą funkcji range(). Przekonałeś się, że łańcuchy są w rzeczywistości tylko
sekwencjami znaków. Poznałeś krotki, które umożliwiają konfigurowanie sekwencji
dowolnego typu. Zobaczyłeś, jak przechodzić przez elementy sekwencji za pomocą pętli
for. Dowiedziałeś się, jak uzyskać informację o długości sekwencji i jak sprawdzić,
czy element jest składnikiem sekwencji. Zobaczyłeś, jak kopiować fragmenty sekwencji
poprzez indeksowanie i wycinanie. Dowiedziałeś się o niemutowalności i pewnych
ograniczeniach, jakie na Ciebie nakłada. Lecz zobaczyłeś także, jak tworzyć nowe
sekwencje z już istniejących poprzez konkatenację, pomimo tej niemutowalności.
W końcu połączyłeś to wszystko razem, by utworzyć ambitną grę, polegającą na
rozwiązywaniu anagramów.
132 Rozdział 4. Pętle for, łańcuchy znaków i krotki. Gra Wymieszane litery

Sprawdź swoje umiejętności


1. Napisz program, który liczy za użytkownika. Umożliw użytkownikowi
wprowadzenie liczby początkowej, liczby końcowej i wielkości odstępu między
kolejnymi liczbami.
2. Utwórz program, który wczytuje komunikat użytkownika, a następnie wypisuje
go w odwrotnej kolejności.
3. Popraw program Wymieszane litery tak, żeby każdemu słowu towarzyszyła
podpowiedź. Gracz powinien mieć możliwość zobaczenia podpowiedzi, jeśli
utknie w martwym punkcie. Dodaj system punktacji, który nagradza graczy
rozwiązujących anagram bez uciekania się do podpowiedzi.
4. Utwórz grę, w której komputer wybiera losowo słowo, które gracz musi
odgadnąć. Komputer informuje gracza, ile liter znajduje się w wybranym
słowie. Następnie gracz otrzymuje pięć szans na zadanie pytania, czy jakaś litera
jest zawarta w tym słowie. Komputer może odpowiedzieć tylko „tak” lub „nie”.
Potem gracz musi odgadnąć słowo.
5
Listy i słowniki.
Gra Szubienica

K rotki stanowią wspaniały sposób pracy z sekwencjami dowolnego typu, ale ich
niemutowalność może być ograniczająca. Na szczęście inny rodzaj sekwencji, lista,
reprezentuje wszystkie możliwości krotki plus coś więcej — to dlatego, że listy są
mutowalne. Elementy mogą być dodawane do listy bądź z niej usuwane. Można nawet
posortować listę. Zostaniesz także zapoznany z innym typem — słownikami. Podczas
gdy listy są związane z obsługą sekwencji informacji, słowniki działają na parach danych.
Słowniki, podobnie jak ich odpowiedniki w realnym życiu, umożliwiają odszukiwanie
jednej wartości za pomocą drugiej. W szczególności w tym rozdziale nauczysz się:
 tworzyć, indeksować i wycinać listy,
 dodawać i usuwać elementy listy,
 używać metod listy do dodawania elementów na końcu listy i do sortowania listy,
 wykorzystywać zagnieżdżone sekwencje do reprezentowania jeszcze bardziej
złożonych informacji,
 używać słowników do obsługi par danych,
 dodawać i usuwać pozycje słownika.

Wprowadzenie do gry Szubienica


Projektem tego rozdziału jest gra w szubienicę. Komputer wybiera ukryte słowo, a zadaniem
gracza jest próba jego stopniowego odgadnięcia, litera po literze. Za każdym razem, gdy
litera podana przez gracza jest niepoprawna, komputer pokazuje nowy rysunek wieszanej
postaci. Jeśli gracz nie odgadnie słowa w porę, ludzik z rysunku traci życie. Rysunki
od 5.1 do 5.3 pokazują grę w pełnej krasie.
134 Rozdział 5. Listy i słowniki. Gra Szubienica

Rysunek 5.1. Gra Szubienica w toku. Hm… zastanawiam się, jakie to może być słowo

Rysunek 5.2. Zwyciężyłem w tej grze

Nie tylko będziesz mógł się cieszyć tą grą, ale zanim skończysz czytanie tego
rozdziału, będziesz wiedział, jak stworzyć swoją własną wersję. Możesz zdefiniować
spersonalizowaną grupę ukrytych słów, a nawet zmienić moją mało wyszukaną grafikę.
Korzystanie z list 135

Rysunek 5.3. Ta gra źle się skończyła, szczególnie dla małego ludzika utworzonego z tekstu

Korzystanie z list
Listy są sekwencjami, dokładnie jak krotki — ale listy są mutowalne. Mogą być
modyfikowane. Tak więc listy mają wszystkie możliwości krotek plus coś więcej.
Listy funkcjonują tak jak krotki, więc wszystko, czego nauczyłeś się o krotkach,
ma zastosowanie do list, co sprawia, że nauczenie się ich używania staje się pestką.

Prezentacja programu Inwentarz bohatera 3.0


Ten program został oparty na programie Inwentarz bohatera 2.0, przedstawionym
w rozdziale 4., w punkcie „Tworzenie krotek”. Lecz zamiast stosować krotki do
przechowywania inwentarza bohatera, ten program używa list. Pierwsza część programu
Inwentarz bohatera 3.0 tworzy takie same wyniki jak wersja 2.0. W istocie kod jest prawie
identyczny. Jedyna różnica polega na użyciu list zamiast krotek. Na rysunku 5.4 zostały
ukazane wyniki pierwszej części programu. Jego druga część wykorzystuje mutowalność
list i robi z sekwencjami pewne całkiem nowe rzeczy. Rysunek 5.5 pokazuje tę część
programu w akcji.

Tworzenie listy
W pierwszych wierszach tego programu tworzę nową listę, przypisuję ją do zmiennej
inventory i wypisuję jej wszystkie elementy. Wszystko działa prawie tak samo jak
w przypadku programu Inwentarz bohatera 2.0. Jedyna różnica polega na tym, że elementy
otoczyłem nawiasami kwadratowymi, a nie zwykłymi, tworząc w ten sposób listę zamiast
krotki. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 5.; nazwa pliku
to inwentarz_bohatera3.py.
136 Rozdział 5. Listy i słowniki. Gra Szubienica

Rysunek 5.4. Inwentarz bohatera jest teraz reprezentowany przez listę. Wyniki wyglądają
prawie tak samo jak w programie Inwentarz bohatera 2.0, w którym inwentarz był
reprezentowany przez krotkę

Rysunek 5.5. Ponieważ inwentarz bohatera jest reprezentowany przez listę,


elementy mogą być dodawane, modyfikowane i usuwane

# Inwentarz bohatera 3.0


# Demonstruje listy

# utwórz listę zawierającą pewne elementy i wyświetl jej zawartość


# za pomocą pętli for
inventory = ["miecz", "zbroja", "tarcza", "napój uzdrawiający"]
print("Elementy Twojego inwentarza:")
for item in inventory:
print(item)
Korzystanie z list 137

Stosowanie funkcji len() do list


Poniższy kod jest dokładnie taki sam, jak odpowiadający mu kod w programie Inwentarz
bohatera 2.0. Funkcja len() zastosowana do list działa tak samo jak w przypadku krotek.
# wyświetl długość listy
print("Twój dobytek zawiera", len(inventory), "elementy(-ów).")

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Używanie operatora in z listami


Ponownie kod tego fragmentu jest dokładnie taki sam jak w poprzedniej wersji
programu. Operator in zastosowany do list działa tak samo jak w przypadku krotek.
# sprawdź, czy element należy do listy, za pomocą operatora in
if "napój uzdrawiający" in inventory:
print("Dożyjesz dnia, w którym stoczysz walkę.")

Indeksowanie list
Znowu kod jest taki sam jak w przypadku krotek. Indeksowanie list wygląda tak samo jak
indeksowanie krotek — wystarczy, że podasz numer pozycji elementu, o który Ci chodzi,
umieszczając go w nawiasach kwadratowych.
# wyświetl jeden element wskazany przez indeks
index = int(input("\nWprowadź indeks elementu inwentarza: "))
print("Pod indeksem", index, "znajduje się", inventory[index])

Tworzenie wycinków list


Czy uwierzysz, że tworzenie wycinka listy jest dokładnie taką samą czynnością jak
wycinanie fragmentu krotki? I tym razem wystarczy, że podasz w nawiasach
kwadratowych dwa punkty graniczne oddzielone dwukropkiem:
# wyświetl wycinek
start = int(input("\nWprowadź indeks wyznaczający początek wycinka: "))
finish = int(input("Wprowadź indeks wyznaczający koniec wycinka: "))
print("inventory[", start, ":", finish, "] to", end=" ")
print(inventory[start:finish])

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Konkatenacja list
Konkatenacja list funkcjonuje w taki sam sposób jak konkatenacja krotek. Jedyna faktyczna
różnica polega na tym, że utworzyłem listę (zamiast krotki) i przypisałem ją do zmiennej
chest. Jest to mała, ale istotna różnica, ponieważ można tylko dokonywać konkatenacji
sekwencji tego samego typu.
138 Rozdział 5. Listy i słowniki. Gra Szubienica

# dokonaj konkatenacji dwóch list


chest = ["złoto", "klejnoty"]
print("Znajdujesz skrzynię, która zawiera:")
print(chest)
print("Dodajesz zawartość skrzyni do swojego inwentarza.")
inventory += chest
print("Twój aktualny inwentarz to:")
print(inventory)

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Mutowalność list
W tym momencie możesz już czuć się nieco zmęczony formułą „działa dokładnie tak
samo jak w przypadku krotek”. Jak dotąd, z wyjątkiem używania nawiasów kwadratowych
zamiast zwykłych, listy nie wydają się niczym różnić od krotek. Ale istnieje między tymi
dwoma strukturami jedna ogromna różnica. Listy są mutowalne. Mogą się zmieniać.
W rezultacie istnieje wiele rzeczy, które można robić z listami, a których nie można robić
z krotkami.

Przypisanie elementowi listy nowej wartości


poprzez indeks
Ponieważ listy są mutowalne, możesz przypisać istniejącemu elementowi nową wartość:
# przypisz poprzez indeks
print("Wymieniasz swój miecz na kuszę.")
inventory[0] = "kusza"
print("Twój aktualny inwentarz to:")
print(inventory)

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

W następującym wierszu wziętym z powyższego kodu łańcuch "kusza" zostaje


przypisany elementowi listy inventory o pozycji 0:
inventory[0] = "kusza"

Nowy łańcuch zajmuje miejsce poprzedniej wartości (którą był łańcuch "miecz").
Wynik będziesz mógł zobaczyć, kiedy funkcja print wyświetli nową wersję listy inventory.

Pułapka
Za pomocą indeksowania możesz przypisać istniejącemu elementowi listy nową
wartość, ale nie możesz w ten sposób utworzyć nowego elementu. Próba
przypisania wartości do nieistniejącego elementu spowoduje wystąpienie błędu.
Korzystanie z list 139

Przypisanie nowej wartości wycinkowi listy


Oprócz przypisania nowej wartości pojedynczemu elementowi możesz także przypisać nową
wartość wycinkowi. Ja przypisałem listę ["kula do wróżenia"] do wycinka inventory[4:6]:
# przypisz poprzez wycinek
print("Zużywasz swoje złoto i klejnoty na zakup kuli do wróżenia.")
inventory[4:6] = ["kula do wróżenia"]
print("Twój aktualny inwentarz to:")
print(inventory)

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Ta instrukcja przypisania zastępuje dwa elementy inventory[4] i inventory[5]


łańcuchem "kula do wróżenia". Ponieważ przypisałem listę z jednym elementem
do wycinka z dwoma elementami, długość listy zmniejszyła się o jeden.

Usunięcie elementu listy


Możesz usunąć element z listy za pomocą del — po prostu wyznacz ten element,
umieszczając po del jego określenie:
# usuń element
print("W wielkiej bitwie Twoja tarcza zostaje zniszczona.")
del inventory[2]
print("Twój aktualny inwentarz to:")
print(inventory)

input("\nAby kontynuować grę, naciśnij klawisz Enter.")

Kiedy ten kod zostanie wykonany, element, który zajmował pozycję numer 2, łańcuch
"tarcza" zostanie usunięty z listy inventory. Usunięcie elementu nie tworzy w sekwencji
luki. Długość listy zmniejsza się o jeden, a wszystkie elementy, które usunięty element
poprzedzał, zostają „przesunięte w lewo” o jedną pozycję. Więc w tym przypadku nadal
istnieje element o pozycji 2; jest to ten sam element, który przedtem zajmował pozycję 3.

Usunięcie wycinka listy


Możesz również usunąć z listy wycinek:
# usuń wycinek
print("Twoja kusza i zbroja zostały skradzione przez złodziei.")
del inventory[:2]
print("Twój aktualny inwentarz to:")
print(inventory)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Poniższy wiersz kodu usuwa wycinek inventory[:2], którym jest ["kusza", "zbroja"]
z listy inventory:
del inventory[:2]
140 Rozdział 5. Listy i słowniki. Gra Szubienica

Tak jak przy usuwaniu elementu, długość listy zostaje zmniejszona, a pozostałe w niej
elementy tworzą nową ciągłą listę rozpoczynającą się od pozycji 0.

Korzystanie z metod listy


Listy udostępniają metody, dzięki którym można nimi manipulować. Używając metod
listy, możesz dodać element, usunąć element na podstawie jego wartości, posortować
listę, a nawet odwrócić kolejność jej elementów.

Prezentacja programu Najlepsze wyniki


Program Najlepsze wyniki wykorzystuje metody listy do tworzenia i utrzymywania listy
najlepszych wyników użytkownika uzyskanych w grze komputerowej. Program używa
prostego interfejsu opartego na menu. Użytkownik ma kilka możliwości wyboru. Może
dodać nowy wynik, usunąć wynik, posortować wyniki lub zakończyć program. Rysunek 5.6
pokazuje ten program w działaniu.

Rysunek 5.6. Użytkownik obsługuje listę najlepszych wyników, wybierając pozycje z menu.
Gros pracy wykonują jednak działające na zapleczu metody listy
Korzystanie z metod listy 141

Skonfigurowanie programu
Kod odpowiedzialny za początkowe ustawienia jest dość prosty. Po początkowych
komentarzach tworzę dwie zmienne. Zmienna scores reprezentuje listę, która ma
zawierać wyniki. Na początek ustawiłem jej wartość jako pustą listę. Zmienna choice
reprezentuje pozycję menu wybraną przez użytkownika. Zainicjalizowałem ją
na None. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 5.; nazwa pliku
to najlepsze_wyniki.py.
# Najlepsze wyniki
# Demonstruje metody listy

scores = []

choice = None

Wyświetlenie menu
Pętla while stanowi rdzeń programu. Jej wykonywanie jest kontynuowane, dopóki
użytkownik nie wprowadzi wartości 0. Pozostała część kodu pętli wyświetla menu
i pobiera wybrany przez użytkownika numer pozycji:
while choice != "0":

print(
"""
Najlepsze wyniki

0 - zakończ
1 - pokaż wyniki
2 - dodaj wynik
3 - usuń wynik
4 - posortuj wyniki
"""
)

choice = input("Wybierasz: ")


print()

Zakończenie programu
Najpierw sprawdzam, czy użytkownik chce zakończyć program. Jeśli użytkownik
wprowadzi 0, komputer mówi „Do widzenia.”:
# zakończ program
if choice == "0":
print("Do widzenia.")

Jeśli użytkownik wprowadzi 0, warunek pętli while będzie fałszywy przy kolejnym
sprawdzeniu. Pętla się zakończy, jak i cały program.
142 Rozdział 5. Listy i słowniki. Gra Szubienica

Wyświetlenie wyników
Jeśli użytkownik wprowadzi 1, wykona się poniższy blok elif i komputer wyświetli wyniki:
# wypisz tabelę najlepszych wyników
elif choice == "1":
print("Najlepsze wyniki")
for score in scores:
print(score)

Dodanie wyniku
Jeśli użytkownik wprowadzi 2, komputer poprosi go o podanie nowego wyniku, który
zostanie przypisany do zmiennej score. Ostatni wiersz kodu wykorzystuje metodę listy
append(), aby umieścić wartość score na końcu listy scores. Długość listy wzrasta o jeden
element.
# dodaj wynik
elif choice == "2":
score = int(input("Jaki wynik uzyskałeś?: "))
scores.append(score)

Usunięcie wyniku
Kiedy użytkownik wprowadzi 3, komputer pobiera od użytkownika wartość wyniku,
który ma zostać usunięty. Jeśli wynik zawiera się na liście, komputer usuwa jego pierwsze
wystąpienie. Jeśli wynik nie występuje na liście, użytkownik jest o tym informowany.
# usuń wynik
elif choice == "3":
score = int(input("Który wynik usunąć?: "))
if score in scores:
scores.remove(score)
else:
print(score, "nie ma na liście wyników.")

Kod najpierw sprawdza, czy wynik zawiera się na liście. Jeśli się zawiera, wywoływana
jest metoda listy remove(). Metoda przegląda listę, zaczynając od pozycji 0, i szuka wartości
przekazanej jej jako argument wywołania — w tym wypadku score. Kiedy metoda odnajduje
pierwsze wystąpienie tej wartości, element jest usuwany z listy. Jeśli wartość w występuje
na liście więcej niż raz, usuwane jest tylko pierwsze wystąpienie. Więc tylko pierwsze
wystąpienie wyniku score zostanie usunięte. Jeśli metoda skutecznie usunie element
z listy, staje się ona o jeden element krótsza.
Zwróć też uwagę, w jaki sposób remove() różni się od del. Metoda remove() usuwa
element nie na podstawie jego pozycji, lecz na podstawie jego wartości.
Korzystanie z metod listy 143

Pułapka
Zachowaj ostrożność, kiedy będziesz korzystać z metody remove(). Jeśli spróbujesz
usunąć wartość, która nie zawiera się na liście, wygenerujesz błąd. Oto bezpieczny
sposób używania tej metody:
if score in scores:
scores.remove(score)

Sortowanie wyników
Wyniki zawarte na liście występują w takiej samej kolejności, w jakiej zostały wprowadzone
przez użytkownika. Aby posortować te wyniki, użytkownik musi jedynie wprowadzić
liczbę 4:
# posortuj wyniki
elif choice == "4":
scores.sort(reverse=True)

Metoda sort() porządkuje elementy listy. To doskonale, z wyjątkiem tego, że domyślnie


sort() ustawia elementy w porządku rosnącym — zaczynając od wartości najmniejszych.
Ale ja chciałbym, aby na początku były najlepsze wyniki. Na szczęście można nakazać
metodzie sort() ustawienie elementów w porządku malejącym — z największymi
wartościami na początku. Możesz to osiągnąć, nadając wartość True parametrowi metody
o nazwie reverse. Właśnie to zrobiłem w swoim programie i w rezultacie wyniki są
sortowane, począwszy od najwyższych wartości.

Wskazówka
Jeśli chcesz posortować listę w porządku rosnącym (najpierw najmniejsze
wartości), możesz po prostu wywołać metodę, nie nadając wartości żadnym
parametrom. Więc jeśli chciałbym posortować listę o nazwie numbers w porządku
rosnącym, mógłbym użyć następującego wiersza:
numbers.sort()

Postępowanie po nieprawidłowym wyborze


Jeśli użytkownik wprowadzi liczbę, która nie stanowi prawidłowego wyboru,
przechwytuje ją klauzula else. Program powiadamia użytkownika, że jego wybór nie
został zrozumiany.
# nieznana opcja
else:
print("Niestety,", choice, "nie jest prawidłowym wyborem.")

Czekanie na decyzję użytkownika


Po wprowadzeniu przez użytkownika wartości 0 następuje wyjście z pętli. Jak zawsze
przed zakończeniem pracy, program czeka na decyzję użytkownika:
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")
144 Rozdział 5. Listy i słowniki. Gra Szubienica

Zobaczyłeś w działaniu kilka użytecznych metod listy. Podsumowanie tych metod


(oraz paru innych) znajdziesz w tabeli 5.1:

Tabela 5.1. Wybrane metody listy


Metoda Opis
append(wartość) Dodaje wartość na końcu listy.
sort() Sortuje elementy, od najmniejszego do największego. Opcjonalnie
można nadać wartość typu boolean parametrowi reverse. Jeśli
będzie to wartość True, lista zostanie posortowana od największej
do najmniejszej wartości.
reverse() Odwraca porządek elementów listy.
count(wartość) Zwraca liczbę wystąpień argumentu wartość.
index(wartość) Zwraca numer pozycji pierwszego wystąpienia argumentu wartość.
insert(i, wartość) Wstawia wartość na pozycji i.
pop(i) Zwraca wartość zajmującą pozycję i oraz usuwa ją z listy. Przekazanie
numeru pozycji i jest opcjonalne. Jeśli argument i nie zostanie
podany, usuwany i zwracany jest ostatni element listy.
remove(wartość) Usuwa z listy pierwsze wystąpienie argumentu wartość.

Kiedy należy używać krotek zamiast list?


W tym momencie być może myślisz: „Po co w ogóle używać krotek?”. To prawda, że listy
zawierają wszystkie możliwości krotek plus coś więcej. Ale nie śpiesz się tak z rezygnacją
z krotek. W Twoim świecie programowania w języku Python jest dla nich miejsce.
Istnieje kilka sytuacji, w których używanie krotek ma więcej sensu niż korzystanie z list.
 Krotki są szybsze niż listy. Ponieważ komputer wie o krotkach, że nie mogą się
zmienić, może je przechowywać w taki sposób, aby ich używanie było szybsze
niż korzystanie z list. W przypadku prostych programów ta różnica szybkości
nie ma znaczenia, ale w bardziej złożonych aplikacjach operujących bardzo
dużymi sekwencjami informacji mogłaby być istotna.
 Niemutowalność krotek sprawia, że doskonale się nadają do tworzenia stałych,
ponieważ nie mogą się zmieniać. Użycie krotek może uczynić Twój kod
bezpieczniejszym i bardziej przejrzystym.
 Czasem użycie krotek jest wymagane. W pewnych przypadkach Python żąda
wartości niemutowalnych. W porządku, właściwie nie zetknąłeś się jeszcze z żadnym
z tych przypadków, ale istnieje często występująca sytuacja, z którą się spotkasz, gdy
będziesz w dalszej części tego rozdziału, w podrozdziale „Używanie słowników”,
poznawał słowniki. Ponieważ słowniki wymagają stosowania typów niemutowalnych,
przy tworzeniu niektórych ich rodzajów krotki będą miały zasadnicze znaczenie.
Lecz ponieważ listy są tak elastyczne, prawdopodobnie ich użycie będzie dla Ciebie
w większości przypadków korzystniejsze niż zastosowanie krotek.
Używanie sekwencji zagnieżdżonych 145

Używanie sekwencji zagnieżdżonych


Przedtem powiedziałem, że listy i krotki mogą być sekwencjami dowolnych elementów.
Jeśli to prawda, listy mogą zawierać inne listy lub też krotki, a krotki mogą zawierać inne
krotki lub listy. Faktycznie mogą, a kiedy tak się dzieje, mamy do czynienia z sekwencjami
zagnieżdżonymi. Sekwencje zagnieżdżone to sekwencje wewnątrz innych sekwencji.
Są one świetnym sposobem organizowania bardziej złożonych kolekcji informacji.
Chociaż ten termin wygląda na jeszcze jeden tajemniczy element komputerowego
żargonu, założę się, że będziesz tworzył sekwencje zagnieżdżone i używał ich nieustannie.
Pozwól, że posłużę się przykładem. Powiedzmy, że układasz listę świątecznych zakupów.
Rozpoczynasz od sporządzenia listy imion i nazwisk. Pod każdym imieniem lub nazwiskiem
umieszczasz listę kilku możliwych podarunków. Właśnie utworzyłeś sekwencję zagnieżdżoną
— masz listę imion, a każde imię reprezentuje listę prezentów. I to wszystko, co na ten
temat można powiedzieć.

Prezentacja programu Najlepsze wyniki 2.0


Poprzedni program, Najlepsze wyniki, operuje tylko wynikami. Ale większość list
z najlepszymi wynikami przechowuje oprócz wyników nazwę gracza. Tak też jest
w przypadku tej nowej wersji programu. Zawiera ona również kilka innych ulepszeń.
Automatycznie sortuje wyniki i nawet ogranicza wielkość tej listy do czołowej piątki.
Na rysunku 5.7 zostało pokazane jego przykładowe uruchomienie.

Rysunek 5.7. Nowa, poprawiona wersja programu Najlepsze wyniki przechowuje


wraz z wynikiem nazwę gracza, wykorzystując sekwencje zagnieżdżone
146 Rozdział 5. Listy i słowniki. Gra Szubienica

Tworzenie sekwencji zagnieżdżonych


Możesz utworzyć zagnieżdżoną listę lub krotkę w zwykły sposób — wypisz poszczególne
elementy, oddzielając je przecinkami. Różnica związana z sekwencjami zagnieżdżonymi
polega na tym, że jako elementy dołączasz całe listy lub krotki. Oto przykład:
>>> nested = ["pierwszy", ("drugi", "trzeci"), ["czwarty", "piąty", "szósty"]]
>>> print(nested)
['pierwszy', ('drugi', 'trzeci'), ['czwarty', 'piąty', 'szósty']]

Więc chociaż widzisz sześć łańcuchów, lista nested ma tylko trzy elementy.
Pierwszym elementem jest łańcuch "pierwszy", drugim — krotka ("drugi", "trzeci"),
a trzecim elementem jest lista ["czwarty", "piąty", "szósty"]. Mimo że możesz
utworzyć listę lub krotkę zawierającą dowolną liczby list i krotek, użyteczne sekwencje
zagnieżdżone mają często spójny schemat. Popatrz na kolejny przykład:
>>> scores = [("Muniek", 1000), ("Lilka", 1500), ("Kajtek", 3000)]
>>> print(scores)
[('Muniek', 1000), ('Lilka', 1500), ('Kajtek', 3000)]

Lista scores ma trzy elementy. Każdy z jej elementów to krotka. Każda krotka
ma dokładnie dwa elementy, łańcuch znaków i liczbę.
Ta sekwencja, nawiasem mówiąc, reprezentuje tabelę najlepszych wyników z nazwami
graczy i wynikami (tak jak powinna wyglądać rzeczywista tabela najlepszych wyników!).
W tym szczególnym przypadku Muniek uzyskał wynik 1000; Lilka — 1500, a najlepszy
wynik Kajtka to 3000.

Pułapka
Chociaż możesz tworzyć zagnieżdżone sekwencje wewnątrz zagnieżdżonych
sekwencji wielokrotnie, tak jak w poniższym przykładzie, nie jest to zwykle
dobry pomysł.
nested = ("głęboki", ("głębszy", ("najgłębszy", "wciąż najgłębszy")))
Sprawy mogą się szybko pogmatwać. Nawet doświadczeni programiści rzadko
używają sekwencji o więcej niż jednym czy dwóch poziomach zagłębienia.
W większości programów, które będziesz pisać, jeden poziom zagłębienia
(tak jak w przypadku listy scores, którą przed chwilą widziałeś) jest naprawdę
wszystkim, czego potrzebujesz.

Uzyskiwanie dostępu do elementów zagnieżdżonych


Dostęp do elementów zagnieżdżonej sekwencji, tak jak w przypadku dowolnej innej
sekwencji, uzyskujesz poprzez indeksowanie:
>>> scores = [("Muniek", 1000), ("Lilka", 1500), ("Kajtek", 3000)]
>>> print(scores[0])
('Muniek', 1000)
>>> print(scores[1])
('Lilka', 1500)
>>> print(scores[3])
('Kajtek', 3000)
Używanie sekwencji zagnieżdżonych 147

Każdy element to krotka, więc taki właśnie otrzymujesz wynik po uzyskaniu dostępu
do każdego z nich. Lecz co masz zrobić, jeśli chcesz uzyskać dostęp do jednego z elementów
jednej z krotek? Jeden sposób polega na przypisaniu krotki do zmiennej i zastosowaniu
indeksu, tak jak poniżej:
>>> a_score = score[2]
>>> print(a_score)
("Kajtek", 3000)
>>> print(a_score[0])
Kajtek

Lecz istnieje bezpośredni sposób dostępu do łańcucha "Kajtek" poprzez samą


zmienną scores:
>>> print(scores[2][0])
Kajtek

Przez podanie dwóch indeksów w wyrażeniu scores[2][0] informujesz komputer,


że ma pobrać z listy scores element zajmujący pozycję 2 (którym jest krotka ("Kajtek",
3000)), a następnie pobrać z niego element zajmujący pozycję 0 (którym jest łańcuch
"Kajtek"). Możesz używać tego rodzaju wielokrotnego indeksowania do zagnieżdżonych
sekwencji, aby się dostać bezpośrednio do zagnieżdżonego elementu.

Rozpakowanie sekwencji
Jeśli wiesz, ile elementów zawiera sekwencja, możesz przypisać każdy z nich do jego
własnej zmiennej w pojedynczym wierszu kodu:
>>> name, score = ("Szymek", 175)
>>> print(name)
Szymek
>>> print(score)
175

Nazywa się to rozpakowaniem i ma zastosowanie do dowolnego typu sekwencji.


Pamiętaj tylko, aby użyć tyle samo zmiennych, ile elementów zawiera sekwencja,
bo w przeciwnym wypadku wygenerujesz błąd.

Wstępne ustawienia programu


Tak jak w oryginalnym programie Najlepsze wyniki, ustawiam wartości zmiennych
i konfiguruję pętlę while. Tak jak przedtem, jeśli użytkownik wprowadzi 0, komputer
wypisze zawartość łańcucha "Do widzenia.". Kod tego programu możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 5.; nazwa pliku to najlepsze_wyniki2.py.
# Najlepsze wyniki 2.0
# Demonstruje sekwencje zagnieżdżone
148 Rozdział 5. Listy i słowniki. Gra Szubienica

scores = []

choice = None
while choice != "0":

print(
"""
Najlepsze wyniki 2.0

0 - zakończ
1 - wyświetl wyniki
2 - dodaj wynik
"""
)

choice = input("Wybierasz: ")


print()

# zakończ
if choice == "0":
print("Do widzenia.")

Wyświetlanie wyników
poprzez dostęp do zagnieżdżonych krotek
Jeśli użytkownik wprowadzi 1, komputer wybiera kolejno każdy element listy scores
i rozpakowuje wynik i nazwę gracza do zmiennych score i name. Następnie komputer
wyświetla ich wartości.
# wyświetl tabelę najlepszych wyników
elif choice == "1":
print("Najlepsze wyniki\n")
print("GRACZ\tWYNIK")
for entry in scores:
score, name = entry
print(name, "\t", score)

Dodanie wyniku
poprzez dołączenie do listy zagnieżdżonej krotki
Jeśli użytkownik wprowadzi 2, komputer umożliwi mu wprowadzenie nowego wyniku
i nazwy gracza. Z tych dwóch wartości komputer tworzy krotkę entry. Zdecydowałem się
na przechowanie wyniku najpierw w tej krotce, ponieważ chciałem, aby wprowadzone
dane zostały posortowane według wyniku, a potem nazwy. Następnie komputer dołącza
tę nową pozycje z wynikiem do listy. Komputer sortuje listę i odwraca jej porządek, tak że
najwyższe wyniki znajdują się na początku. Ostatnia instrukcja wycina i przypisuje listę,
tak aby zostało zachowanych tylko pięć najwyższych wyników.
# add a score
elif choice == "2":
Referencje współdzielone 149

name = input("Podaj nazwę gracza: ")


score = int(input("Jaki wynik gracz uzyskał?: "))
entry = (score, name)
scores.append(entry)
scores.sort(reverse=True)
scores = scores[:5] # zachowaj tylko 5 najlepszych wyników

Postępowanie po nieprawidłowym wyborze


Jeśli użytkownik wprowadzi inną wartość niż 0, 1 lub 2, przechwyci ją klauzula else.
Program informuje użytkownika, że jego wybór nie został zrozumiany.
# nieznana opcja
else:
print("Niestety,", choice, "nie jest prawidłowym wyborem.")

Czekanie na decyzję użytkownika


Po wprowadzeniu przez użytkownika wartości 0 następuje wyjście z pętli i program czeka
na decyzję użytkownika dotyczącą jego zakończenia:
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Referencje współdzielone
W rozdziale 2. dowiedziałeś się, że zmienna odwołuje się do wartości. Z technicznego
punktu widzenia to oznacza, że zmienna nie przechowuje kopii wartości, lecz odwołuje
się do miejsca w pamięci komputera, gdzie ta wartość jest przechowywana. Na przykład
instrukcja language = "Python" przechowuje łańcuch "Python" gdzieś w pamięci
Twojego komputera, a następnie tworzy zmienną language, która odwołuje się do tego
miejsca w pamięci. Wizualne przedstawienie efektu działania tej instrukcji znajdziesz na
rysunku 5.8.

Rysunek 5.8. Zmienna language odwołuje się do miejsca w pamięci,


gdzie przechowywana jest wartość łańcucha "Python"

Mówienie, że zmienna language przechowuje łańcuch "Python", tak jak pojemnik


firmy Tupperware przechowuje udko kurczaka, nie jest dokładne. W niektórych językach
programowania mogłaby to być dobra analogia, ale nie w Pythonie. Lepszym sposobem
wyobrażenia sobie tej kwestii byłoby coś takiego: zmienna odwołuje się do wartości w taki
sam sposób jak nazwisko odwołuje się do osoby. Powiedzieć, że nazwisko „przechowuje”
osobę byłoby czymś niewłaściwym (i głupim). Wykorzystując nazwisko osoby, możesz
dotrzeć do niej samej. Używając nazwy zmiennej, możesz sięgnąć do jej wartości.
150 Rozdział 5. Listy i słowniki. Gra Szubienica

Więc jakie to wszystko ma znaczenie? Cóż, w przypadku wartości niemutowalnych,


których używałeś, takich jak liczby, łańcuchy i krotki, nie znaczy to wiele. Ale ma swoje
znaczenie w kontekście wartości mutowalnych, takich jak listy. Kiedy kilka zmiennych
odwołuje się do tej samej wartości mutowalnej, współdzielą one tę samą referencję.
Wszystkie odwołują się do jednej i tej samej kopii tej wartości. A zmiana tej wartości
poprzez jedną z tych zmiennych skutkuje zmianą wartości ich wszystkich, ponieważ
istnieje tylko jedno wspólne źródło tej wartości.
Oto przykład pokazujący, jak to działa. Przypuśćmy, że urządzam szałowe party
z udziałem moich przyjaciół oraz dygnitarzy z całego świata. (Hej, to moja książka.
Mogę wymyślić taki przykład, jaki tylko chcę). Różni ludzie na przyjęciu nazywają mnie
w różny sposób, mimo że jestem tylko jedną osobą. Powiedzmy, że kolega nazywa mnie
„Mike”, dygnitarz — „Mr. Dawson”, a moja dziewczyna — supermodelka i zdobywczyni
nagrody Pulitzera, która właśnie wróciła ze swojej podróży po świecie, mającej na celu
zbieranie funduszy na walkę z analfabetyzmem (ponownie: moja książka — moja
fikcyjna dziewczyna) — zwraca się do mnie „honey” (kochanie). Więc każde z tych trojga
ludzi, odnosząc się do mnie, używa różnych nazw. Na tej samej zasadzie trzy zmienne
mogłyby się odwoływać do tej samej listy. Oto początek sesji interaktywnej, która
powinna Ci pokazać, co mam na myśli:
>>> mike = ["spodnie khaki", "koszula frakowa", "marynarka"]
>>> mr_dawson = mike
>>> honey = mike
>>> print(mike)
['spodnie khaki', 'koszula frakowa', 'marynarka']
>>> print(mr_dawson)
['spodnie khaki', 'koszula frakowa', 'marynarka']
>>> print(honey)
['spodnie khaki', 'koszula frakowa', 'marynarka']

Więc wszystkie trzy zmienne, mike, mr_dawson i honey, odwołują się do jednej i tej
samej listy reprezentującej moją osobę (lub przynajmniej to, w co byłem ubrany na tym
przyjęciu). Rysunek 5.9 pomaga w wyjaśnieniu tej koncepcji:

Rysunek 5.9. Wszystkie zmienne, mike, mr_dawson i honey, odwołują się do tej samej listy

To oznacza, że zmiana na liście dokonana przy użyciu jednej z tych trzech zmiennych
dotyczy listy, do której wszystkie się odwołują. Wracając do przyjęcia, powiedzmy,
że moja dziewczyna przyciąga moją uwagę, nazywając mnie „honey”. Prosi mnie, żebym
zmienił moją marynarkę na czerwony sweter, który zrobiła na drutach (tak, robi także
Referencje współdzielone 151

na drutach). Ja oczywiście robię to, o co prosi. W mojej sesji interaktywnej mogłoby to


zostać wyrażone następująco:
>>> honey[2] = "czerwony sweter"
>>> print(honey)
['spodnie khaki', 'koszula frakowa', 'czerwony sweter']

Wyniki są takie, jakich można by oczekiwać. Element zajmujący pozycję numer 2


na liście, do której odwołuje się zmienna honey, to już nie "marynarka", lecz "czerwony
sweter".
Więc gdyby na przyjęciu kolega chciał zwrócić na siebie moją uwagę, nazywając mnie
„Mike”, lub dygnitarz chciał mnie przywołać, zwracając się do mnie „Mr. Dawson”,
obydwaj by mnie zobaczyli w czerwonym swetrze, chociaż nikt z nich nie miał nic
wspólnego z moją zmianą ubioru. To samo dotyczy Pythona. Jeśli nawet zmieniłem
wartość elementu na pozycji numer 2 poprzez użycie zmiennej honey, ta zmiana została
odzwierciedlona przez każdą zmienną, która odwołuje się do tej listy. Więc kontynuuję
moją sesję interaktywną:
>>> print(mike)
['spodnie khaki', 'koszula frakowa', 'czerwony sweter']
>>> print(mr_dawson)
['spodnie khaki', 'koszula frakowa', 'czerwony sweter']

Element zajmujący pozycję numer 2 listy, do której odwołują się zmienne mike
i mr_dawson to "czerwony sweter". Nie może być inaczej, skoro istnieje tylko jedna lista.
Więc morał, który płynie z tej historii, jest następujący: uważaj na współdzielone
referencje, kiedy korzystasz z wartości mutowalnych. Jeśli zmienisz wartość poprzez
jedną zmienną, zostanie zmieniona dla wszystkich.
Możesz jednak uniknąć tego efektu, jeśli wykonasz kopię listy poprzez wycinanie.
Na przykład:
>>> mike = ["spodnie khaki", "koszula frakowa", "marynarka"]
>>> honey = mike[:]
>>> honey[2] = "czerwony sweter"
>>> print(honey)
['spodnie khaki', 'koszula frakowa', 'czerwony sweter']
>>> print(mike)
["spodnie khaki", "koszula frakowa", "marynarka"]

Tym razem do zmiennej honey zostaje przypisana kopia listy mike. Zmienna honey
nie odwołuje się do tej samej listy. Natomiast odwołuje się do jej kopii. Więc zmiana
dotycząca listy honey nie ma żadnego wpływu na listę mike. To tak, jakbym został
sklonowany. Teraz moja dziewczyna ubiera mojego klona w czerwony sweter, podczas
gdy oryginał mojej osoby nosi nadal marynarkę. W porządku, to przyjęcie staje się dość
niesamowite, gdy mój klon paraduje w czerwonym swetrze, który moja fikcyjna dziewczyna
zrobiła dla mnie na drutach, więc myślę, iż nadeszła pora, aby zakończyć tę dziwaczną,
choć użyteczną analogię.
152 Rozdział 5. Listy i słowniki. Gra Szubienica

Ostatnia rzecz, którą warto zapamiętać, to uwaga, że czasem efekt współdzielonej


referencji będzie dla Ciebie pożądany, a czasem nie. Teraz, kiedy rozumiesz, na czym
on polega, możesz go kontrolować.

Używanie słowników
Do tej pory zdążyłeś sobie prawdopodobnie uświadomić, że programiści uwielbiają
organizowanie informacji. Przekonałeś się, jak listy i krotki umożliwiają organizowanie
różnych rzeczy w sekwencje. Słowniki również umożliwiają Ci organizowanie informacji,
lecz w inny sposób. W przypadku słownika nie przechowujesz informacji w postaci
sekwencji; zamiast tego przechowujesz je w postaci par. Przypomina to trochę prawdziwy
słownik, w którym każda pozycja stanowi parę: słowo oraz jego definicję. Kiedy znajdujesz
słowo, odczytujesz jego definicję. Słowniki w Pythonie funkcjonują w taki sam sposób:
szukasz klucza i pobierasz jego wartość.

Wprowadzenie do programu Translator


slangu komputerowego
Świat zaawansowanych technologii stworzył wiele rzeczy, które wpływają na nasze życie,
łącznie z samą kulturą. W wyniku rozwoju technologii narodziły się nowe słowa i pojęcia.
Translator slangu komputerowego ma Ci pomóc zrozumieć wkraczających w Twoje
życie entuzjastów postępu technicznego. Program tworzy słownik zawierający terminy
używane przez maniaków komputerowych oraz ich definicje. Program umożliwia nie
tylko odszukanie definicji terminu, ale również dodanie nowego wyrażenia, zmianę
definicji oraz usunięcie terminu. Rysunek 5.10 ilustruje działanie programu.

Rysunek 5.10. Więc „uninstalled” oznacza zwolniony. Byłem całkowitym 404 w tej kwestii
Używanie słowników 153

Tworzenie słowników
Pierwszą rzeczą, jaką zrobiłem w programie, było utworzenie słownika terminów i ich
definicji. Terminy ze slangu komputerowego znajdują się po lewej, a ich definicje po
prawej stronie. Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 5.; nazwa pliku
to translator_slangu.py.
# Translator slangu komputerowego
# Demonstruje używanie słowników

geek = {"404": "ignorant; od używanego w sieci WWW komunikatu o błędzie 404\n - nie
znaleziono strony.",
"Googling": "googlowanie; wyszukiwanie w internecie informacji dotyczących
jakiejś osoby.",
"Keyboard Plaque" : "(skojarzone z kamieniem nazębnym)zanieczyszczenia
nagromadzone w klawiaturze komputera.",
"Link Rot" : "(obumieranie linków) proces, w wyniku którego linki do stron WWW
stają się nieaktualne.",
"Percussive Maintenance" : "(konserwacja perkusyjna)naprawa urządzenia
elektronicznego przez stuknięcie.",
"Uninstalled" : "(odinstalowany) zwolniony z pracy; termin szczególnie popularny
w okresie bańki internetowej."}

Ten kod tworzy słownik o nazwie geek. Składa się on z sześciu par zwanych
elementami. Na przykład jednym z elementów jest "Keyboard Plaque" : "(skojarzone
z kamieniem nazębnym)zanieczyszczenia nagromadzone w klawiaturze komputera.".
Każdy element składa się z klucza i wartości. Klucze znajdują się po lewej stronie
dwukropków.
Wartości znajdują się po prawej stronie. Więc "Keyboard Plaque" to klucz, a jego
wartość to "(skojarzone z kamieniem nazębnym)zanieczyszczenia nagromadzone
w klawiaturze komputera.". Klucz jest dosłownie „kluczem” umożliwiającym dostęp
do wartości. To oznacza, że mógłbyś użyć klucza "Keyboard Plaque", aby pobrać jego
wartość "(skojarzone z kamieniem nazębnym)zanieczyszczenia nagromadzone
w klawiaturze komputera.".
Aby utworzyć swój własny słownik, postępuj według wzorca, którego ja użyłem.
Wpisz klucz, za nim dwukropek i wartość klucza. Do oddzielenia poszczególnych par
klucz-wartość użyj przecinków, a całość otocz nawiasami klamrowymi. Tak jak
w przypadku krotek czy list, możesz umieścić całość w jednym wierszu lub zapisać każdą
parę w oddzielnej linii, przechodząc do nowego wiersza po każdym przecinku.

Uzyskiwanie dostępu do wartości słownika


Jedną z najczęstszych czynności, które będziesz wykonywał w stosunku do słownika, jest
użycie klucza do pobrania wartości. Istnieje kilka różnych sposobów realizacji tego zadania.
Przykład każdego z nich pokażę Ci w tym rozdziale przy użyciu interaktywnego
interpretera.
154 Rozdział 5. Listy i słowniki. Gra Szubienica

Użycie klucza do pobrania wartości


Najprostszy sposób pobrania wartości ze słownika to uzyskanie do niej dostępu
za pomocą klucza. Aby uzyskać wartość klucza, umieść go po prostu w nawiasach
kwadratowych po nazwie słownika. Oto sesja interaktywna, która ma Ci pokazać,
co mam na myśli (przyjmijmy, że już wcześniej zdefiniowałem słownik geek):
>>> geek["404"]
'ignorant; od używanego w sieci WWW komunikatu o błędzie 404\n - nie znaleziono
strony.'
>>> geek["Link Rot"]
'Link Rot" : "(obumieranie linków) proces, w wyniku którego linki do stron WWW
stają się nieaktualne.'

Wygląda to podobnie do indeksowania sekwencji, ale istnieje ważna różnica.


Kiedy indeksujesz sekwencję, używasz numeru pozycji. Kiedy szukasz wartości
w słowniku, używasz klucza. Jest to jedyny bezpośredni sposób pobierania wartości
ze słownika. Tak naprawdę słowniki w ogóle nie obsługują numerów pozycji.
Czymś, co czasem zbija z tropu początkujących programistów, jest fakt, że w przypadku
słownika wartość nie może zostać wykorzystana do uzyskania klucza. To tak, jakby
próbować wykorzystać definicję do znalezienia słowa w rzeczywistym słowniku. Prawdziwe
słowniki nie są po prostu przystosowane do tego rodzaju rzeczy i dotyczy to również
słowników Pythona. Więc zapamiętaj — podajesz klucz i otrzymujesz wartość, nigdy
na odwrót.

Pułapka
Jeśli spróbujesz uzyskać wartość ze słownika poprzez bezpośredni dostęp
za pomocą klucza, który nie istnieje, wygenerujesz błąd:
>>> geek["Dancing Baloney"]
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
geek["Dancing Baloney"]
KeyError: 'Dancing Baloney'
Ponieważ "Dancing Baloney" nie jest kluczem w tym słowniku, mamy efekt
w postaci błędu. (Nawiasem mówiąc, termin „Dancing Baloney” — tańczące
głupoty — oznacza animowaną grafikę i inne efekty wizualne, które nie stanowią
istotnej wartości i są często używane przez projektantów stron WWW w celu
zrobienia wrażenia na klientach).

Sprawdzanie klucza za pomocą operatora in


przed pobraniem wartości
Ponieważ użycie nieistniejącego klucza może doprowadzić do błędu, zazwyczaj najlepiej
nie podejmować próby bezpośredniego dostępu do słownika bez przedsięwzięcia pewnych
środków ostrożności. Jedną z rzeczy, które możesz zrobić, jest sprawdzenie, czy klucz
istnieje, przed próbą pobrania jego wartości. Istnienie klucza można sprawdzić za
pomocą operatora in:
Używanie słowników 155

>>> if "Dancing Baloney" in geek:


print("Wiem, co to jest Dancing Baloney.")
else
print("Nie mam pojęcia, co to jest Dancing Baloney.")

Nie mam pojęcia, co to jest Dancing Baloney.

Ponieważ słownik nie zawiera klucza "Dancing Baloney", warunek "Dancing


Baloney" in geek jest fałszywy. Więc komputer stwierdza, że nie wie, co to jest.
Operatora in używa się do słowników w dużej mierze w taki sam sposób, jak był
stosowany do list czy krotek. Wpisujesz wartość, którą chcesz sprawdzić, a po niej in i
nazwę słownika. To tworzy warunek, który jest prawdziwy, jeśli klucz znajduje się
w słowniku; w przeciwnym wypadku warunek jest fałszywy. Jest to przydatne przed
podjęciem próby pobrania wartości. Lecz pamiętaj, operatorem in możesz sprawdzać
tylko klucze; nie można w ten sposób sprawdzać wartości.

Użycie metody get() do pobrania wartości


Istnieje także inny sposób pobierania wartości ze słownika. Można wykorzystać metodę
słownika o nazwie get(). Ta metoda ma wbudowane zabezpieczenia służące do obsługi
sytuacji, w których dochodzi do próby pobrania wartości nieistniejącego klucza.
Jeśli klucz nie istnieje, metoda zwraca wartość domyślną, którą można zdefiniować.
Spójrz na jeszcze jedną taką próbę:
>>> print(geek.get("Dancing Baloney", "Nie mam pojęcia."))
Nie mam pojęcia.

Dzięki użyciu w powyższym przykładzie metody get() uzyskałem gwarancję


otrzymania wartości zwrotnej. Jeśli ten termin nie występowałby w słowniku w roli
klucza, otrzymałbym jego definicję. Ponieważ go rzeczywiście nie było, otrzymałem
domyślną wartość zwrotną, którą zdefiniowałem, łańcuch "Nie mam pojęcia.".
By użyć metody get(), wystarczy przy jej wywołaniu podać poszukiwany klucz,
a następnie opcjonalną wartość domyślną. Jeśli klucz znajduje się w słowniku, pobierasz
jego wartość. Jeśli klucza nie ma w słowniku, otrzymujesz wartość domyślną. Ale istnieje
pewna szczególna sytuacja: jeśli nie podasz wartości domyślnej (to Twoja opcja), otrzymasz
zwrotnie wartość None. Oto przykład, w którym nie podałem wartości domyślnej:
>>> print(geek.get("Dancing Baloney"))
None

Skonfigurowanie programu
Pora na powrót do kodu programu Translator slangu komputerowego. Po utworzeniu
słownika geek zaimplementowałem system menu, z jakim wcześniej się spotkałeś, tym
razem obejmujący pięć możliwości wyboru. Tak jak przedtem, jeśli użytkownik wybiera
opcję 0, komputer go żegna.
156 Rozdział 5. Listy i słowniki. Gra Szubienica

choice = None
while choice != "0":

print(
"""
Translator slangu komputerowego

0 - zakończ
1 - znajdź termin
2 - dodaj nowy termin
3 - zmień definicję terminu
4 - usuń termin
"""
)

choice = input("Wybierasz: ")


print()

# wyjdź
if choice == "0":
print("Żegnaj.")

Pobranie wartości
Jeśli użytkownik wprowadzi liczbę 1, w kolejnym fragmencie programu zostanie
zapytany o termin do odszukania. Komputer sprawdza, czy ten termin znajduje się
w słowniku. W przypadku pozytywnej odpowiedzi program realizuje dostęp do słownika,
używając terminu jako klucza, pobiera definicję terminu i wyświetla ją. Jeśli terminu nie
ma w słowniku, komputer informuje o tym użytkownika.
# pobierz definicję
elif choice == "1":
term = input("Jaki termin mam przetłumaczyć?: ")
if term in geek:
definition = geek[term]
print("\n", term, "oznacza", definition)
else:
print("\nNiestety, nie znam terminu", term)

Dodanie pary klucz-wartość


Słowniki są mutowalne, więc można je modyfikować. Jeśli użytkownik wprowadzi liczbę
2, w kolejnym fragmencie programu do słownika zostanie dodany nowy termin:
# dodaj parę termin-definicja
elif choice == "2":
term = input("Jaki termin mam dodać?: ")
if term not in geek:
definition = input("\nPodaj definicję tego terminu: ")
geek[term] = definition
Używanie słowników 157

print("\nTermin", term, "został dodany.")


else:
print("\nTen termin już istnieje! Spróbuj zmienić jego definicję.")

Komputer pyta użytkownika o nowy termin do dodania. Jeśli terminu nie ma


jeszcze w słowniku, komputer wczytuje jego definicję i dodaje tak skompletowaną parę
do słownika:
geek[term] = definition

Powyższy kod tworzy nowy element w słowniku geek. Termin jest kluczem,
a definicja jego wartością. W dokładnie taki sposób przypisuje się nowy element
do słownika. Podajesz nazwę słownika, a po niej klucz w nawiasach kwadratowych,
operator przypisania i wartość klucza.
Napisałem ten program tak, aby komputer odmówił dodania terminu, jeśli znajduje
się już w słowniku. Jest to zabezpieczenie, które stworzyłem w celu uzyskania pewności,
że użytkownik nie nadpisze przypadkowo istniejącego wcześniej terminu. Jeśli użytkownik
chce faktycznie zmienić definicję istniejącego terminu, powinien wybrać opcję 3.

Sztuczka
Szczypta pesymizmu nie zaszkodzi, przynajmniej przy programowaniu. Jak widziałeś,
założyłem, że użytkownik mógłby spróbować dodać nowy termin, nie zdając sobie
sprawy z tego, że jest już on w słowniku. Jeżeli nie sprawdziłbym tego, użytkownik
mógłby nieświadomie nadpisać termin. Kiedy będziesz pisać swoje własne
programy, spróbuj pomyśleć o rzeczach, które mogłyby pójść w złą stronę,
a potem staraj się upewnić, że Twój program będzie sobie z nimi radził.
Więc bądź troszkę pesymistą.

Wymiana pary klucz-wartość


Jeśli użytkownik wprowadzi liczbę 3, w kolejnym fragmencie programu nastąpi wymiana
pary klucz-wartość:
# zmiana definicji istniejącego terminu
elif choice == "3":
term = input("Jaki termin mam przedefiniować?: ")
if term in geek:
definition = input("Jaka jest nowa definicja?: ")
geek[term] = definition
print("\nTermin", term, "został przedefiniowany.")
else:
print("\nTen termin nie istnieje! Spróbuj go dodać.")

Aby wymienić parę klucz-wartość, użyłem dokładnie takiego samego wiersza kodu
jak przy dodawaniu nowej pary:
geek[term] = definition

Python zastępuje dotychczasową wartość (definicję) nową.


158 Rozdział 5. Listy i słowniki. Gra Szubienica

Pułapka
Jeśli przypiszesz wartość do słownika przy użyciu klucza, który już istnieje, Python
zastąpi dotychczasową wartość bez protestu. Musisz zatem uważać, ponieważ
mógłbyś nadpisać wartość istniejącego klucza, nie zdając sobie z tego sprawy.

Usunięcie pary klucz-wartość


Jeśli użytkownik wprowadzi liczbę 4, zostanie wykonany następujący blok elif:
# usunięcie pary termin-definicja
elif choice == "4":
term = input("Jaki termin mam usunąć?: ")
if term in geek:
del geek[term]
print("\nOK, usunąłem go", term)
else:
print("\nNie mogę tego zrobić! Terminu", term, "nie ma w słowniku.")

Program pyta użytkownika o termin ze slangu komputerowego, który to termin ma


zostać usunięty. Następnie program sprawdza za pomocą operatora in, czy wprowadzony
przez użytkownika termin faktycznie znajduje się w słowniku. Jeśli się w nim znajduje,
właściwy element zostaje usunięty za pomocą instrukcji:
del geek[term]
Oznacza to usunięcie elementu z kluczem term ze słownika geek. W taki sposób
można usunąć dowolny element słownika. Wystarczy umieścić del przed wyrażeniem
złożonym z nazwy słownika i ujętego w nawiasy kwadratowe klucza elementu, który ma
zostać usunięty.
Jeśli na początku okaże się, że żargonowego terminu nie ma w słowniku,
wykonywana jest klauzula else i komputer powiadamia użytkownika o tym fakcie.

Pułapka
Próba usunięcia elementu słownika poprzez klucz, który nie istnieje, spowoduje
błąd. Upewnienie się, że klucz, którego chcesz użyć, istnieje, jest mądrym
posunięciem.

Dokończenie programu
Końcowa klauzula else informuje użytkownika, że wprowadził niepoprawną wartość
opcji:
# nieznana opcja
else:
print("\nNiestety,", choice, "to nieprawidłowy wybór.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


Powrót do gry Szubienica 159

Wymagania dotyczące struktury słownika


Jest kilka kwestii, o których powinieneś pamiętać, tworząc słowniki:
 Słownik nie może zawierać wielu elementów z tym samym kluczem. Pomyśl znowu
o prawdziwym słowniku. Stałby się dość niezrozumiały, gdybyś mógł dodawać
to samo słowo z całkowicie nowymi definicjami, ilekroć chciałbyś to zrobić.
 Klucz musi być niemutowalny. Może być łańcuchem znaków, liczbą lub krotką,
co daje Ci mnóstwo możliwości. Klucz musi być niemutowalny, bo gdyby
nie był taki, mógłbyś się później do niego wkraść i pozmieniać klucze,
co prawdopodobnie skończyłoby się pojawieniem się dwóch identycznych
kluczy. A dopiero co się dowiedziałeś, że jest to niedopuszczalne!
 Wartości nie muszą być unikalne. Poza tym mogą być mutowalne
lub niemutowalne. Mogą być zupełnie dowolne.
Ze słownikami można zrobić nawet jeszcze więcej rzeczy. Tabela 5.2 podsumowuje
niektóre użyteczne metody, które mogą Ci pomóc wydobyć z tego typu danych więcej
możliwości.

Tabela 5.2. Wybrane metody słownika


Metoda Opis
get(klucz, [wartość_ Zwraca wartość klucza. Jeśli klucz nie istnieje, zwracana jest
domyślna]) opcjonalna wartość_domyślna. Jeśli klucz nie istnieje i wartość_
domyślna nie została określona, zwracana jest wartość None.
keys() Zwraca widok wszystkich kluczy występujących w słowniku.
values() Zwraca widok wszystkich wartości występujących w słowniku.
items() Zwraca widok wszystkich elementów słownika. Każdy element
to dwuskładnikowa krotka, której pierwszym składnikiem jest
klucz, a drugim wartość klucza.

Pułapka
Widoki słownika — zwracane przez metody keys(), values() i items() — są pod
pewnymi względami podobne do list. Można po nich iterować za pomocą pętli
for. Nie są to jednak listy. Nie mogą na przykład być indeksowane. W dodatku
widoki są dynamiczne, co oznacza, że ich zawartość nie jest niezależna od
związanych z nimi słowników. Więc zmiana w słowniku znajduje swoje odbicie
w widokach tego słownika. Aby dowiedzieć się więcej o widokach, zajrzyj do
dokumentacji zamieszczonej na oficjalnej stronie Pythona (www.python.org).

Powrót do gry Szubienica


Łącząc to wszystko, czego się do tej pory nauczyłeś, możesz utworzyć grę Szubienica
zaprezentowaną na początku tego rozdziału. Program jest dużo dłuższy od któregokolwiek
z dotychczas poznanych, ale nie przerażaj się jego rozmiarem. Jego kod nie jest dużo
160 Rozdział 5. Listy i słowniki. Gra Szubienica

bardziej skomplikowany niż w przypadku innych projektów, jakie przeanalizowałeś.


Największą część tego programu stanowi moja skromna twórczość z dziedziny ASCII-art
— osiem wersji wieszanego ludzika z „patyczków”. Konkretna substancja tego programu
zajmuje niewiele więcej niż pełny ekran kodu.

Skonfigurowanie programu
Ale zacznijmy od początku. Jak zawsze rozpocząłem od otwierających komentarzy
wyjaśniających program. Następnie zaimportowałem moduł random. Potrzebuję tego
modułu do losowego wybrania słowa z sekwencji. Kod tego programu możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 5.; nazwa pliku to szubienica.py.
# Szubienica
#
# Klasyczna gra w szubienicę. Komputer losowo wybiera słowo,
# a gracz próbuje odgadnąć jego poszczególne litery. Jeśli gracz
# nie odgadnie w porę całego słowa, mały ludzik zostaje powieszony.

# import modułów
import random

Tworzenie stałych
Chociaż ten kolejny fragment programu obejmuje kilka ekranów kodu, tworzę w nim
tylko trzy stałe. Najpierw utworzyłem największą krotkę, z jaką się spotkałeś. To tak
naprawdę tylko sekwencja ośmiu elementów, ale każdy element to łańcuch w potrójnym
cudzysłowie, który obejmuje 12 wierszy.
Każdy łańcuch przedstawia szubienicę, na której jest wieszany ludzik z patyków.
Każdy kolejny łańcuch pokazuje coraz bardziej kompletną postać. Za każdym razem,
gdy odpowiedź gracza jest niepoprawna, wyświetlany jest następny łańcuch. Po siedmiu
nietrafnych odpowiedziach rysunek jest już kompletny, a ludzik zostaje nieboszczykiem.
Jeśli ten ostatni łańcuch zostaje wyświetlony, gracz poniósł porażkę i gra się skończyła.
Przypisałem tę krotkę do zmiennej HANGMAN, której nazwa zawiera same duże litery,
ponieważ będę jej używał jako stałej.
# stałe
HANGMAN = (
"""
------
| |
|
|
|
|
|
|
|
----------
Powrót do gry Szubienica 161

""",
"""
------
| |
| O
|
|
|
|
|
|
----------
""",
"""
------
| |
| O
| -+-
|
|
|
|
|
----------
""",
"""
------
| |
| O
| /-+-
|
|
|
|
|
----------
""",
"""
------
| |
| O
| /-+-/
|
|
|
|
|
----------
""",
"""
------
| |
| O
162 Rozdział 5. Listy i słowniki. Gra Szubienica

| /-+-/
| |
|
|
|
|
----------
""",
"""
------
| |
| O
| /-+-/
| |
| |
| |
| |
|
----------
""",
"""
------
| |
| O
| /-+-/
| |
| |
| | |
| | |
|
----------
""")

Następnie utworzyłem stałą reprezentującą maksymalną liczbę nieudanych prób


odgadnięcia litery, które gracz może podjąć przed zakończeniem gry:
MAX_WRONG = len(HANGMAN) - 1

Maksymalna liczba niepoprawnych prób odgadnięcia jest o jeden mniejsza niż


długość krotki HANGMAN. Jest to podyktowane tym, że pierwszy rysunek pustej szubienicy
jest wyświetlany, jeszcze zanim gracz podejmie swoją pierwszą próbę. Więc chociaż
w krotce HANGMAN znajduje się osiem rysunków, gracz otrzymuje tylko siedem możliwości
popełnienia błędu, zanim gra się zakończy.
W końcu utworzyłem krotkę zawierającą wszystkie słowa, które może wybrać
komputer do odgadnięcia przez gracza. Możesz śmiało zmodyfikować program
i sporządzić swoją własną listę tych słów.
WORDS = ("NADUŻYWANY", "MAŁŻ", "GUAM", "TAFTA", "PYTHON")
Powrót do gry Szubienica 163

Inicjalizacja zmiennych
Następnie przeprowadziłem inicjalizację zmiennych. W celu losowego wybrania słowa
z listy możliwych posłużyłem się funkcją random.choice(). Przypisałem to tajemne słowo
do zmiennej word.
# inicjalizacja zmiennych
word = random.choice(WORDS) # słowo do odgadnięcia

Utworzyłem jeszcze jeden łańcuch o nazwie so_far odzwierciedlający to, co już


graczowi udało się w bieżącej turze gry odgadnąć. Początkowa postać łańcucha to ciąg
kresek, z których każda odpowiada pojedynczej literze słowa. Kiedy gracz odgadnie
prawidłowo jakąś literę, kreski zajmujące jej pozycje zostaną zastąpione samą literą.
so_far = "-" * len(word) # kreska zastępuje nieodgadniętą literę

Utworzyłem zmienną wrong i przypisałem jej liczbę 0. Zmienna wrong rejestruje


liczbę nieudanych prób odgadnięcia litery dokonanych przez gracza.
wrong = 0 # liczba nietrafionych liter

Utworzyłem też pustą listę, used, która ma zawierać wszystkie litery, których gracz
użył w trakcie odgadywania:
used = [] # litery już użyte w zgadywaniu

Utworzenie głównej pętli programu


Utworzyłem pętlę, która jest wykonywana, dopóki gracz nie wprowadzi zbyt dużej liczby
błędnych liter albo nie odgadnie wszystkich liter występujących w słowie:
print("Witaj w grze 'Szubienica'. Powodzenia!")

while wrong < MAX_WRONG and so_far != word:


print(HANGMAN[wrong])
print("\nWykorzystałeś już następujące litery:\n", used)
print("\nNa razie zagadkowe słowo wygląda tak:\n", so_far)

Następnie wyświetlam aktualną postać ludzika na podstawie liczby nieudanych prób


odgadnięcia litery dokonanych przez gracza. Im więcej gracz takich prób wykonał, tym
bliższy swojego końca jest ludzik. Potem wyświetlam listę liter, których gracz użył w tej
grze. A następnie pokazuję, jak aktualnie wygląda częściowo odgadnięte słowo.

Pobranie litery wprowadzonej przez gracza


Wczytuję literę wprowadzoną przez gracza i zamieniam ją na dużą, tak aby można ją było
znaleźć w tajemnym słowie (które zostało zapisane przy użyciu samych dużych liter).
Potem sprawdzam, czy gracz nie użył już tej litery. Jeśli gracz już przedtem podał tę literę,
wymuszam na nim wprowadzenie nowego znaku, dopóki nie wprowadzi takiego, jakiego
dotychczas nie użył. Kiedy gracz wprowadzi poprawną literę, przekształcam ją na dużą
i dodaję do listy użytych liter.
164 Rozdział 5. Listy i słowniki. Gra Szubienica

guess = input("\n\nWprowadź literę: ")


guess = guess.upper()

while guess in used:


print("Już wykorzystałeś literę", guess)
guess = input("Wprowadź literę: ")
guess = guess.upper()

used.append(guess)

Sprawdzenie, czy odgadywana litera znajduje się


w słowie
Następnie sprawdzam, czy litera występuje w tajemnym słowie. Jeśli tak, powiadamiam
o tym gracza. Potem przystępuję do tworzenia nowej wersji łańcucha so_far, w którym ta
nowa litera wystąpi we wszystkich miejscach, w których występuje w tajemnym słowie.
if guess in word:
print("\nTak!", guess, "znajduje się w zagadkowym słowie!")

# utwórz nową wersję zmiennej so_far, aby zawierała odgadniętą literę


new = ""
for i in range(len(word)):
if guess == word[i]:
new += guess
else:
new += so_far[i]
so_far = new

Jeśli litera zaproponowana przez gracza nie występuje w słowie, powiadamiam go


o tym i zwiększam liczbę błędnych prób o jeden.
else:
print("\nNiestety,", guess, "nie występuje w zagadkowym słowie.")
wrong += 1

Zakończenie gry
W tym momencie gra została zakończona. Jeśli liczba nieudanych prób odgadnięcia
litery osiągnęła wartość maksymalną, gracz przegrał, więc wyświetlam końcowy rysunek
ludzika. W przeciwnym razie gratuluję graczowi. W każdym z tych przypadków
informuję gracza, jakie to było słowo.
if wrong == MAX_WRONG:
print(HANGMAN[wrong])
print("\nZostałeś powieszony!")
else:
print("\nOdgadłeś!")

print("\nZagadkowe słowo to", word)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


Podsumowanie 165

Podsumowanie
W tym rozdziale dowiedziałeś się wszystkiego o listach i słownikach — dwóch nowych
typach danych. Dowiedziałeś się, że listy są mutowalnymi sekwencjami. Zobaczyłeś, jak
można dodawać, usuwać i sortować elementy listy, a nawet jak odwracać ich kolejność.
Ale dowiedziałeś się również, że pomimo tego wszystkiego, co oferują listy, są pewne
przypadki, w których mniej elastyczna krotka jest akurat lepszym (lub wymaganym)
wyborem. Poznałeś również referencje współdzielone, które mogą występować przy
mutowalnych typach danych, i zobaczyłeś, jak ich unikać, gdy to konieczne. Widziałeś,
jak można tworzyć sekwencje zagnieżdżone i jak je można wykorzystywać do obsługi
jeszcze ciekawszych struktur informacji, takich jak lista najlepszych wyników.
Dowiedziałeś się także, jak tworzyć i modyfikować słowniki, które umożliwiają obsługę
par danych.

Sprawdź swoje umiejętności


1. Utwórz program, który wypisuje listę słów w przypadkowej kolejności.
Program powinien wypisać wszystkie słowa bez żadnych powtórzeń.
2. Napisz program Kreator postaci do gry z podziałem na role. Gracz powinien
otrzymać pulę 30 punktów, którą może spożytkować na cztery atrybuty: siła,
zdrowie, mądrość i zręczność. Gracz powinien mieć możliwość przeznaczania
punktów z puli na dowolny atrybut, jak również możliwość odbierania
punktów przypisanych do atrybutu i przekazywania ich z powrotem do puli.
3. Napisz program Kto jest twoim tatą?, który umożliwia użytkownikowi
wprowadzenie imienia i nazwiska osoby płci męskiej i przedstawia imię
i nazwisko jej ojca. (Możesz dla zabawy wykorzystać nazwiska celebrytów,
osób fikcyjnych lub nawet postaci historycznych). Umożliw użytkownikowi
dodawanie, wymianę i usuwanie par syn-ojciec.
4. Udoskonal program Kto jest moim tatą? poprzez dodanie opcji, która umożliwi
użytkownikowi wprowadzenie imienia i (lub) nazwiska jakiejś osoby i uzyskanie
odpowiedzi, kto jest jej dziadkiem. Twój program powinien nadal wykorzystywać
tylko jeden słownik par syn-ojciec. Pamiętaj, aby włączyć do swojego słownika
kilka pokoleń, tak aby możliwe było tego rodzaju dopasowanie.
166 Rozdział 5. Listy i słowniki. Gra Szubienica
6
Funkcje.
Gra Kółko i krzyżyk

K ażdy program, który do tej pory pisałeś, był jednym, nieprzerwanym ciągiem
instrukcji. Kiedy już jednak programy osiągną pewien rozmiar lub stopień
złożoności, ten sposób pracy z nimi staje się trudny. Na szczęście istnieją metody rozbicia
dużych programów na mniejsze, łatwe do opanowania kawałki kodu. W tym rozdziale
poznasz jeden ze sposobów realizacji tego zadania poprzez tworzenie swoich własnych
funkcji. W szczególności w tym rozdziale nauczysz się:
 pisać swoje własne funkcje,
 przyjmować w swoich funkcjach wartości z zewnątrz poprzez parametry,
 zwracać ze swoich funkcji informacje poprzez wartości zwrotne,
 wykorzystywać zmienne globalne i stałe,
 tworzyć komputerowego przeciwnika w grze strategicznej.

Wprowadzenie do gry Kółko i krzyżyk


Dzięki projektowi z tego rozdziału dowiesz się, jak utworzyć komputerowego
przeciwnika przy użyciu szczypty sztucznej inteligencji (ang. artificial intelligence — AI).
Gracz i komputer stają do ostatecznej rozgrywki między człowiekiem a maszyną w grze
o wysokie stawki o nazwie Kółko i krzyżyk. Komputer prowadzi wspaniałą, choć
niedoskonałą grę i wykazuje się podejściem, które sprawia, że każda rozgrywka
dostarcza dobrej zabawy. Rysunki od 6.1 do 6.3 ilustrują przebieg gry.
168 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Rysunek 6.1. Komputerowi nie brakuje pewności siebie

Rysunek 6.2. Nie zauważyłem tego zagrożenia. Nawet po zastosowaniu prostych technik
programowania komputer potrafi wykonywać dość dobre posunięcia

Rysunek 6.3. Znalazłem słabą stronę komputera i tym razem wygrałem


Tworzenie funkcji 169

Tworzenie funkcji
Już miałeś okazję zobaczyć kilka funkcji wbudowanych w działaniu, w tym funkcje len()
i range(). Jeśli Ci one nie wystarczają, Python umożliwi Ci utworzenie swoich własnych.
Twoje funkcje działają dokładnie tak samo jak te, które są standardowo dostępne
w języku. Uruchamiają się i wykonują zadanie, a potem zwracają sterowanie do programu.
Tworzenie swoich własnych funkcji daje wiele korzyści. Jedną z największych jest możliwość
rozbicia kodu na łatwe do ogarnięcia, niewielkie kawałki. Programy składające się z jednego,
długiego ciągu instrukcji, niepodzielne pod względem logicznym są trudne do pisania,
zrozumienia i konserwacji. Programy, które są zbudowane z funkcji, mogą być łatwiejsze
w tworzeniu i użytkowaniu. Podobnie jak funkcje, z którymi już się spotkałeś, Twoje
nowe funkcje powinny dobrze wykonywać jedno zadanie.

Prezentacja programu Instrukcja


Na podstawie zrzutów ekranu z grą Kółko i krzyżyk, możesz zapewne stwierdzić, że
komputerowy przeciwnik jest troszkę arogancki. Widać to całkiem jasno z instrukcji,
jaką komputer wyświetla przed rozpoczęciem gry. Kodowi, który tworzy tę instrukcję,
przyjrzysz się w kolejnym programie, o nazwie Instrukcja. Ten kod jest nieco inny od
tego, czego mógłbyś oczekiwać. Jest tak dlatego, że utworzyłem funkcję do wyświetlania
instrukcji. Tej samej funkcji użyłem w programie Instrukcja. Spójrz na rysunek 6.4,
aby zobaczyć przykładowe uruchomienie programu.

Rysunek 6.4. Instrukcja jest za każdym razem wyświetlana za pomocą tylko jednego
wiersza kodu — wywołania utworzonej przeze mnie funkcji
170 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 6.; nazwa pliku
to instrukcja.py.
# Instrukcja
# Demonstruje funkcje tworzone przez programistę

def instructions():
"""Wyświetl instrukcję gry."""
print(
"""
Witaj w największym intelektualnym wyzwaniu wszech czasów, jakim jest
gra 'Kółko i krzyżyk'. Będzie to ostateczna rozgrywka między Twoim
ludzkim mózgiem a moim krzemowym procesorem.

Swoje posunięcie wskażesz poprzez wprowadzenie liczby z zakresu 0 - 8.


Liczba ta odpowiada pozycji na planszy zgodnie z poniższym schematem:

0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8

Przygotuj się, Człowieku. Ostateczna batalia niebawem się rozpocznie. \n


"""
)

# main
print("Oto instrukcja do gry 'Kółko i krzyżyk':")
instructions()
print("Ponownie ta sama instrukcja:")
instructions()
print("Prawdopodobnie teraz już zrozumiałeś tę grę.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Definiowanie funkcji
Rozpocząłem definicję swojej nowej funkcji od pojedynczego wiersza:
def instructions():

Wiersz ten informuje komputer, że blok kodu, który po nim wystąpi, ma zostać
potraktowany w całości jako funkcja instructions(). Zasadniczo nadaję temu blokowi
instrukcji nazwę. To oznacza, że ilekroć w tym programie wywołam funkcję
instructions(), zostanie wykonany ten blok kodu.
Ten wiersz i następujący po nim blok instrukcji stanowią definicję funkcji. Określają,
co funkcja robi, ale jej nie uruchamiają. Kiedy komputer napotyka definicję funkcji,
odnotowuje, że ta funkcja istnieje, więc może jej później użyć. Nie uruchomi tej funkcji,
dopóki nie napotka w dalszej części programu odnoszącego się do niej wywołania.
Tworzenie funkcji 171

Aby zdefiniować swoją własną funkcję, naśladuj mój przykład. Zacznij od słowa def,
po którym wpisz nazwę funkcji z parą nawiasów, następnie dwukropek i wcięty blok
instrukcji. Przy wyborze nazwy funkcji przestrzegaj podstawowych reguł odnoszących się
do nazywania zmiennych. Spróbuj także użyć nazwy, która odzwierciedla to, co funkcja
tworzy, lub to, jaką czynność wykonuje.

Dokumentowanie funkcji
Funkcje zawierają specjalny mechanizm, który pozwala na ich dokumentowanie za pomocą
czegoś, co nazywa się łańcuchem dokumentacyjnym (ang. documentation string, docstring).
Do funkcji instructions() utworzyłem następujący łańcuch dokumentacyjny:
"""Wyświetl instrukcję gry."""

Łańcuch dokumentacyjny jest na ogół łańcuchem w potrójnym cudzysłowie i jeśli


takiego używasz, musi stanowić pierwszy wiersz bloku kodu Twojej funkcji. W przypadku
prostych funkcji możesz zrobić to, co ja w tym programie — wpisać pojedyncze zdanie,
które opisuje to, co funkcja robi. Funkcje działają równie dobrze bez łańcuchów
dokumentacyjnych, ale ich używanie jest dobrym pomysłem. Pozwala Ci nabrać
zwyczaju komentowania swojego kodu i zmusza Cię do opisania jednego, dobrze
zdefiniowanego zadania funkcji. Łańcuch dokumentacyjny funkcji może także wyświetlić
się w postaci interaktywnej dokumentacji, kiedy wpiszesz jego wywołanie w IDLE.

Wywołanie funkcji utworzonej przez programistę


Wywołanie funkcji utworzonej przez programistę działa dokładnie tak samo jak wywołanie
funkcji wbudowanej. Należy użyć nazwy funkcji i umieszczonej za nią pary nawiasów.
Ja wywołałem swoją nową funkcję kilkakrotnie, za każdym razem za pomocą wiersza:
instructions()

To wywołanie nakazuje komputerowi uruchomienie i wykonanie funkcji, którą


wcześniej zdefiniowałem. Więc ilekroć tego wiersza używam, komputer wypisuje
instrukcję gry.

Pojęcie abstrakcji
Pisząc i wywołując funkcje, stosujesz w praktyce coś, co jest znane pod nazwą abstrakcji.
Abstrakcja pozwala Ci myśleć o ogólnym obrazie bez troszczenia się o szczegóły. Więc
i w tym programie mogę używać funkcji instructions(), nie martwiąc się o szczegóły
związane z wyświetlaniem tekstu. Wszystko, co mam do zrobienia, to wywołanie funkcji
w pojedynczym wierszu kodu, a ona wykonuje całe zadanie.
Mógłbyś być zaskoczony tym, gdzie można spotkać się z abstrakcją, ale ludzie
korzystają z niej nieustannie. Weź na przykład pod uwagę dwóch pracowników w lokalu
fastfoodowym. Jeśli jeden mówi do drugiego, że właśnie załadował trójkę i podliczył ją,
drugi pracownik wie, że ten pierwszy odebrał od klienta zamówienie, podszedł do
172 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

podgrzewaczy, złapał hamburgera, podszedł do frytkownicy, napełnił największy


kartonowy pojemnik frytkami, podszedł do saturatora, chwycił największy kubek,
napełnił go wodą sodową, podał to wszystko klientowi, wziął od niego pieniądze i wydał
mu resztę. Taka wersja konwersacji byłaby nie tylko nudna, ale również niepotrzebna.
Obydwaj pracownicy wiedzą, co to znaczy załadować trójkę i ją podliczyć. Nie muszą
zawracać sobie głowy wszystkimi szczegółami, ponieważ wykorzystują abstrakcję.

Używanie parametrów i wartości zwrotnych


Jak już widziałeś przy omawianiu funkcji wbudowanych, możesz dostarczać do funkcji
pewne wartości i otrzymywać z powrotem wartości od niej. Na przykład w przypadku
funkcji len() dostarczasz sekwencję, a funkcja zwraca jej długość. Twoje własne funkcje
mogą również otrzymywać i zwracać wartości. To umożliwia Twoim funkcjom
komunikowanie się z resztą programu.

Prezentacja programu Pobierz i zwróć


Utworzyłem w programie Pobierz i zwróć trzy funkcje, aby pokazać różne kombinacje
pobierania i zwracania wartości. Jedna funkcja pobiera wartość. Następna zwraca pewną
wartość. A ostatnia funkcja zarówno pobiera, jak i zwraca wartość. Przyjrzyj się
rysunkowi 6.5, aby zobaczyć, co dokładnie się dzieje w wyniku uruchomienia programu.

Rysunek 6.5. Każda z funkcji wykorzystuje albo parametr, albo wartość zwrotną, albo obie
wartości do komunikowania się z główną częścią programu

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 6.; nazwa pliku
to pobierz_i_zwroc.py.
# Pobierz i zwróć
# Demonstruje parametry i wartości zwrotne

def display(message):
print(message)

def give_me_five():
Używanie parametrów i wartości zwrotnych 173

five = 5
return five

def ask_yes_no(question):
"""Zadaj pytanie, na które można odpowiedzieć tak lub nie."""
response = None
while response not in ("t", "n"):
response = input(question).lower()
return response

# main
display("To wiadomość dla Ciebie.\n")

number = give_me_five()
print("Oto co otrzymałem z funkcji give_me_five():", number)

answer = ask_yes_no("\nProszę wprowadzić 't' lub 'n': ")


print("Dziękuję za wprowadzenie:", answer)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Otrzymywanie informacji poprzez parametry


Pierwsza funkcja, jaką zdefiniowałem, display(), otrzymuje wartość i wypisuje ją.
Wartość tę otrzymuje poprzez swój parametr. Parametry są zasadniczo nazwami
zmiennych występującymi wewnątrz nawiasów nagłówka funkcji:
def display(message):

Parametry przechwytują wartości przesłane do funkcji z wywołania funkcji poprzez


jego argumenty. Więc w tym przypadku, kiedy wywoływana jest funkcja display(),
parametrowi message zostaje przypisana wartość dostarczona przez argument
"To wiadomość dla Ciebie.\n".
W głównej części programu wywołuję funkcję display() za pomocą instrukcji:
display("To wiadomość dla Ciebie.\n")

W rezultacie parametr message otrzymuje wartość w postaci łańcucha "To wiadomość


dla Ciebie.\n". Potem funkcja jest wykonywana. Parametr message, jak każdy inny
parametr, istnieje wewnątrz funkcji jako zmienna. Tak więc wiersz kodu:
print(message)

wyświetla łańcuch "To wiadomość dla Ciebie.\n".


Jeśli nie przekazałbym wartości do parametru message, wygenerowałbym błąd.
Funkcja display() wymaga podania dokładnie jednej wartości jako argumentu.
Chociaż funkcja display() ma tylko jeden parametr, inne funkcje mogą posiadać ich
wiele. Aby zdefiniować funkcję z wieloma parametrami, wymień wszystkie, oddzielając je
przecinkami.
174 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Zwracanie informacji poprzez wartości zwrotne


Kolejna funkcja, jaką napisałem, give_me_five(), zwraca wartość. Zwraca ją poprzez
(możesz wierzyć lub nie) instrukcję return:
return five

Kiedy wykonywany jest ten wiersz kodu, funkcja przekazuje wartość zmiennej five
z powrotem do tej części programu, w której została wywołana, a następnie kończy swoje
działanie. Funkcja zawsze kończy swoją pracę po napotkaniu instrukcji return.
Przechwycenie wartości zwróconej przez funkcję i zrobienie coś z nią jest zadaniem
tej części programu, która tę funkcję wywołała. Oto główna część programu, w której
wywołałem funkcję:
number = give_me_five()
print("Oto co otrzymałem z funkcji give_me_five():", number)

Zastosowałem sposób przechwycenia wartości zwrotnej funkcji poprzez przypisanie


wyniku wywołania funkcji do zmiennej number. Więc kiedy funkcja kończy swoje działanie,
zmienna number otrzymuje wartość zwrotną funkcji give_me_five(), która jest równa 5.
Następny wiersz kodu wypisuje wartość zmiennej number, aby pokazać, że zmienna
otrzymała wartość zwrotną w prawidłowy sposób.
Można przekazać z powrotem z funkcji więcej niż jedną wartość. Wystarczy
wymienić wszystkie wartości, które mają być zwrócone, oddzielając je przecinkami.

Pułapka
Pamiętaj, aby przewidzieć wystarczającą liczbę zmiennych do przechwycenia
wszystkich wartości zwrotnych funkcji. Jeśli w przypisaniu nie użyjesz właściwej
ich liczby, wygenerujesz błąd.

Pojęcie hermetyzacji
Być może nie widzisz potrzeby korzystania z wartości zwrotnych w sytuacji, gdy używasz
swoich własnych funkcji. Dlaczego nie wykorzystać samej zmiennej five po powrocie
do głównej części programu? Ponieważ jest to niemożliwe. Zmienna five nie istnieje
na zewnątrz funkcji give_me_five(). W gruncie rzeczy żadna zmienna, którą utworzysz
w funkcji, nie wyłączając parametrów, nie jest bezpośrednio dostępna na zewnątrz niej
samej. To dobra zasada, która nazywa się hermetyzacją (lub kapsułkowaniem, ang.
encapsulation). Hermetyzacja prawdziwie pomaga oddzielić niezależny kod poprzez
ukrycie, czyli zakapsułkowanie jego szczegółów. To dlatego używa się parametrów i wartości
zwrotnych — do przekazywania tylko tych informacji, które muszą być wymieniane. Poza
tym nie musisz śledzić zmiennych, które tworzysz wewnątrz funkcji, w pozostałej części
programu. W sytuacji, gdy Twoje programy się rozrastają, to wielka korzyść.
Hermetyzacja może bardzo przypominać abstrakcję. To dlatego, że te dwa pojęcia są
ze sobą blisko związane. Hermetyzacja stanowi główny mechanizm abstrakcji. Abstrakcja
chroni Cię przed koniecznością troszczenia się o szczegóły. Hermetyzacja ukrywa przed
Używanie parametrów i wartości zwrotnych 175

Tobą szczegóły. Jako przykład weź pod uwagę pilot do telewizora wyposażony w przyciski
zwiększania i zmniejszania głośności. Kiedy używasz pilota do zmiany głośności stosujesz
abstrakcję, ponieważ nie musisz wiedzieć, co się dzieje wewnątrz telewizora, żeby to
działało. Teraz przypuśćmy, że pilot ma 10 poziomów głośności. Możesz uzyskać je
wszystkie przy użyciu pilota, ale nie masz do nich bezpośredniego dostępu. To znaczy nie
możesz bezpośrednio (poprzez numer) wybrać określonego poziomu głośności. Możesz
tylko używać przycisków zwiększających lub zmniejszających głośność, aby w końcu
uzyskać poziom, jakiego potrzebujesz. Sam poziom głośności (jego numer) został
zakapsułkowany i nie jest dla Ciebie bezpośrednio dostępny.

Wskazówka
Nie martw się, jeśli jeszcze nie zrozumiałeś całkowicie subtelnej różnicy między
abstrakcją a hermetyzacją. Te pojęcia splatają się ze sobą, więc sprawa może
być nieco złożona. Poza tym będziesz mógł zobaczyć je ponownie w działaniu,
kiedy będziesz poznawał obiekty programowe i programowanie obiektowe
w rozdziałach 8. i 9.

Otrzymywanie i zwracanie wartości


w tej samej funkcji
Ostatnia funkcja, jaką napisałem, ask_yes_no(), otrzymuje jedną wartość i zwraca drugą.
Pobiera pytanie i zwraca odpowiedź użytkownika, "t" lub "n". Funkcja otrzymuje
pytanie poprzez swój parametr:
def ask_yes_no(question):

Parametr question otrzymuje wartość argumentu przekazanego do funkcji. W tym


przypadku jest to łańcuch "\nProszę wprowadzić 't' or 'n': ". Kolejna część funkcji
wykorzystuje ten łańcuch do poproszenia użytkownika o podanie odpowiedzi:
response = None
while response not in ("t", "n"):
response = input(question).lower()

Pętla while powtarza zadawanie tego pytania, dopóki użytkownik nie wprowadzi t, T,
n lub N. Funkcja zawsze przekształca to, co wprowadził użytkownik, na małe litery.
W końcu, kiedy użytkownik wprowadzi prawidłową odpowiedź, funkcja przesyła
łańcuch z powrotem do tej części programu, z której została wywołana:
return response

i kończy swoje działanie.


W głównej części programu wartość zwrotna zostaje przypisana do zmiennej answer
i wyświetlona:
answer = ask_yes_no("\nProszę wprowadzić 't' or 'n': ")
print("Dziękuję za wprowadzenie:", answer)
176 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Ponowne wykorzystanie kodu


Inną wspaniałą właściwością funkcji jest to, że mogą być łatwo ponownie wykorzystane
w innych programach. Na przykład zadawanie użytkownikowi pytania, na które można
odpowiedzieć „tak” lub „nie”, jest tak często występującą czynnością, że można by
sięgnąć po funkcję ask_yes_no() i wykorzystać ją w innym programie bez tworzenia
jakiegoś dodatkowego kodu. Ten typ działania nazywa się ponownym wykorzystaniem
kodu. Więc pisanie dobrych funkcji nie tylko pozwala na zaoszczędzenie czasu i energii
w bieżącym projekcie, ale może również zmniejszyć Twój wysiłek, który będziesz musiał
włożyć w przyszłe projekty!

W świecie rzeczywistym
Wymyślanie koła na nowo jest zawsze stratą czasu, więc ponowne użycie kodu
poprzez wykorzystanie istniejącego oprogramowania i innych elementów projektu
w nowych projektach to technika, którą biznes wziął sobie do serca. Ponowne
wykorzystanie kodu pozwala:
 zwiększyć wydajność przedsiębiorstwa — dzięki ponownemu wykorzystaniu
kodu i innych elementów, które już istnieją, firmy mogą realizować swoje
projekty przy mniejszym wysiłku;
 poprawić jakość oprogramowania — jeśli przedsiębiorstwo już przetestowało
jakąś część kodu, może używać tego kodu, wiedząc, że jest wolny od błędów;
 zapewnić spójność produktów programowych — używając na przykład tego
samego interfejsu użytkownika, firmy mogą tworzyć nowe oprogramowanie,
z którym użytkownikom od razu będzie się wygodnie pracowało;
 poprawić wydajność oprogramowania — jeśli przedsiębiorstwo dysponuje
dobrym sposobem realizacji zadania poprzez oprogramowanie, ponowne jego
użycie nie tylko zaoszczędza firmie trudu wymyślania koła na nowo, ale także
chroni ją przed możliwością wynalezienia mniej efektywnego koła.

Jednym ze sposobów wykorzystania napisanych przez Ciebie funkcji jest przekopiowanie


ich do Twojego nowego programu. Ale istnieje lepszy sposób. Możesz utworzyć swoje
własne moduły i zaimportować (za pomocą instrukcji import) swoje funkcje do nowego
programu, podobnie jak ja importowałem standardowe moduły Pythona i używałem ich
funkcji. Jak tworzyć swoje własne moduły i importować napisany przez siebie kod
do ponownego wykorzystania, dowiesz się w rozdziale 9., w podrozdziale „Tworzenie
modułów”.

Wykorzystanie argumentów nazwanych


i domyślnych wartości parametrów
Przekazywanie wartości przez argumenty do parametrów umożliwia dostarczanie
do funkcji informacji. Lecz do tej pory spotkałeś się tylko z najbardziej podstawowym
sposobem realizacji tego zadania. Python pozwala na większą kontrolę i elastyczność
Wykorzystanie argumentów nazwanych i domyślnych wartości parametrów 177

co do sposobu przekazywania informacji dzięki domyślnym wartościom parametrów


i argumentom nazwanym.

Prezentacja programu Życzenia urodzinowe


Program Życzenia urodzinowe, którego przykładowe uruchomienie zostało przedstawione
na rysunku 6.6, wysyła życzenia urodzinowe przy użyciu dwóch bardzo podobnych
funkcji. Pierwsza funkcja wykorzystuje parametry takiego samego typu jak te, z którymi
się spotkałeś w poprzednim podrozdziale, nazywane parametrami pozycyjnymi. Druga
wersja funkcji używa domyślnych wartości parametrów. Najlepszym sposobem oceny
różnicy między tymi rozwiązaniami jest zobaczenie ich przykładów w działaniu.

Rysunek 6.6. Funkcje mogą być wywoływane na różne sposoby z wykorzystaniem


elastyczności, jaką oferują argumenty nazwane i domyślne wartości parametrów

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 6.; nazwa pliku
to zyczenia_urodzinowe.py.
# Życzenia urodzinowe
# Demonstruje argumenty nazwane i domyślne wartości parametrów

# parametry pozycyjne
def birthday1(name, age):
print("Szczęśliwych urodzin,", name, "!", " Masz już", age, "lat.\n")

# parametry z wartościami domyślnymi


def birthday2(name = "Janusz", age = 5):
print("Szczęśliwych urodzin,", name, "!", " Masz już", age, "lat.\n")
178 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

birthday1("Janusz", 5)
birthday1(5, "Janusz")
birthday1(name = "Janusz", age = 5)
birthday1(age = 5, name = "Janusz")

birthday2()
birthday2(name = "Katarzyna")
birthday2(age = 12)
birthday2(name = "Katarzyna", age = 12)
birthday2("Katarzyna", 12)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Wykorzystywanie parametrów pozycyjnych


i argumentów pozycyjnych
Wpisując ciąg nazw zmiennych w nagłówku funkcji, tworzysz parametry pozycyjne:
def birthday1(name, age):

Jeśli wywołujesz funkcję, podając jedynie ciąg wartości, tworzysz argumenty


pozycyjne:
birthday1("Janusz", 5)

Używanie parametrów pozycyjnych i argumentów pozycyjnych oznacza, że parametry


otrzymują swoje wartości jedynie na podstawie pozycji przesłanych wartości. Pierwszy
parametr otrzymuje pierwszą przesłaną wartość, drugi parametr otrzymuje drugą
przesłaną wartość itd.
W przypadku tego konkretnego wywołania funkcji to oznacza, że parametr name
otrzymuje wartość "Janusz", a parametr age — wartość 5. Wynikiem tego jest komunikat
Szczęśliwych urodzin, Janusz ! Masz już 5 lat. Jeśli zamienisz miejsca tych dwóch
argumentów, parametry otrzymają inne wartości. Tak więc w wyniku wywołania:
birthday1(5, "janusz")

parametr name otrzyma pierwszą wartość, 5, a parametr age drugą wartość, "Janusz".
W rezultacie powstanie komunikat, który będzie zapewne nie taki, jakiego byś sobie
życzył: Szczęśliwych urodzin, 5 ! Masz już Janusz lat.
Ten sposób tworzenia i wywoływania funkcji już przedtem widziałeś. Lecz istnieją
też inne sposoby tworzenia list parametrów i argumentów w programach.

Używanie parametrów pozycyjnych


i argumentów nazwanych
Parametry pozycyjne otrzymują przesłane do nich argumenty zgodnie z kolejnością,
chyba że nakażesz funkcji inne postępowanie. Możesz zażądać, aby funkcja przypisała
pewne wartości do określonych parametrów niezależnie od kolejności, poprzez użycie
Wykorzystanie argumentów nazwanych i domyślnych wartości parametrów 179

argumentów nazwanych. W przypadku argumentów nazwanych używasz faktycznych


nazw parametrów z nagłówka funkcji do powiązania wartości z parametrem. Więc
w wyniku wywołania tej samej funkcji birthday1() w postaci:
birthday1(name = "Janusz", age = 5)

zmienna name otrzymuje wartość "Janusz", a zmienna age wartość 5 i funkcja wyświetla
komunikat Szczęśliwych urodzin, Janusz ! Masz już 5 lat.. Nie robi to zbyt wielkiego
wrażenia. Można by uzyskać ten sam wynik bez argumentów nazwanych — wystarczyłoby
przesłanie tych wartości w tej kolejności. Ale urok argumentów nazwanych polega na tym,
że ich kolejność nie ma znaczenia; to nazwy łączą wartości z parametrami. Więc wywołanie
birthday1(age = 5, name = "Janusz")

także powoduje utworzenie komunikatu Szczęśliwych urodzin, Janusz ! Masz już 5 lat.,
mimo że wartości zostały wyszczególnione w odwrotnej kolejności.
Argumenty nazwane pozwalają na przekazywanie wartości w dowolnym porządku.
Ale największą korzyścią z ich stosowania jest klarowność kodu. Kiedy widzisz
wywołanie funkcji przy użyciu argumentów nazwanych, uzyskujesz dużo lepszą
świadomość tego, co te wartości reprezentują.

Pułapka
Możesz łączyć argumenty nazwane i argumenty pozycyjne w tym samym wywołaniu
funkcji, ale może się to okazać zdradliwe. Kiedy już użyjesz argumentu nazwanego,
wszystkie pozostałe argumenty w wywołaniu muszą być także argumentami
nazwanymi. Aby nie komplikować spraw, staraj się używać samych argumentów
nazwanych albo samych argumentów pozycyjnych w swoich wywołaniach funkcji.

Używanie domyślnych wartości parametrów


Na koniec dysponujesz opcją przypisania wartości domyślnych do swoich parametrów —
wartości, które zostaną przypisane do parametrów, gdy nie zostanie do nich przekazana
żadna wartość. Właśnie z tej możliwości skorzystałem, tworząc funkcję birthday2().
Wprowadziłem zmiany tylko w nagłówku:
def birthday2(name = "Janusz", age = 5):

To oznacza, że jeśli do parametru name nie zostanie dostarczona żadna wartość,


przyjmie on wartość "Janusz". I jeśli żadna wartość nie zostanie przekazana do
parametru age, przyjmie on wartość 5. Więc wywołanie:
birthday2()

nie wygeneruje błędu; zamiast tego do parametrów zostaną przypisane wartości domyślne
i funkcja wyświetli komunikat Szczęśliwych urodzin Janusz ! Masz już 5 lat..
180 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Pułapka
Kiedy już przypiszesz wartość domyślną do jednego z elementów listy parametrów,
musisz przypisać wartości domyślne do wszystkich parametrów, które występują
w tej liście po nim. Więc ten nagłówek funkcji jest całkowicie poprawny:
def monkey_around(bananas = 100, barrel_of = "tak", uncle = "małpi"):
Lecz ten już nie:
def monkey_around(bananas = 100, barrel_of, uncle):
Powyższy nagłówek wygeneruje błąd.

Na razie wszystko jest w porządku. Ale możesz sprawy nieco skomplikować


poprzez zastąpienie wartości domyślnych wybranych lub wszystkich parametrów innymi
wartościami. W wyniku wywołania:
birthday2(name = "Katarzyna")

domyślna wartość parametru name zostaje zastąpiona przez łańcuch "Katarzyna",


parametr age nadal przyjmuje swoją wartość domyślną 5 i wyświetlony zostaje
komunikat Szczęśliwych urodzin Katarzyna ! Masz już 5 lat..
Po wywołaniu funkcji:
birthday2(age = 12)

wartość domyślna parametru age zostaje zastąpiona liczbą 12. Parametr name przyjmuje
swoją domyślną wartość "Janusz" i zostaje wyświetlony komunikat Szczęśliwych
urodzin Janusz ! Masz już 12 lat..
W przypadku wywołania:
birthday2(name = "Katarzyna", age = 12)

obie wartości domyślne zostają zastąpione. Parametr name otrzymuje wartość "Katarzyna",
a parametr age wartość 12. Zostaje wyświetlony komunikat Szczęśliwych urodzin
Katarzyna ! Masz już 12 lat..

A w wyniku wywołania:
birthday2("Katarzyna", 12)

otrzymujesz dokładnie taki sam wynik jak przy poprzednim wywołaniu. Obie wartości
domyślne zostają zastąpione. Parametr name otrzymuje wartość "Katarzyna", a parametr
age wartość 12. I zostaje wyświetlony komunikat Szczęśliwych urodzin Katarzyna !
Masz już 12 lat..

Sztuczka
Domyślne wartości parametrów są znakomitym rozwiązaniem w sytuacji, gdy
masz do czynienia z funkcją, w której przy prawie każdym wywołaniu pewien
parametr otrzymuje taką samą wartość. Aby oszczędzić programistom używającym
Twojej funkcji trudu wpisywania tej wartości za każdym razem, mógłbyś
zdefiniować dla tego parametru wartość domyślną.
Wykorzystanie zmiennych globalnych i stałych 181

Wykorzystanie zmiennych globalnych i stałych


Dzięki magii hermetyzacji funkcje, z którymi się zapoznałeś, są całkowicie odizolowane
i niezależne wzajemnie od siebie i od głównej części programu. Można do nich dostarczać
informacje jedynie poprzez parametry, a jedynym sposobem wydobywania z nich
informacji jest wykorzystanie wartości zwrotnych. Nie jest to cała prawda. Istnieje
jeszcze jeden sposób współdzielenia informacji między różnymi częściami programu
— wykorzystanie zmiennych globalnych.

Pojęcie zakresu
Zakresy reprezentują różne obszary programu, które są wzajemnie oddzielone.
Na przykład każda funkcja, którą definiujesz, ma swój własny zakres. To dlatego
w przypadku funkcji, które poznałeś, zmienne jednej nie są dostępne dla drugiej.
Przedstawienie wizualne naprawdę pomaga w skrystalizowaniu tego pojęcia, więc
przyjrzyj się rysunkowi 6.7.

Rysunek 6.7. Ten prosty program zawiera trzy różne zakresy:


po jednym dla każdej funkcji plus jeden zakres globalny

Na rysunku 6.7 pokazano program z trzema różnymi zakresami. Pierwszy jest zdefiniowany
przez funkcję func1(), drugi jest zdefiniowany przez funkcję func2(), a trzeci to zakres
globalny (który automatycznie występuje we wszystkich programach). W tym programie
jesteś w zakresie globalnym, kiedy nie znajdujesz się wewnątrz jednej z funkcji. Zacieniony
obszar na rysunku reprezentuje zakres globalny. Każda zmienna, którą tworzysz w zakresie
globalnym, nazywa się zmienną globalną, podczas gdy każda zmienna utworzona
wewnątrz funkcji jest nazywana zmienną lokalną (jest lokalna dla tej funkcji).
Ponieważ zmienna variable1 została zdefiniowana wewnątrz funkcji func1(), jest
zmienną lokalną, która istnieje tylko w zakresie funkcji func1(). Do zmiennej variable1
182 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

nie można uzyskać dostępu z żadnego innego zakresu. Więc żadne polecenie w funkcji
func2() nie może do niej sięgnąć i żadne polecenie w przestrzeni globalnej nie może
uzyskać do niej dostępu ani zmodyfikować jej wartości.
Dobrym sposobem na zapamiętanie, jak to działa, jest wyobrażenie sobie zakresów
jako domów, a hermetyzacji jako przyciemniane okna nadające prywatność każdemu
domowi. W rezultacie widzisz wszystko, co znajduje się wewnątrz domu, jeśli Ty sam
jesteś w środku. Lecz jeśli jesteś na zewnątrz, nie widzisz, co znajduje się wewnątrz domu.
Podobnie jest z funkcjami. Kiedy znajdujesz się w funkcji, masz dostęp do wszystkich jej
zmiennych. Ale kiedy jesteś na zewnątrz funkcji, na przykład w zakresie globalnym, nie
widzisz żadnej ze zmiennych występujących wewnątrz funkcji.
Jeśli dwie zmienne znajdujące się wewnątrz dwóch oddzielnych funkcji mają tę samą
nazwę, są to całkowicie inne zmienne, które nie mają ze sobą żadnego związku. Gdybym
na przykład utworzył zmienną o nazwie variable2 wewnątrz funkcji func1(), to nie
miałaby ona nic wspólnego ze zmienną o nazwie variable2 w funkcji func2(). Dzięki
hermetyzacji istniałyby jakby w odrębnych światach i nie miałyby wzajemnie na siebie
żadnego wpływu.
Zmienne globalne stanowią jednak małą rysę na idei hermetyzacji, jak będziesz miał
okazję zobaczyć.

Prezentacja programu Globalny zasięg


Program Globalny zasięg pokazuje, jak można odczytywać czy nawet zmieniać wartości
zmiennych globalnych z wnętrza funkcji. Na rysunku 6.8 przedstawiono wyniki
wyświetlone przez program.

Rysunek 6.8. Możesz odczytać, przesłonić, a nawet zmienić wartość zmiennej globalnej
z wnętrza funkcji

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 6.; nazwa pliku
to globalny_zasieg.py.
Wykorzystanie zmiennych globalnych i stałych 183

# Globalny zasięg
# Demonstruje zmienne globalne

def read_global():
print("Wartość zmiennej value odczytana wewnątrz zakresu lokalnego",
"\nfunkcji read_global() wynosi:", value)

def shadow_global():
value = -10
print("Wartość zmiennej value odczytana wewnątrz zakresu lokalnego",
"\nfunkcji shadow_global() wynosi:", value)

def change_global():
global value
value = -10
print("Wartość zmiennej value odczytana wewnątrz zakresu lokalnego",
"\nfunkcji change_global() wynosi:", value)

# główna część programu


# value jest zmienną globalną, ponieważ jesteśmy teraz w zakresie globalnym
value = 10
print("W zakresie globalnym wartość zmiennej value została ustawiona na:", value, "\n")

read_global()
print("Po powrocie do zakresu globalnego wartość zmiennej value nadal wynosi:", value,
"\n")

shadow_global()
print("Po powrocie do zakresu globalnego wartość zmiennej value nadal wynosi:", value,
"\n")

change_global()
print("Po powrocie do zakresu globalnego okazuje się, że wartość zmiennej value",
"\nzmieniła się na:", value)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Odczytywanie wartości zmiennej globalnej


wewnątrz funkcji
Chociaż do tej pory zdążyłeś się już zapewne dość dobrze oswoić z pojęciem hermetyzacji,
chcę Cię nieco zaskoczyć — wartość zmiennej globalnej można odczytać z dowolnego
zakresu w programie. Lecz nie obawiaj się, to wciąż pozostaje w zgodzie z koncepcją
domów i przyciemnianych okien. Pamiętaj, przyciemniane okna chronią prywatność
domów (czyli funkcji). Ale przyciemniane okna pozwalają także widzieć, co się dzieje
na zewnątrz. Zawsze więc możesz wyjrzeć na zewnątrz funkcji, na zakres globalny,
i zobaczyć wartość zmiennej globalnej. Właśnie to zrobiłem, gdy utworzyłem funkcję
read_global(). Wyświetla ona bez problemu wartość zmiennej globalnej value.
Chociaż możesz zawsze odczytać wartość zmiennej globalnej w dowolnej funkcji,
nie możesz jej bezpośrednio zmienić (przynajmniej bez wyraźnego zażądania tego typu
184 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

dostępu). Tak więc próba wykonania w funkcji read_global() czegoś takiego jak poniżej
wygenerowałaby paskudny błąd:
value += 1

W kontekście analogii z domami i przyciemnianymi szybami to oznacza, że widzisz


zmienną z wnętrza funkcji przez zaciemnione okno, ale nie możesz jej dotknąć, ponieważ
znajduje się na zewnątrz, za szybą. Więc chociaż możesz odczytać wartość zmiennej
globalnej z wnętrza funkcji, nie możesz jej jednak zmienić bez zażądania specjalnego
dostępu do niej.

Przesłonięcie zmiennej globalnej wewnątrz funkcji


Jeśli nadasz zmiennej wewnątrz funkcji taką samą nazwę, jaką ma zmienna globalna,
przesłonisz zmienną globalną. To znaczy, że ukryjesz ją za swoją nową zmienną. Mogłoby się
wydawać, że mógłbyś przez to zmienić wartość zmiennej globalnej, lecz zmieniasz tylko
wartość zmiennej lokalnej, którą utworzyłeś. Właśnie to zrobiłem w funkcji shadow_global().
Kiedy przypisałem -10 do zmiennej value za pomocą instrukcji:
value = -10

nie zmieniłem globalnej wersji zmiennej value. Natomiast utworzyłem nową lokalną
wersję zmiennej value wewnątrz funkcji, która otrzymała wartość -10. Możesz się przekonać,
że tak się stało naprawdę, ponieważ po zakończeniu wykonywania funkcji główny
program wypisuje wartość globalnej wersji zmiennej value za pomocą instrukcji:
print("Po powrocie do zakresu globalnego wartość zmiennej value nadal wynosi:", value,
"\n")

i wynosi ona nadal 10.

Pułapka
Przesłanianie zmiennej globalnej wewnątrz funkcji nie jest dobrym pomysłem.
Może doprowadzić do pomyłki. Mógłbyś myśleć, że używasz zmiennej globalnej,
a tak by w istocie nie było. Pamiętaj o każdej zmiennej globalnej występującej
w programie i nigdzie w kodzie nie używaj jej nazwy w innym znaczeniu.

Zmiana wartości zmiennej globalnej z wnętrza funkcji


Aby uzyskać całkowity dostęp do zmiennej globalnej, użyj słowa kluczowego global,
tak jak ja zrobiłem w funkcji change_global():
global value

W tym momencie funkcja ma całkowity dostęp do zmiennej value. Więc kiedy


zmieniłem jej wartość za pomocą instrukcji:
value = -10
Powrót do gry Kółko i krzyżyk 185

zmienna globalna value otrzymała wartość -10. Kiedy program wyświetla wartość value
po ponownym powrocie do głównej części kodu za pomocą instrukcji:
print("Po powrocie do zakresu globalnego okazuje się, że wartość zmiennej value",
"\nzmieniła się na:", value)

wyświetlona zostaje liczba -10. Wartość zmiennej globalnej została zmieniona z wnętrza
funkcji.

Kiedy należy używać zmiennych globalnych i stałych?


To, że coś możesz robić, nie oznacza, że powinieneś to robić. Jest to dobre motto
programowania. Czasem pewne rzeczy są technicznie możliwe do wykonania, ale nie są
dobrymi pomysłami. Przykładem tego jest korzystanie ze zmiennych globalnych.
Generalnie zmienne globalne powodują większe zagmatwanie programu, ponieważ
śledzenie ich zmieniających się wartości może okazać się trudne. Powinieneś możliwie
jak najbardziej ograniczać ich użycie.
Z drugiej strony, stałe globalne (zmienne globalne, które traktujesz jako stałe),
mogą przyczynić się do zmniejszenia zagmatwania programów. Powiedzmy na przykład,
że piszesz aplikację biznesową, która oblicza czyjeś podatki. Jako dobry programista
uwzględniłeś w swoim kodzie szereg różnych funkcji, z których wszystkie wykorzystują
nieco tajemniczą wartość .28 jako stawkę podatku. Zamiast tego mógłbyś utworzyć stałą
globalną o nazwie TAX_RATE i ustawić jej wartość na .28. Wtedy mógłbyś w każdej funkcji
zastąpić liczbę .28 nazwą TAX_RATE. To daje dwie korzyści. Sprawia, że Twój kod staje się
jaśniejszy i eliminuje trud związany z wprowadzaniem zmian (takich jak nowa stawka
podatku).

Powrót do gry Kółko i krzyżyk


Przedstawiona na początku tego rozdziału gra Kółko i krzyżyk jest jak dotychczas Twoim
najambitniejszym projektem w tym rozdziale. Z pewnością masz już wszystkie umiejętności
potrzebne do utworzenia tej gry, ale zamiast przystąpić od razu do pisania kodu,
zamierzam przejść przez etap projektowania, aby pomóc Ci w uzyskaniu ogólnego
obrazu i zrozumieniu zasad tworzenia większego programu.

Projektowanie gry Kółko i krzyżyk


Jeśli do tej pory nie doszedłeś do tego wniosku, będę znów Cię nudził tą regułą: najważniejszą
częścią programowania jest projektowanie programu. Bez mapy drogowej nigdy nie
dojedziesz tam, gdzie chcesz (lub zajmie Ci to dużo więcej czasu, gdy będziesz
podróżował trasą bogatą w piękne widoki).
186 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Zapisanie projektu w pseudokodzie


Pora na powrót do Twojego ulubionego języka, który tak naprawdę nie jest językiem —
pseudokodu. Ponieważ będę wykorzystywał funkcje do większości zadań w programie,
mogę sobie pozwolić na przemyślenie go na dość ogólnym poziomie. Każdy wiersz
pseudokodu powinien sugerować jedno wywołanie funkcji. Później będę tylko musiał
napisać funkcje, które wynikają z projektu. Oto ten pseudokod:
wyświetl instrukcję gry
ustal, do kogo należy pierwszy ruch
utwórz pustą planszę gry
wyświetl planszę
dopóki nikt nie wygrał ani nie padł remis
jeśli ruch należy do człowieka
odczytaj ruch człowieka
zaktualizuj planszę przez zaznaczenie ruchu
w przeciwnym wypadku
wyznacz ruch komputera
zaktualizuj planszę przez zaznaczenie ruchu
wyświetl planszę
zmień wykonawcę ruchu

Sposób reprezentowania danych


W porządku, mam dobry projekt, ale jest on dosyć abstrakcyjny i raz po raz odwołuje się
do różnych elementów, które w gruncie rzeczy nie zostały jeszcze w moim umyśle
zdefiniowane. Koncepcję wykonywania ruchu postrzegam jako umieszczenie żetonu
na planszy. Ale jaki konkretny sposób reprezentacji mam wybrać dla planszy?
A jak przedstawić żeton? Albo ruch?
Ponieważ zamierzam wyświetlać planszę do gry na ekranie, dlaczego miałbym nie
użyć do reprezentowania żetonu pojedynczego znaku, "X" albo "O". Pusty żeton mógłby
być po prostu spacją. Sama plansza powinna być listą, ponieważ ma się zmieniać po
wykonaniu ruchu przez każdego z graczy. Plansza gry w kółko i krzyżyk obejmuje
dziewięć pól, więc lista powinna zawierać dziewięć elementów. Każde pole planszy
powinno odpowiadać pozycji na liście reprezentującej tę planszę. Rysunek 6.9 stanowi
ilustrację mojej koncepcji.

Rysunek 6.9. Numer każdego pola odpowiada pozycji na liście reprezentującej planszę
Powrót do gry Kółko i krzyżyk 187

Więc każde pole czy też pozycja na planszy jest reprezentowana przez swój numer
z zakresu od 0 do 8. To oznacza, że lista będzie miała dziewięć elementów zajmujących
pozycje o numerach od 0 do 8. Ponieważ każdy ruch wskazuje pole, na którym należy
położyć żeton, można go również przedstawić jako numer z zakresu od 0 do 8.
Strony uczestniczące w grze (których rolę odgrywają gracz i komputer) również
mogłyby być reprezentowane przez litery "X" i "O", identycznie jak żetony używane
w grze. Także zmienna mająca reprezentować stronę, do której należy kolejka, miałaby
wartość "X" albo "O".

Utworzenie listy funkcji


Pseudokod podsuwa mi pomysły różnych funkcji, których będę potrzebował. Utworzyłem ich
listę, zastanawiając się nad tym, co mogłyby robić, jakie mogłyby mieć parametry i jakie
wartości mogłyby zwracać. Tabela 6.1 ukazuje wyniki moich wysiłków.

Tabela 6.1. Funkcje występujące w grze Kółko i krzyżyk


Funkcja Opis
display_instruct() Wyświetla instrukcję gry.
ask_yes_no(question) Zadaje pytanie, na które można odpowiedzieć „tak”
lub „nie”. Otrzymuje pytanie. Zwraca "t" lub "n".
ask_number(question, low, high) Prosi o podanie liczby z odpowiedniego zakresu.
Otrzymuje pytanie, najmniejszą poprawną wartość
liczby i największą poprawną wartość liczby. Zwraca
liczbę z zakresu od low do high.
pieces() Ustala, do kogo należy pierwszy ruch. Zwraca żeton
komputera i żeton człowieka.
new_board() Tworzy nową, pustą planszę gry. Zwraca planszę.
display_board(board) Wyświetla planszę na ekranie. Otrzymuje planszę.
legal_moves(board) Tworzy listę prawidłowych ruchów. Otrzymuje
planszę. Zwraca listę prawidłowych ruchów.
winner(board) Ustala zwycięzcę gry. Otrzymuje planszę. Zwraca
żeton, "TIE" (remis) lub None.
human_move(board, human) Odczytuje ruch człowieka podany przez gracza.
Otrzymuje planszę i żeton człowieka. Zwraca ruch
człowieka.
computer_move(board, computer, Wyznacza ruch komputera. Otrzymuje planszę, żeton
human) komputera i żeton człowieka. Zwraca ruch
komputera.
next_turn(turn) Zmienia stronę wykonującą ruch na podstawie
bieżącej kolejki. Otrzymuje żeton. Zwraca żeton.
congrat_winner(the_winner, Gratuluje zwycięzcy lub ogłasza remis. Otrzymuje
computer, human) żeton zwycięzcy, żeton komputera i żeton człowieka.
188 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Skonfigurowanie programu
Pierwszą czynnością, jaką wykonałem w trakcie pisania programu, było zdefiniowanie
kilku stałych globalnych. Chodzi tu wartości, które będą wykorzystywane przez więcej
niż jedną funkcję. Ich utworzenie sprawi, że funkcje będą bardziej przejrzyste,
a jakiekolwiek zmiany dotyczące tych wartości łatwiejsze. Kod tego programu możesz
znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 6.; nazwa pliku to kolko_i_krzyzyk.py.
# Kółko i krzyżyk
# Komputer gra w kółko i krzyżyk przeciwko człowiekowi

# stałe globalne
X = "X"
O = "O"
EMPTY = " "
TIE = "TIE"
NUM_SQUARES = 9

Stała X to krótka nazwa łańcucha "X" odgrywającego w grze rolę jednego z dwóch
żetonów. Stała O reprezentuje "O" — drugi żeton wykorzystywany w grze. Stała EMPTY
reprezentuje puste pole na planszy. Jej wartością jest spacja, ponieważ po wyświetleniu
tego znaku pole wygląda tak, jakby było puste. Stała TIE reprezentuje wynik remisowy.
A wartość stałej NUM_SQUARES jest równa liczbie pól na planszy.

Funkcja display_instruct()
Funkcja ta wyświetla instrukcję gry. Miałeś okazję poznać ją już wcześniej:
def display_instruct():
"""Wyświetl instrukcję gry."""
print(
"""
Witaj w największym intelektualnym wyzwaniu wszech czasów, jakim jest
gra 'Kółko i krzyżyk'. Będzie to ostateczna rozgrywka między Twoim
ludzkim mózgiem a moim krzemowym procesorem.

Swoje posunięcie wskażesz poprzez wprowadzenie liczby z zakresu 0 - 8.


Liczba ta odpowiada pozycji na planszy zgodnie z poniższym schematem:

0 | 1 | 2
---------
3 | 4 | 5
---------
6 | 7 | 8

Przygotuj się, Człowieku. Ostateczna batalia niebawem się rozpocznie. \n


"""
)

Jedyna rzecz, jaką zrobiłem, jest zmiana nazwy funkcji ze względu na zachowanie
konsekwencji w programie.
Powrót do gry Kółko i krzyżyk 189

Funkcja ask_yes_no()
Ta funkcja zadaje pytanie, na które można odpowiedzieć „tak” lub „nie”. Odbiera odpowiedź i
zwraca wartość "t" lub "n". Z tą funkcją również spotkałeś się już wcześniej:
def ask_yes_no(question):
"""Zadaj pytanie, na które można odpowiedzieć tak lub nie."""
response = None
while response not in ("t", "n"):
response = input(question).lower()
return response

Funkcja ask_number()
Ta funkcja prosi o podanie liczby z pewnego zakresu. W postaci argumentów wywołania
otrzymuje treść pytania oraz najmniejszą i największą dopuszczalną wartość liczby.
Zwraca liczbę z określonego zakresu.
def ask_number(question, low, high):
"""Poproś o podanie liczby z odpowiedniego zakresu."""
response = None
while response not in range(low, high):
response = int(input(question))
return response

Funkcja pieces()
Funkcja ta pyta gracza, czy chce wykonywać pierwszy ruch, i na podstawie jego decyzji
zwraca żeton komputera oraz żeton człowieka. Zgodnie z tym, co dyktuje wielka tradycja
gry w kółko i krzyżyk, pierwszy ruch należy do właściciela żetonu X.
def pieces():
"""Ustal, czy pierwszy ruch należy do gracza, czy do komputera."""
go_first = ask_yes_no("Czy chcesz mieć prawo do pierwszego ruchu? (t/n): ")
if go_first == "t":
print("\nWięc pierwszy ruch należy do Ciebie. Będzie Ci potrzebny.")
human = X
computer = O
else:
print("\nTwoja odwaga Cię zgubi... Ja wykonuję pierwszy ruch.")
computer = X
human = O
return computer, human

Zwróć uwagę, że ta funkcja wywołuje inną z moich funkcji, ask_yes_no().


Wszystko jest w jak najlepszym porządku. Jedna funkcja może wywoływać drugą.

Funkcja new_board()
Ta funkcja tworzy nową planszę (w postaci listy), której wszystkie elementy mają
przypisaną wartość EMPTY, i zwraca ją:
190 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

def new_board():
"""Utwórz nową planszę gry."""
board = []
for square in range(NUM_SQUARES):
board.append(EMPTY)
return board

Funkcja display_board()
Ta funkcja wyświetla planszę przekazaną do niej jako argument. Ponieważ każdy element
planszy jest albo spacją, albo znakiem "X", albo znakiem "O", funkcja może wyświetlić
każdy z nich. Kilka innych znaków dostępnych na klawiaturze zostało użytych
do narysowania przyzwoicie wyglądającej planszy do gry w kółko i krzyżyk.
def display_board(board):
"""Wyświetl planszę gry na ekranie."""
print("\n\t", board[0], "|", board[1], "|", board[2])
print("\t", "---------")
print("\t", board[3], "|", board[4], "|", board[5])
print("\t", "---------")
print("\t", board[6], "|", board[7], "|", board[8], "\n")

Funkcja legal_moves()
Ta funkcja otrzymuje planszę poprzez swój parametr i zwraca listę prawidłowych ruchów.
Jest ona wykorzystywana przez inne funkcje. Funkcja human_move() używa jej do sprawdzenia,
czy gracz wybrał prawidłowy ruch. Korzysta z niej także funkcja computer_move(),
aby sprawić, że komputer będzie mógł rozpatrywać tylko prawidłowe ruchy w swoim
procesie podejmowania decyzji.
Prawidłowy ruch jest reprezentowany przez numer pustego pola. Gdyby na przykład
pole centralne było otwarte, prawidłowym ruchem byłoby 4. Jeśli otwarte byłyby tylko
pola narożne, lista prawidłowych ruchów wyglądałaby następująco: [0, 2, 6, 8].
(Jeśli nie jest to dla Ciebie jasne, zerknij na rysunek 6.9).
Więc funkcja ta przegląda w pętli listę reprezentującą planszę. Ilekroć znajdzie puste
pole, dodaje jego numer do listy prawidłowych ruchów. Na końcu zwraca gotową listę
prawidłowych posunięć.
def legal_moves(board):
"""Utwórz listę prawidłowych ruchów."""
moves = []
for square in range(NUM_SQUARES):
if board[square] == EMPTY:
moves.append(square)
return moves
Powrót do gry Kółko i krzyżyk 191

Funkcja winner()
Ta funkcja otrzymuje w wywołaniu planszę i zwraca wartość określającą zwycięzcę.
Możliwe są cztery takie wartości. Funkcja zwraca X lub O, jeśli jeden z graczy zwyciężył
w grze. Jeśli wszystkie pola zostały zapełnione i nikt nie wygrał, zostaje zwrócona wartość
TIE. Wreszcie jeśli nikt nie wygrał oraz istnieje przynajmniej jedno puste pole, funkcja
zwraca wartość None.
Pierwszą rzeczą, jaką robię w tej funkcji, jest zdefiniowanie stałej o nazwie WAYS_TO_WIN,
która reprezentuje osiem sposobów ustawienia trzech żetonów w jednym rzędzie. Każdy
sposób na zwycięstwo jest reprezentowany przez krotkę. Każda krotka to ciąg trzech
pozycji planszy tworzących jeden rząd, których zajęcie przez jedną ze stron gry daje
zwycięstwo. Weźmy pod uwagę pierwszą krotkę w sekwencji: (0, 1, 2). Reprezentuje
ona górny rząd planszy: pozycje 0, 1 i 2. Następna krotka (3, 4, 5) reprezentuje rząd
środkowy. I tak dalej.
def winner(board):
"""Ustal zwycięzcę gry."""
WAYS_TO_WIN = ((0, 1, 2),
(3, 4, 5),
(6, 7, 8),
(0, 3, 6),
(1, 4, 7),
(2, 5, 8),
(0, 4, 8),
(2, 4, 6))
Następnie wykorzystuję pętlę for do przejścia przez wszystkie możliwe sposoby
uzyskania przez gracza zwycięstwa, aby sprawdzić, czy któryś z graczy nie ustawił trzech
swoich żetonów w jednym rzędzie. Instrukcja if sprawdza, czy dane trzy pola tworzące
jeden rząd zawierają taką samą wartość i czy nie są puste. Jeśli tak jest w istocie, oznacza
to, że rząd zawiera albo trzy znaki X, albo trzy znaki O, a więc ktoś został zwycięzcą.
Komputer przypisuje jeden z żetonów wygrywającej trójki do zmiennej winner, zwraca jej
wartość i kończy wykonywanie funkcji.
for row in WAYS_TO_WIN:
if board[row[0]] == board[row[1]] == board[row[2]] != EMPTY:
winner = board[row[0]]
return winner
Jeśli żaden z graczy nie wygrał, wykonywanie funkcji jest kontynuowane. W następnej
kolejności sprawdza ona, czy na planszy nie pozostały jakieś puste pola. Jeśli takich
nie ma, w grze padł remis (ponieważ funkcja już wcześniej, wykonując pętlę for ustaliła,
że nie ma zwycięzcy) i zostaje zwrócona wartość TIE.
if EMPTY not in board:
return TIE
Jeśli gra nie skończyła się remisem, funkcja wykonuje się dalej. Ostatecznie, jeśli
żaden z graczy nie zwyciężył i gra nie zakończyła się remisem, to wynik gry pozostaje
otwarty. Więc funkcja zwraca wartość None.
return None
192 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

Funkcja human_move()
Ta kolejna funkcja otrzymuje w wywołaniu planszę i żeton człowieka. Zwraca numer
pola, w którym gracz chce umieścić swój żeton.
Najpierw funkcja pobiera listę wszystkich prawidłowych ruchów dla tej planszy.
Następnie kontynuuje proszenie użytkownika o wprowadzenie numeru pola, w którym
chce umieścić swój żeton, dopóki jego (lub jej) odpowiedź nie będzie się zawierać na
liście prawidłowych ruchów. Po uzyskaniu prawidłowej odpowiedzi funkcja zwraca ten
ruch (numer pola).
def human_move(board, human):
"""Odczytaj ruch człowieka."""
legal = legal_moves(board)
move = None
while move not in legal:
move = ask_number("Jaki będzie Twój ruch? (0 - 8):", 0, NUM_SQUARES)
if move not in legal:
print("\nTo pole jest już zajęte, niemądry Człowieku. Wybierz inne.\n")
print("Znakomicie...")
return move

Funkcja computer_move()
Funkcja computer_move() otrzymuje w formie argumentów planszę, żeton komputera
oraz żeton człowieka. Zwraca ruch komputera.

Sztuczka
Jest to zdecydowanie najbardziej treściwa funkcja w programie. Wiedząc, że tak
będzie, utworzyłem najpierw krótką, tymczasową funkcję, która wybiera losowe,
niemniej prawidłowe posunięcie. Potrzebowałem czasu, aby przemyśleć tę funkcję,
lecz nie chciałem spowalniać postępu całego projektu. Więc wstawiłem do programu
funkcję tymczasową i uzyskałem działającą grę. Później wróciłem do tej kwestii
i dodałem lepszą funkcję, która rzeczywiście wybiera posunięcia w racjonalny sposób.
Dysponowałem tą elastycznością z powodu modularności projektu osiągniętej
dzięki zastosowaniu w nim funkcji. Wiedziałem, że funkcja computer_move() była
całkowicie niezależnym składnikiem i mogła być później zastąpiona bez problemu.
W gruncie rzeczy mógłbym nawet wstawić nową funkcję właśnie teraz, taką, która
wybiera jeszcze lepsze ruchy. (Wygląda to obecnie na strasznie trudne wyzwanie,
prawda?).

Muszę być ostrożny w tym miejscu, ponieważ plansza (lista) jest mutowalna, a ja
zmieniam ją w tej funkcji w trakcie szukania najlepszego ruchu komputera. Problem, jaki
stąd wynika, polega na tym, że każda zmiana, jaką wprowadzę w planszy, znajdzie swoje
odbicie w tej części programu, która wywołała tę funkcję. Jest to efekt referencji
współdzielonych, o których dowiedziałeś się w rozdziale 5., w podrozdziale „Referencje
współdzielone”. Zasadniczo istnieje tylko jeden egzemplarz tej listy i jakakolwiek zmiana,
której dokonuję w tym miejscu, odnosi się do tego pojedynczego egzemplarza. Więc
pierwszą rzeczą, jaką robię, jest wykonanie swojej własnej lokalnej kopii roboczej:
Powrót do gry Kółko i krzyżyk 193

def computer_move(board, computer, human):


"""Spowoduj wykonanie ruchu przez komputer."""
# utwórz kopię roboczą, ponieważ funkcja będzie zmieniać listę
board = board[:]

Wskazówka
Zawsze, gdy wartość mutowalna zostaje przekazana do funkcji, musisz zachować
ostrożność. Jeśli wiesz, że możesz zmienić tę wartość w trakcie jej wykorzystywania,
utwórz jej kopię i używaj tej kopii.

Pułapka
Mógłbyś pomyśleć, że wprowadzenie zmiany w planszy byłoby dobrym rozwiązaniem.
Mógłbyś zmienić ją tak, aby zawierała nowy ruch komputera. W ten sposób nie
musiałbyś przesyłać planszy z powrotem jako wartości zwrotnej.
Tego typu bezpośrednia zmiana parametru mutowalnego jest traktowana jako
tworzenie skutku ubocznego (ang. side effect). Nie wszystkie skutki uboczne są
złe, ale ten ich rodzaj jest na ogół źle widziany (nawet w tej chwili marszczą mi
się brwi, gdy tylko o tym pomyślę). Najlepiej komunikować się z pozostałą częścią
programu poprzez wartości zwrotne; w ten sposób wyraźnie widać, jakie dokładnie
informacje są przekazywane z powrotem.

W porządku, oto podstawowa strategia, jaką wymyśliłem dla komputera:


1. Jeśli istnieje ruch, który pozwala komputerowi wygrać w tej kolejce, komputer
powinien wybrać ten ruch.
2. Jeśli istnieje ruch, który pozwoli człowiekowi wygrać w następnej kolejce,
komputer powinien go zablokować.
3. W przeciwnym razie komputer powinien wybrać najlepsze puste pole jako swój
ruch. Najlepszym polem jest środek planszy. Kolejne najlepsze pola to rogi.
Potem przychodzi kolej na pozostałe pola.
Więc w kolejnym fragmencie kodu definiuję krotkę, która ma reprezentować
najlepsze pola w odpowiedniej kolejności:
# najlepsze pozycje do zajęcia według kolejności
BEST_MOVES = (4, 0, 2, 6, 8, 1, 3, 5, 7)

print("Wybieram pole numer", end=" ")

Następnie tworzę listę wszystkich prawidłowych ruchów. Działając w pętli, umieszczam


na próbę żeton komputera w każdym pustym polu, którego numer pobrałem z listy
prawidłowych ruchów, i sprawdzam, czy ten ruch nie daje komputerowi zwycięstwa.
Jeśli tak się dzieje, jest to ruch, który należy wykonać. W takim przypadku funkcja zwraca
ten ruch i kończy swoje działanie. W przeciwnym razie wycofuję ten ruch i przechodzę
do następnego elementu listy.
# jeśli komputer może wygrać, wykonaj ten ruch
for move in legal_moves(board):
194 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

board[move] = computer
if winner(board) == computer:
print(move)
return move
# ten ruch został sprawdzony, wycofaj go
board[move] = EMPTY

Dotarcie do tego miejsca w funkcji oznacza, że komputer nie może wygrać w swoim
kolejnym posunięciu. Więc sprawdzam, czy w swoim następnym ruchu może zwyciężyć
gracz. Kod pobiera w pętli kolejne elementy listy prawidłowych ruchów, umieszczając
żeton człowieka w każdym po kolei pustym polu i sprawdzając możliwość zwycięstwa
człowieka. Jeśli okaże się, że testowany ruch daje człowiekowi zwycięstwo, komputer
powinien go wykonać jako pierwszy, blokując tę możliwość. W tym przypadku funkcja
zwraca ten ruch i kończy swoje działanie. W przeciwnym razie wycofuję ten ruch
i sprawdzam kolejny prawidłowy ruch z listy.
# jeśli człowiek może wygrać, zablokuj ten ruch
for move in legal_moves(board):
board[move] = human
if winner(board) == human:
print(move)
return move
# ten ruch został sprawdzony, wycofaj go
board[move] = EMPTY

Z tego, że dotarłem do tego miejsca funkcji, wynika, że żadna strona nie może wygrać
w swoim następnym ruchu. Więc przeglądam listę najlepszych posunięć i wybieram
z niej pierwsze prawidłowe. Komputer pobiera w pętli kolejne elementy krotki BEST_MOVES
i gdy tylko znajdzie taki, który jest prawidłowym ruchem, zwraca jego wartość.
# ponieważ nikt nie może wygrać w następnym ruchu, wybierz najlepsze wolne pole
for move in BEST_MOVES:
if move in legal_moves(board):
print(move)
return move

W świecie rzeczywistym
Program Kółko i krzyżyk rozpatruje tylko następny możliwy ruch w grze.
Programy, które obsługują poważne gry strategiczne, takie jak szachy, analizują
konsekwencje poszczególnych ruchów dużo głębiej, rozpatrując wiele
poziomów posunięć i kontrposunięć. Dzisiejsze komputery mogą sprawdzić
olbrzymią liczbę pozycji w grze. Wyspecjalizowane maszyny, takie jak komputer
do gry w szachy firmy IBM, o nazwie Deep Blue, który pokonał mistrza świata
Garriego Kasparowa, mogą sprawdzić ich dużo więcej. Deep Blue potrafi
zbadać ponad 200 000 000 pozycji na szachownicy w ciągu sekundy.
Wygląda to całkiem imponująco, dopóki nie uświadomisz sobie, że ogólna liczba
pozycji na szachownicy została w całościowej analizie szachów oceniona na ponad
100 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000,
co oznacza, że przejrzenie tych wszystkich możliwych pozycji zajęłoby
komputerowi Deep Blue ponad 1 585 489 599 188 229 lat. (nawiasem
mówiąc, wiek wszechświata jest szacowany na jedyne 15 000 000 000 lat).
Powrót do gry Kółko i krzyżyk 195

Funkcja next_turn()
Funkcja ta otrzymuje jako argument żeton bieżącej kolejki i zwraca żeton następnej kolejki.
Żeton kolejki reprezentuje stronę, do której należy ruch, i przyjmuje wartość X lub O.
def next_turn(turn):
"""Zmień wykonawcę ruchu."""
if turn == X:
return O
else:
return X

Powyższa funkcja jest wykorzystywana do zmiany autora kolejnego ruchu po tym,


jak jeden z graczy wykonał swój ruch.

Funkcja congrat_winner
Ta funkcja otrzymuje jako argumenty wywołania żetony: zwycięzcy gry, komputera
i człowieka. Jest wywoływana tylko wtedy, gdy gra zostaje zakończona, więc parametrowi
the_winner zostanie przekazana wartość X lub O, jeśli jeden z graczy zwyciężył w grze,
albo TIE, jeśli gra zakończyła się remisem.
def congrat_winner(the_winner, computer, human):
"""Pogratuluj zwycięzcy."""
if the_winner != TIE:
print(the_winner, "jest zwycięzcą!\n")
else:
print("Remis!\n")

if the_winner == computer:
print("Jak przewidywałem, Człowieku, jeszcze raz zostałem triumfatorem. \n" \
"Dowód na to, że komputery przewyższają ludzi pod każdym względem.")

elif the_winner == human:


print("No nie! To niemożliwe! Jakoś udało Ci się mnie zwieść, Człowieku. \n" \
"Ale to się nigdy nie powtórzy! Ja, komputer, przyrzekam Ci to!")

elif the_winner == TIE:


print("Miałeś mnóstwo szczęścia, Człowieku, i jakoś udało Ci się ze mną " \
"zremisować. \nŚwiętuj ten dzień... bo to najlepszy wynik, jaki możesz " \
"kiedykolwiek osiągnąć.")

Funkcja main()
Główną część programu umieściłem w jej własnej funkcji, zamiast pozostawić ją na poziomie
globalnym. To hermetyzuje także i główny kod. Z wyjątkiem sytuacji, gdy piszesz prosty,
krótki program, hermetyzacja nawet głównej jego części jest zwykle dobrym pomysłem.
Jeśli umieścisz swój główny kod w funkcji takiej jak ta, nie musisz nazywać jej main()1.

1
W tłumaczeniu na język polski: główna — przyp. tłum.
196 Rozdział 6. Funkcje. Gra Kółko i krzyżyk

W tej nazwie nie ma nic magicznego. Ale ponieważ jest to dość często spotykana
praktyka, dobrze się do niej stosować.
W porządku, oto kod głównej części programu. Jak możesz się przekonać, prawie
dokładnie, wiersz w wiersz, odpowiada on pseudokodowi, który napisałem wcześniej:
def main():
display_instruct()
computer, human = pieces()
turn = X
board = new_board()
display_board(board)

while not winner(board):


if turn == human:
move = human_move(board, human)
board[move] = human
else:
move = computer_move(board, computer, human)
board[move] = computer
display_board(board)
turn = next_turn(turn)

the_winner = winner(board)
congrat_winner(the_winner, computer, human)

Rozpoczęcie programu
Kolejny wiersz kodu wywołuje główną funkcję (która z kolei wywołuje pozostałe funkcje)
z poziomu globalnego:
# rozpocznij program
main()
input("\n\nAby zakończyć grę, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale nauczyłeś się pisać swoje własne funkcje. Zobaczyłeś następnie,
jak przyjmować i zwracać wartości w swoich funkcjach. Dowiedziałeś się o zakresach
i zobaczyłeś, jak można uzyskiwać z wnętrza funkcji dostęp do zmiennych globalnych
i zmieniać ich wartość. Nauczyłeś się również ograniczać wykorzystywanie zmiennych
globalnych, lecz zobaczyłeś też, jak korzystać ze stałych globalnych, jeśli jest to konieczne.
Otarłeś się nawet troszeczkę o pewne koncepcje z dziedziny sztucznej inteligencji,
tworząc komputerowego przeciwnika w grze strategicznej.
Podsumowanie 197

Sprawdź swoje umiejętności


1. Popraw funkcję ask_number() w taki sposób, aby mogła być wywoływana
z wartością kroku. Spraw, aby domyślną wartością kroku była liczba 1.
2. Zmodyfikuj projekt Jaka to liczba? z rozdziału 3. przez użycie w nim funkcji
ask_number().
3. Zmodyfikuj nową wersję gry Jaka to liczba?, którą utworzyłeś w ramach
poprzedniego zadania, tak aby kod programu znalazł się w funkcji o nazwie
main(). Nie zapomnij o wywołaniu funkcji main() w celu uruchomienia gry.
4. Napisz nową funkcję computer_move() do gry Kółko i krzyżyk, aby zlikwidować
lukę w strategii komputera. Sprawdź, czy potrafisz utworzyć przeciwnika, który
będzie nie do pobicia.
198 Rozdział 6. Funkcje. Gra Kółko i krzyżyk
7
Pliki i wyjątki.
Gra Turniej wiedzy

Z mienne stanowią znakomity sposób przechowywania informacji i uzyskiwania do


nich dostępu w trakcie wykonywania programu, lecz często będziesz chciał zapisać
dane w taki sposób, aby można je było później odzyskać. W tym rozdziale dowiesz się,
jak wykorzystywać pliki do tego rodzaju trwałego przechowywania danych. Dowiesz się
również, jak obsługiwać błędy, jakie Twój kod może wygenerować. W szczególności
nauczysz się:
 odczytywać dane z plików tekstowych,
 zapisywać informacje w plikach tekstowych,
 odczytywać i zapisywać bardziej złożone dane z wykorzystaniem plików,
 przechwytywać i obsługiwać błędy podczas wykonywania programu.

Wprowadzenie do programu Turniej wiedzy


Gra Turniej wiedzy sprawdza wiedzę gracza za pomocą serii pytań wielokrotnego
wyboru. Gra udostępnia pytania w formie pojedynczego „odcinka”. Odcinek, który
utworzyłem w celu zademonstrowania programu, dotyczy mafii i nazywa się „Odcinkiem
nie do odrzucenia”. Wszystkie występujące w nim pytania odnoszą się w jakiś sposób
do mafii (choć czasem w nieco pośredni sposób).
Znakomitą stroną tej gry jest to, że pytania określonego odcinka są przechowywane
w odrębnym pliku, niezależnie od kodu gry. W ten sposób łatwo jest wykorzystywać
w grze różne odcinki. Oznacza to nawet więcej — każdy, kto dysponuje edytorem tekstu
(takim jak Notatnik na maszynach z systemem Windows), może tworzyć swoje własne
odcinki konkursu wiedzy dotyczące dowolnie wybranego tematu, od anime do zoologii.
Rysunek 7.1 pokazuje grę (i mój odcinek) w działaniu.
200 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Rysunek 7.1. Gracz ma zawsze do wyboru cztery kuszące odpowiedzi,


ale tylko jedna z nich jest poprawna

Odczytywanie danych z plików tekstowych


Odczytywanie łańcuchów znaków ze zwykłych plików tekstowych — plików, które
składają się z samych znaków ASCII — jest proste. (Chociaż istnieją różne typy plików
tekstowych, gdy używam terminu „plik tekstowy”, mam na myśli zwykły plik tekstowy).
Pliki tekstowe stanowią dobry wybór w sytuacji, gdy chcesz przechowywać proste
informacje z jakichś powodów. Plik tekstowy na maszynie z systemem Windows jest
takim samym plikiem tekstowym na komputerze Mac oraz takim samym plikiem
tekstowym w systemie Unix. Po drugie, pliki tekstowe są łatwe w użyciu. Większość
systemów operacyjnych jest wyposażona w podstawowe narzędzia służące do ich
przeglądania i edytowania.

Prezentacja programu Odczytaj to


Program Odczytaj to demonstruje kilka sposobów odczytywania łańcuchów znaków
z pliku tekstowego. Program pokazuje, jak można odczytać cokolwiek, zaczynając od
pojedynczego znaku, a kończąc na całym pliku. Przedstawia również różne sposoby
odczytywania danych po jednym wierszu na raz. Został zilustrowany na rysunku 7.2.
Program odczytuje dane z prostego pliku tekstowego, który utworzyłem w swoim
systemie przy użyciu edytora tekstu. Ten plik możesz znaleźć na stronie internetowej tej
książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 7.; nazwa pliku
to odczytaj_to.txt. Oto zawartość pliku:
Wiersz 1
To jest wiersz 2
Ten tekst tworzy wiersz 3
Odczytywanie danych z plików tekstowych 201

Rysunek 7.2. Ten plik jest odczytywany przy użyciu kilku różnych technik

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 7.; nazwa pliku
to odczytaj_to.py.
# Odczytaj to
# Demonstruje odczytywanie danych z pliku tekstowego

print("Otwarcie i zamknięcie pliku.")


text_file = open("odczytaj_to.txt", "r")
text_file.close()

print("\nOdczytywanie znaków z pliku.")


text_file = open("odczytaj_to.txt", "r")
print(text_file.read(1))
print(text_file.read(7))
text_file.close()

print("\nOdczytanie całego pliku za jednym razem.")


text_file = open("odczytaj_to.txt", "r")
whole_thing = text_file.read()
print(whole_thing)
202 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

text_file.close()

print("\nOdczytywanie znaków z wiersza.")


text_file = open("odczytaj_to.txt", "r")
print(text_file.readline(1))
print(text_file.readline(7))
text_file.close()

print("\nOdczytywanie po jednym wierszu na raz.")


text_file = open("odczytaj_to.txt", "r")
print(text_file.readline())
print(text_file.readline())
print(text_file.readline())
text_file.close()

print("\nWczytanie całego pliku do listy.")


text_file = open("odczytaj_to.txt", "r")
lines = text_file.readlines()
print(lines)
print(len(lines))
for line in lines:
print(line)
text_file.close()

print("\nPobieranie zawartości pliku wiersz po wierszu przy użyciu pętli.")


text_file = open("odczytaj_to.txt", "r")
for line in text_file:
print(line)
text_file.close()

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Pokażę Ci dokładnie, jak ten kod działa, poprzez sesję interaktywną.

Otwarcie i zamknięcie pliku


Zanim będziesz mógł odczytywać dane z pliku (lub zapisywać je do pliku), musisz go
otworzyć. To pierwsza czynność, jaką wykonałem w programie Odczytaj to:
>>> text_file = open("odczytaj_to.txt", "r")

Używam funkcji open() do otwarcia pliku i przypisania wyniku operacji do zmiennej


text_file. W wywołaniu funkcji podaje dwa argumenty w postaci łańcuchów znaków:
nazwę pliku i tryb dostępu.
Argument z nazwą pliku "odczytaj_to.txt" jest dość oczywisty. Ponieważ nie
zawarłem w nim żadnej informacji o ścieżce dostępu, Python szuka pliku w katalogu
bieżącym. Mogę uzyskać dostęp do pliku znajdującego się w dowolnym katalogu poprzez
podanie właściwej informacji o ścieżce.
Następnie podaję "r" jako tryb dostępu, informując Pythona, że chcę otworzyć ten
plik do odczytu. Można otworzyć plik do odczytu, do zapisu lub do odczytu i zapisu.
Odczytywanie danych z plików tekstowych 203

W tabeli 7.1 znajduje się opis wybranych prawidłowych trybów dostępu do pliku
tekstowego.

Tabela 7.1. Wybrane tryby dostępu do pliku tekstowego


Tryb Opis
"r" Odczyt danych z pliku tekstowego. Jeśli plik nie istnieje, Python zasygnalizuje
błąd.
"w" Zapis danych do pliku tekstowego. Jeśli plik już istnieje, jego zawartość
zostaje zastąpiona przez nowe dane. Jeśli nie istnieje, zostaje utworzony.
"a" Dopisanie danych na końcu pliku tekstowego. Jeśli plik istnieje, nowe dane
zostają do niego dopisane. Jeśli plik nie istnieje, jest tworzony.
"r+" Odczyt i zapis danych z (do) pliku tekstowego. Jeśli plik nie istnieje, Python
zasygnalizuje błąd.
"w+" Zapis i odczyt danych do (z) pliku tekstowego. Jeśli plik istnieje, jego zawartość
zostanie zastąpiona nowymi danymi. Jeśli nie istnieje, zostanie utworzony.
"a+" Dopisywanie i odczyt danych do (z) pliku tekstowego. Jeśli plik istnieje, nowe
dane są dopisywane na jego końcu. Jeśli plik nie istnieje, zostanie utworzony.

Po otwarciu pliku mam do niego dostęp poprzez zmienną text_file, która


reprezentuje obiekt pliku. Istnieje wiele użytecznych metod obiektu pliku, które mogę
wywoływać, lecz najprostsza z nich jest metoda close(), która zamyka plik, uniemożliwiając
ewentualne dalsze operacje odczytu czy zapisu, dopóki plik nie zostanie otwarty
ponownie. Właśnie to robię w swoim programie w następnej kolejności:
>>> text_file.close()

Cokolwiek zrobisz z plikiem, jego zamknięcie jest dobrą praktyką w programowaniu.

Odczytywanie znaków z pliku


Żeby mieć z pliku jakiś pożytek, musisz coś zrobić z jego zawartością między jego
otwarciem i zamknięciem. Więc w następnej kolejności otwieram plik i odczytuję jego
zawartość za pomocą metody obiektu pliku read(). Metoda read() umożliwia Ci odczytanie
z pliku określonej liczby znaków, które zostają zwrócone w postaci łańcucha. Po ponownym
otwarciu pliku odczytuję z niego dokładnie jeden znak i wyświetlam go:
>>> text_file = open("odczytaj_to.txt", "r")
>>> print(text_file.read(1))
W

Muszę jedynie podać w nawiasach liczbę znaków. Następnie odczytuję i wyświetlam


kolejne siedem znaków:
>>> print(text_file.read(7))
iersz 1
204 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Zauważ, że odczytuję siedem znaków następujących po literze "W". Python pamięta,


w którym miejscu zakończyłem poprzedni odczyt. To tak, jakby komputer umieszczał
w pliku zakładkę — każda kolejna operacja read() rozpoczyna swoje działanie w miejscu,
w którym zakończyła się poprzednia. Kiedy odczytasz wszystkie znaki do końca pliku,
kolejne odczyty zwrócą pusty łańcuch.
Aby rozpocząć odczytywanie z powrotem od początku pliku, możesz go zamknąć
i otworzyć ponownie. Właśnie to zrobiłem w następnej kolejności:
>>> text_file.close()
>>> text_file = open("odczytaj_to.txt", "r")

Jeśli nie podasz liczby znaków do odczytania, Python zwróci cały plik jako jeden
łańcuch znaków. Następnie więc odczytuję całą zawartość pliku, przypisuję zwrócony
łańcuch znaków do zmiennej i wyświetlam jej wartość:
>>> whole_thing = text_file.read()
>>> print(whole_thing)
Wiersz 1
To jest wiersz 2
Ten tekst tworzy wiersz 3

Jeśli plik jest dość mały, odczytanie go od razu w całości może mieć sens. Ponieważ
odczytałem zawartość całego pliku, jakiekolwiek kolejne odczyty zwrócą jedynie pusty
łańcuch. Więc zamykam plik ponownie:
>>> text_file.close(

Odczytywanie znaków z wiersza


Często będziesz chciał przetwarzać dane pliku tekstowego po jednym wierszu. Metoda
readline() umożliwia odczytywanie znaków z bieżącego wiersza. Przekazujesz tylko
liczbę znaków, które chcesz odczytać z bieżącego wiersza, a metoda zwraca je w postaci
łańcucha. Jeśli nie przekażesz liczby znaków, metoda odczyta wszystkie znaki od pozycji
bieżącej do końca wiersza. Gdy już odczytasz wszystkie znaki wiersza, wierszem bieżącym
staje się kolejny wiersz pliku. Po ponownym otwarciu pliku odczytuję pierwszy znak
bieżącego wiersza:
>>> text_file = open("odczytaj_to.txt", "r")
>>> print(text_file.readline(1))
W

Następnie odczytuję kolejne siedem znaków z bieżącego wiersza:


>>> print(text_file.readline(7))
iersz 1
>>> text_file.close()

W tym momencie może się wydawać, że metoda readline() niczym się nie różni
od read(), lecz readline() odczytuje znaki tylko z bieżącego wiersza, podczas gdy
metoda read() odczytuje znaki z całego pliku. Z tego powodu metoda readline()
jest zwykle wywoływana w celu odczytania jednego wiersza tekstu na raz:
Odczytywanie danych z plików tekstowych 205

>>> text_file = open("odczytaj_to.txt", "r")


>>> print(text_file.readline())
Wiersz 1

>>> print(text_file.readline())
To jest wiersz 2

>>> print(text_file.readline())
Ten tekst tworzy wiersz 3

>>> text_file.close()

Zwróć uwagę, że po każdym wyświetlonym wierszu pojawia się pusta linia.


To dlatego, że każdy wiersz w pliku tekstowym kończy się znakiem nowej linii ("\n").

Wczytywanie wszystkich wierszy do listy


Inny rodzaj obsługi pojedynczych wierszy tekstu udostępnia metoda readlines(), która
wczytuje zawartość pliku tekstowego do listy, w której każdy wiersz pliku staje się jako
łańcuch znaków elementem listy. Jako następną wywołuję metodę readlines():
>>> text_file = open("odczytaj_to.txt", "r")
>>> lines = text_file.readlines()

Zmienna lines odwołuje się teraz do listy, której elementami są wszystkie wiersze
zawarte w pliku tekstowym:
>>> print(lines)
['Wiersz 1\n', 'To jest wiersz 2\n', 'Ten tekst tworzy wiersz 3\n']

Lista lines nie różni się od innych list. Możesz znaleźć jej długość, a nawet
przetwarzać jej elementy w pętli:
>>> print(len(lines))
3
>>> for line in lines:
print(line)

Wiersz 1

To jest wiersz 2

Ten tekst tworzy wiersz 3

>>> text_file.close()

Odczytywanie zawartości pliku w pętli


Można także zbudować pętlę opartą bezpośrednio na wierszach pliku, wykorzystując
w tym celu instrukcję for:
>>> text_file = open("odczytaj_to.txt", "r")
>>> for line in text_file:
print(line)
206 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Wiersz 1

To jest wiersz 2

Ten tekst tworzy wiersz 3

>>> text_file.close()

Jak widzisz, zmienna pętli (w tym kodzie line) otrzymuje, jako swoją wartość, każdy
po kolei wiersz pliku. Przy pierwszej iteracji pętli „pobiera” pierwszy wiersz, przy drugiej
iteracji — drugi wiersz itd. Ta technika jest najbardziej eleganckim rozwiązaniem
w sytuacji, gdy chcesz przetwarzać zawartość pliku po jednym wierszu na raz.

Zapisywanie danych do pliku tekstowego


Aby pliki tekstowe mogły stanowić realną formę przechowywania danych, musisz
mieć możliwość umieszczania w nich informacji. W przypadku Pythona zapisywanie
łańcuchów znaków do plików tekstowych jest również prostą sprawą. Zademonstruję
dwa podstawowe sposoby realizacji tego zadania.

Prezentacja programu Zapisz to


Program Zapisz to tworzy plik tekstowy o takiej samej zawartości, jaką miał plik
odczytaj_to.txt, który wykorzystałem w programie Odczytaj to. Właściwie program tworzy
i wyświetla ten nowy plik dwukrotnie, używając za każdym razem innej metody zapisu
danych do pliku. Rysunek 7.3 pokazuje wynik działania tego programu. Kod tego programu
możesz znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 7.; nazwa pliku to zapisz_to.py.

Rysunek 7.3. Ten sam plik jest tworzony dwukrotnie,


za każdym razem za pomocą innej metody obiektu pliku
Zapisywanie danych do pliku tekstowego 207

Zapisywanie łańcuchów znaków do pliku


Tak jak poprzednio, w celu wykorzystania pliku muszę go otworzyć we właściwym trybie.
Więc pierwszą rzeczą, jaką robię w programie, jest otwarcie pliku w trybie zapisu:
# Zapisz to
# Demonstruje zapisywanie danych do pliku tekstowego

print("Utworzenie pliku tekstowego za pomocą metody write().")


text_file = open("zapisz_to.txt", "w")

Pojawia się plik zapisz_to.txt jako pusty plik tekstowy oczekujący na dane,
które program będzie w nim zapisywał. Gdyby plik zapisz_to.txt istniał już wcześniej,
zostałby zastąpiony całkowicie nowym, pustym plikiem, a jego pierwotna zawartość
zostałaby usunięta.
Korzystam teraz z metody write() obiektu pliku, która zapisuje łańcuch znaków
do pliku:
text_file.write("Wiersz 1\n")
text_file.write("To jest wiersz 2\n")
text_file.write("Ten tekst tworzy wiersz 3\n")

Metoda write() nie wstawia automatycznie znaku nowego wiersza na końcu


łańcucha, który zapisuje. Musisz sam wstawić znaki nowego wiersza tam, gdzie są one
potrzebne. Jeśli opuściłbyś trzy znaki nowej linii występujące w powyższych wierszach
kodu, program zapisałby w pliku jeden długi wiersz.
Nie musisz również każdego łańcucha, jaki zapisujesz do pliku, kończyć na znaku
nowego wiersza. Aby osiągnąć taki sam ostateczny wynik, mógłbym równie dobrze
wszystkie trzy poprzednio zapisane łańcuchy skleić razem, aby utworzyć jeden długi
łańcuch, "Wiersz 1\nTo jest wiersz 2\nTen tekst tworzy wiersz 3\n", i zapisać go
do pliku poprzez jednokrotne wywołanie metody write().
W końcu zamykam plik:
text_file.close()

Następnie w celu udowodnienia, że zapisy zostały wykonane zgodnie z oczekiwaniem,


odczytuję i wyświetlam całą zawartość pliku:
print("\nOdczytanie zawartości nowo utworzonego pliku.")
text_file = open("zapisz_to.txt", "r")
print(text_file.read())
text_file.close()

Zapisywanie listy łańcuchów do pliku


Następnie tworzę od nowa ten sam plik przy użyciu metody writelines() obiektu pliku.
Podobnie jak jej odpowiedniczka readlines(), metoda writelines() obsługuje listę
łańcuchów, lecz zamiast wczytywać zawartość pliku tekstowego do listy, zapisuje listę
łańcuchów do pliku.
208 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Najpierw otwieram plik do zapisu:


print("\nUtworzenie pliku tekstowego za pomocą metody writelines().")
text_file = open("zapisz_to.txt", "w")

Otwieram ten sam plik, zapisz_to.txt, co oznacza, że wymazuję całkowicie istniejący


plik i zaczynam od nowego, pustego. Następnie tworzę listę łańcuchów znaków, które
mają być po kolei zapisane do pliku:
lines = ["Wiersz 1\n",
"To jest wiersz 2\n",
"Ten tekst tworzy wiersz 3\n"]

Tak jak poprzednio, wstawiłem znaki nowego wiersza tam, gdzie według mnie
powinny się znajdować w pliku tekstowym.
Następnie zapisuję całą listę łańcuchów do pliku za pomocą metody writelines():
text_file.writelines(lines)

Po czym zamykam plik:


text_file.close()

Na koniec wyświetlam zawartość pliku, aby pokazać, że nowy plik jest dokładnie taki
sam jak jego poprzednia wersja:
print("\nOdczytanie zawartości nowo utworzonego pliku.")
text_file = open("zapisz_to.txt", "r")
print(text_file.read())
text_file.close()

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


Poznałeś wiele metod obiektu pliku służących do odczytywania i zapisywania danych.
Ich podsumowanie znajdziesz w tabeli 7.2.

Tabela 7.2. Wybrane metody obiektu pliku

Metoda Opis
close() Zamyka plik. Odczytywanie danych z zamkniętego pliku oraz
zapisywanie do niego jest niemożliwe, dopóki nie zostanie
ponownie otwarty.
read([rozmiar]) Odczytuje z pliku wskazaną przez argument rozmiar liczbę znaków
i zwraca je w postaci łańcucha. Jeśli rozmiar nie jest określony,
metoda zwraca wszystkie znaki od pozycji bieżącej do końca pliku.
readline([rozmiar]) Odczytuje z bieżącego wiersza pliku wskazaną przez argument
rozmiar liczbę znaków i zwraca je w postaci łańcucha. Jeśli rozmiar
nie jest określony, metoda zwraca wszystkie znaki od pozycji
bieżącej do końca wiersza.
readlines() Odczytuje wszystkie wiersze pliku i zwraca je jako elementy listy.
write(dane) Zapisuje łańcuch dane do pliku.
writelines(dane) Zapisuje łańcuchy będące elementami listy dane do pliku.
Przechowywanie złożonych struktur danych w plikach 209

Przechowywanie złożonych struktur danych


w plikach
Pliki tekstowe są wygodne w użyciu, ponieważ można je odczytywać i manipulować nimi
za pomocą dowolnego edytora tekstu, lecz ich zastosowanie ogranicza się do przechowywania
ciągów znaków. Czasem możesz chcieć przechowywać bardziej złożone informacje, na
przykład takie jak lista lub słownik. Mógłbyś spróbować przekształcić zawartość tych
struktur danych na znaki i zapisać ją w pliku tekstowym, ale Python oferuje dużo lepszy
sposób. Możesz zapisać bardziej złożone dane w pliku za pomocą pojedynczego wiersza
kodu. Możesz nawet przechowywać w pojedynczym pliku prostą bazę danych, która
działa jak słownik.

Prezentacja programu Zamarynuj to


Marynowanie (ang. pickling) oznacza konserwowanie — i takie właśnie znaczenie ma
ten termin w Pythonie. Możesz zamarynować złożoną strukturę danych, taką jak lista
czy słownik, i zapisać ją w całości w pliku. Najlepsze ze wszystkiego jest to, że Twoje ręce
nie będą pachnieć octem, kiedy zakończysz swoją pracę.
Program Zamarynuj to marynuje, przechowuje w pliku binarnym i pobiera trzy listy
łańcuchów. Robi to dwukrotnie. Najpierw program marynuje, przechowuje i pobiera te
trzy listy sekwencyjnie, co w dużym stopniu przypomina sposób obsługi znaków w pliku
tekstowym, z którym się wcześniej spotkałeś. Następnie program przechowuje i pobiera
te same listy z zastosowaniem dostępu swobodnego. Wynik działania programu został
pokazany na rysunku 7.4. Kod tego programu możesz znaleźć na stronie internetowej tej
książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 7.; nazwa pliku
to zamarynuj_to.py.

Rysunek 7.4. Każda lista jest zapisywana do pliku i odczytywana z pliku w całości
210 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Marynowanie danych i zapisywanie ich do pliku


Pierwszą rzeczą, jaką robię w programie, jest zaimportowanie dwóch nowych modułów:
# Zamarynuj to
# Demonstruje marynowanie i odkładanie danych na półkę

import pickle, shelve

Moduł pickle umożliwia marynowanie i przechowywanie w pliku bardziej złożonych


danych. Moduł shelve umożliwia magazynowanie zamarynowanych obiektów w pliku
i zapewnia do nich dostęp swobodny.
Marynowanie jest proste. Zamiast zapisywać do pliku znaki, możesz zapisać w nim
zamarynowany obiekt. Zamarynowane obiekty są przechowywane w plikach w dużej
mierze tak samo jak znaki; możesz składować i pobierać je sekwencyjnie. Najpierw tworzę
trzy listy, które zamierzam zamarynować za pomocą modułu pickle i zapisać do pliku.
print("Marynowanie list.")
variety = ["łagodny", "pikantny", "kwaszony"]
shape = ["cały", "krojony wzdłuż", "w plasterkach"]
brand = ["Dawtona", "Klimex", "Vortumnus"]

Następnie otwieram nowy plik, aby przechować w nim zamarynowane listy.


f = open("pikle1.dat", "wb")

Zamarynowane obiekty muszą być przechowywane w pliku binarnym — nie mogą


zostać zapisane w pliku tekstowym. Więc otwieram nowy plik binarny o nazwie pikle1.dat
do zapisu poprzez przekazanie łańcucha "wb" wyznaczającego tryb dostępu do pliku.
W tabeli 7.3 znajduje się wykaz użytecznych trybów dostępu do pliku binarnego.

Tabela 7.3. Wybrane tryby dostępu do pliku binarnego


Tryb Opis
"rb" Odczyt danych z pliku binarnego. Jeśli plik nie istnieje, Python zasygnalizuje błąd.
"wb" Zapis danych do pliku binarnego. Jeśli plik już istnieje, jego zawartość zostaje
zastąpiona przez nowe dane. Jeśli nie istnieje, zostaje utworzony.
"ab" Dopisanie danych na końcu pliku binarnego. Jeśli plik istnieje, nowe dane
zostają do niego dopisane. Jeśli plik nie istnieje, jest tworzony.
"rb+" Odczyt i zapis danych z (do) pliku binarnego. Jeśli plik nie istnieje, Python
zasygnalizuje błąd.
"wb+" Zapis i odczyt danych do (z) pliku binarnego. Jeśli plik istnieje, jego zawartość
zostanie zastąpiona nowymi danymi. Jeśli nie istnieje, zostanie utworzony.
"ab+" Dopisywanie i odczyt danych do (z) pliku binarnego. Jeśli plik istnieje, nowe
dane są dopisywane na jego końcu. Jeśli plik nie istnieje, zostanie utworzony.

Następnie marynuję i magazynuję trzy listy, variety, shape i brand, w pliku pikle1.dat
przy użyciu funkcji pickle.dump(). Funkcja wymaga podania dwóch argumentów:
danych do zamarynowania i pliku do ich przechowywania.
Przechowywanie złożonych struktur danych w plikach 211

pickle.dump(variety, f)
pickle.dump(shape, f)
pickle.dump(brand, f)
f.close()

Więc ten kod marynuje listę, do której odwołuje się zmienna variety, i zapisuje
całość jako jeden obiekt do pliku pikle1.dat. Następnie program marynuje listę, do której
odwołuje się zmienna shape, i zapisuje całość jako jeden obiekt do pliku. Po czym program
marynuje listę, do której odwołuje się zmienna brand, i zapisuje całość jako jeden obiekt
do pliku. Na koniec program zamyka plik.
Można marynować różne obiekty, w tym:
 liczby,
 łańcuchy znaków,
 krotki,
 listy,
 słowniki.

Odczytywanie danych z pliku i ich odmarynowanie


Następnie pobieram z pliku i odmarynowuję te trzy listy za pomocą funkcji pickle.load().
Funkcja ta przyjmuje jeden argument — plik, z którego ma załadować kolejny
zamarynowany obiekt.
print("\nOdmarynowanie list.")
f = open("pikle1.dat", "rb")
variety = pickle.load(f)
shape = pickle.load(f)
brand = pickle.load(f)

Program odczytuje pierwszy zamarynowany obiekt w pliku, odmarynowuje go,


tworząc listę ["łagodny", "pikantny", "kwaszony"], i przypisuje tę listę do zmiennej
variety. Następnie program odczytuje kolejny zamarynowany obiekt w pliku,
odmarynowuje go, tworząc listę ["cały", "krojony wzdłuż", "w plasterkach"],
i przypisuje tę listę do zmiennej shape. Na koniec program odczytuje ostatni
zamarynowany obiekt w pliku, odmarynowuje go, tworząc listę ["Dawtona", "Klimex",
"Vortumnus"], i przypisuje tę listę do zmiennej brand.
W końcu wyświetlam odmarynowane listy, aby udowodnić, że cały ten proces
przebiegł prawidłowo:
print(variety)
print(shape)
print(brand)
f.close()

W tabeli 7.4 zostały opisane wybrane funkcje modułu pickle.


212 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Tabela 7.4. Wybrane funkcje modułu pickle


Funkcja Opis
dump(obiekt, plik, Zapisuje zamarynowaną wersję obiektu obiekt do pliku plik. Jeśli
[, bin]) bin ma wartość True, obiekt zostaje zapisany w formacie binarnym.
A jeśli bin ma wartość False, obiekt zostaje zapisany w mniej
wydajnym, ale czytelniejszym dla człowieka formacie binarnym.
Wartość domyślna parametru bin jest równa False.
load(plik) Odmarynowuje i zwraca kolejny zamarynowany obiekt z pliku plik.

Wykorzystanie półki do przechowywania


zamarynowanych danych
Teraz posuwam się o krok dalej w stosunku do koncepcji marynowania poprzez
umieszczenie tych list „na półce” (ang. shelving), w pojedynczym pliku. Korzystając
z modułu shelve, tworzę półkę (ang. shelf), która funkcjonuje jak słownik umożliwiający
bezpośredni (swobodny) dostęp do list:
Najpierw tworzę półkę o nazwie s:
print("\nOdkładanie list na półkę.")
s = shelve.open("pikle2.dat")

Funkcja shelve.open() przypomina w dużej mierze funkcję otwierania pliku open().


Funkcja shelve.open() operuje jednak plikiem, który przechowuje zamarynowane
obiekty, a nie znaki. W tym przypadku przypisałem półkę stanowiącą wynik wykonania
funkcji do zmiennej s, która teraz funkcjonuje jako słownik, którego zawartość jest
przechowywana w pliku lub grupie plików.

Wskazówka
Gdy wywołasz funkcję shelve.open(), Python może dodać rozszerzenie do podanej
przez Ciebie nazwy pliku. Python może również utworzyć dodatkowe pliki do obsługi
nowo utworzonej półki.

Funkcja shelve.open() wymaga podania jednego argumentu — nazwy pliku.


Przyjmuje także opcjonalny tryb dostępu. Jeśli nie podasz trybu dostępu (tak jak ja),
przyjmie on wartość domyślną "c". Tabela 7.5 zawiera szczegóły dotyczące trybów
dostępu obsługiwanych przez tę funkcję.

Tabela 7.5. Tryby dostępu obsługiwane przez moduł shelve


Tryb Opis
"c" Otwórz plik do odczytu i zapisu. Jeśli plik nie istnieje, zostaje utworzony.
"n" Utwórz nowy plik do odczytu i zapisu. Jeśli plik już istnieje, jego zawartość
zostaje nadpisana.
"r" Odczytuj dane z pliku. Jeśli plik nie istnieje, Python zasygnalizuje błąd.
"w" Zapisuj dane do pliku. Jeśli plik nie istnieje, Python zasygnalizuje błąd.
Przechowywanie złożonych struktur danych w plikach 213

Następnie dodaję do półki trzy listy:s


s["odmiana"] = ["łagodny", "pikantny", "kwaszony"]
s["kształt"] = ["cały", "krojony wzdłuż", "w plasterkach"]
s["marka"] = ["Dawtona", "Klimex", "Vortumnus"]

Półka s funkcjonuje jak słownik. Tak więc klucz "odmiana" tworzy parę z zawartością
["łagodny", "pikantny", "kwaszony"]. Kluczowi "kształt" odpowiada wartość
["cały", "krojony wzdłuż", "w plasterkach"], a klucz "marka" tworzy parę z wartością
["Dawtona", "Klimex", "Vortumnus"]. Jedną z ważnych rzeczy, na które należy zwrócić
uwagę, jest to, że klucz półki może być tylko łańcuchem znaków.
Na koniec wywołuję metodę sync() półki:
s.sync() # upewnij się, że dane zostały zapisane

Python zapisuje zmiany, które powinny się znaleźć w pliku półki, do bufora, a potem
okresowo zapisuje zawartość bufora do pliku. Aby mieć pewność, że zawartość pliku
odzwierciedla zmiany dokonane w półce, możesz wywołać jej metodę sync(). Plik półki
jest również aktualizowany wtedy, gdy zamykasz go za pomocą metody close().

Wskazówka
Chociaż mógłbyś zasymulować półkę poprzez zamarynowanie słownika, to jednak
moduł shelve wykorzystuje pamięć efektywniej. Więc jeśli potrzebujesz swobodnego
dostępu do zamarynowanych obiektów, utwórz półkę.

Wykorzystanie półki do przywrócenia


zamarynowanych danych
Ponieważ półka funkcjonuje jak słownik, możesz uzyskiwać bezpośredni dostęp do
zamarynowanych obiektów dzięki podaniu klucza. Aby to udowodnić, realizuję dostęp
do zamarynowanych list z półki s w odwrotnej kolejności.
print("\nPobieranie list z pliku półki:")
print("marka -", s["marka"])
print("kształt -", s["kształt"])
print("odmiana -", s["odmiana"])

Na koniec zamykam plik:


s.close()

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

W świecie rzeczywistym
Zamarynowanie i odmarynowanie to dobre sposoby magazynowania i pobierania
z powrotem ustrukturyzowanych informacji, lecz bardziej złożone informacje
mogą wymagać nawet silniejszych i elastyczniejszych środków. Dwie popularne
metody magazynowania i pobierania bardziej skomplikowanych struktur informacji to
bazy danych i pliki XML, a Python zawiera moduły, które mogą współpracować
z każdą z nich. Aby dowiedzieć się na ten temat więcej, odwiedź stronę języka
Python http://www.python.org.
214 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Obsługa wyjątków
Kiedy Python napotyka błąd, zatrzymuje bieżący program i wyświetla komunikat o błędzie.
Ujmując to bardziej precyzyjnie, zgłasza wyjątek, wskazując, że zdarzyło się coś niezwykłego.
Jeśli z tym wyjątkiem nic się nie robi, Python przerywa to, co wykonuje w danej chwili,
i wyświetla komunikat o błędzie podający szczegóły wyjątku.
Oto prosty przykład zgłoszenia przez Python wyjątku:
>>> num = float("Hej!")
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
num = float("Hej!")
ValueError: could not convert string to float: Hej!

W tej sesji interaktywnej Python próbuje przekształcić łańcuch "Hej!" na liczbę


zmiennoprzecinkową. Ponieważ okazuje się to niemożliwe, Python zgłasza wyjątek
i wyświetla stosowne szczegóły.
Wykorzystując funkcjonalność obsługi wyjątków oferowaną przez Pythona, możesz
przechwytywać i obsługiwać wyjątki, tak aby Twój program nie kończył się w nagły
i nieoczekiwany sposób (nawet jeśli użytkownik wprowadzi "Hej!" wtedy, gdy prosisz o podanie
liczby). Możesz przynajmniej sprawić, aby Twój program zakończył się zgrabnie i elegancko.

Prezentacja programu Obsłuż to


Program Obsłuż to otwiera się na wyjątki wynikające z danych wprowadzonych przez
użytkownika, a potem celowo generuje kilka własnych wyjątków. Lecz zamiast przerwać
swoje działanie, program wykonuje się do końca. Jest tak, ponieważ program obsługuje
wyjątki, które są zgłaszane. Rysunek 7.5 pokazuje program w działaniu. Kod tego programu
możesz znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 7.; nazwa pliku to obsluz_to.py.

Rysunek 7.5. Chociaż program nie może dokonać konwersji łańcucha "Hej!" na liczbę,
nie przerywa swojego działania, kiedy zostają zgłoszone wyjątki
Obsługa wyjątków 215

Użycie instrukcji try z klauzulą except


Najbardziej elementarnym sposobem obsługi (wyłapywania) wyjątków jest użycie
instrukcji try z klauzulą except. Dzięki użyciu instrukcji try wydzielasz pewien fragment
kodu, który mógłby potencjalnie wywołać wyjątek. Następnie tworzysz klauzulę except
z blokiem instrukcji, które są wykonywane tylko w przypadku zgłoszenia wyjątku.
Pierwszą czynnością, jaką wykonuję w programie Obsłuż to, jest poproszenie
użytkownika o podanie liczby. Pobieram łańcuch znaków wprowadzony przez użytkownika
i usiłuję dokonać konwersji tego łańcucha na liczbę zmiennoprzecinkową. Do obsługi
wszystkich wyjątków, jakie mogłyby zostać zgłoszone w trakcie tej operacji, wykorzystuję
try i except.
# Obsłuż to
# Demonstruje obsługę wyjątków

# try/except
try:
num = float(input("Wprowadź liczbę: "))
except:
print("Wystąpił jakiś błąd!")

Jeśli wywołanie funkcji float() powoduje zgłoszenie wyjątku (w następstwie


wprowadzenia przez użytkownika łańcucha nienadającego się do konwersji, takiego jak
na przykład "Hej!"), wyjątek zostaje wyłapany i użytkownik jest informowany, że Wystąpił
jakiś błąd!. Jeśli nie został zgłoszony żaden wyjątek, liczba wprowadzona przez
użytkownika zostaje przypisana do zmiennej num i program omija klauzulę except,
kontynuując wykonywanie reszty kodu.

Specyfikacja typu wyjątku


Różne rodzaje błędów skutkują różnymi typami wyjątków. Na przykład próba dokonania
konwersji łańcucha "Hej!" za pomocą funkcji float() powoduje zgłoszenie wyjątku
ValueError (błąd wartości), ponieważ znaki w łańcuchu mają niewłaściwą wartość
(nie są cyframi). Istnieje ponad dwadzieścia typów wyjątków, lecz w tabeli 7.6 zostało
wymienionych tylko kilka najczęściej występujących.
Klauzula except umożliwia dokładne określenie typu wyjątków, jaki będzie obsługiwać.
Aby określić jeden typ wyjątków, wystarczy podać jego specyfikację po słowie except.
# specyfikacja typu wyjątku
try:
num = float(input("\nWprowadź liczbę: "))
except ValueError:
print("To nie była liczba!")

W tej sytuacji funkcja print zostanie wykonana tylko wtedy, kiedy zostanie zgłoszony
wyjątek ValueError. Dzięki temu mogę być konkretniejszy i wyświetlić komunikat To nie
była liczba!. Jeśli jednak wewnątrz instrukcji try zostanie zgłoszony wyjątek jakiegoś
innego typu, klauzula except go nie wyłapie i wykonywanie programu zostanie przerwane.
216 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Tabela 7.6. Wybrane typy wyjątków


Typ wyjątku Opis
IOError Zgłaszany w przypadku wystąpienia błędu operacji wejścia-wyjścia,
takiego jak próba otwarcia nieistniejącego pliku w trybie odczytu.
IndexError Zgłaszany przy indeksowaniu sekwencji, gdy numer indeksu
wskazuje na nieistniejący element.
KeyError Zgłaszany, gdy nie zostanie znaleziony klucz słownika.
NameError Zgłaszany, gdy nie zostanie znaleziona nazwa (na przykład zmiennej
lub funkcji).
SyntaxError Zgłaszany, gdy zostanie wykryty błąd składni.
TypeError Zgłaszany, gdy wbudowana operacja lub funkcja zostanie
zastosowana do obiektu nieodpowiedniego typu.
ValueError Zgłaszany, gdy wbudowana operacja lub funkcja otrzyma argument,
który ma właściwy typ, ale nieodpowiednią wartość.
ZeroDivisionError Zgłaszany, gdy drugi argument operacji dzielenia lub modulo jest
równy zeru.

Specyfikowanie typów wyjątków pozwalające na obsługę każdego przypadku


indywidualnie stanowi dobrą praktykę w programowaniu. Wyłapywanie wszystkich
wyjątków w taki sposób, jak to zrobiłem w pierwszej klauzuli except programu, jest
właściwie niebezpieczne. Na ogół powinieneś unikać uniwersalnych rozwiązań tego typu.

Wskazówka
Kiedy powinno się stosować obsługę wyjątków? Każde miejsce zewnętrznej
interakcji z Twoim programem jest warte rozważenia pod kątem wyjątków.
Dobrym pomysłem jest obsługa wyjątków przy otwieraniu pliku do odczytu,
nawet jeśli uważasz, że plik już istnieje. Możesz także zdefiniować obsługę
wyjątków, gdy próbujesz przeprowadzać konwersję danych pochodzących
z zewnętrznego źródła, takiego jak użytkownik.

Sztuczka
Więc powiedzmy, że chcesz zdefiniować obsługę wyjątku, lecz nie jesteś całkiem
pewien, jak się nazywa jego typ. Oto łatwy sposób na dowiedzenie się tego —
wystarczy wygenerować ten wyjątek. Jeśli na przykład wiesz, że potrzebujesz
obsługi wyjątku dzielenia przez zero, ale nie pamiętasz dokładnie, jak się nazywa
odpowiedni typ wyjątku, skorzystaj z interpretera i podziel jakąś liczbę przez 0:
>>> 1/0
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
1/0
ZeroDivisionError: int division or modulo by zero
Dzięki tej sesji interaktywnej mogę zobaczyć, że wyjątek ma nazwę
ZeroDivisionError. Na szczęście interpreter nie jest nieśmiały i informuje Cię
dokładnie, jaki typ wyjątku wywołałeś.
Obsługa wyjątków 217

Obsługa wielu typów wyjątków


Pojedynczy fragment kodu może wygenerować różne typy wyjątków. Na szczęście
możesz zdefiniować obsługę wielu typów wyjątków. Jednym ze sposobów realizacji tego
zadania jest wymienienie ich w pojedynczej klauzuli except w postaci zamkniętej
w nawiasach grupy elementów oddzielonych przecinkami:
# obsłuż kilka typów wyjątków
print()
for value in (None, "Hej!"):
try:
print("Próba konwersji:", value, "-->", end=" ")
print(float(value))
except (TypeError, ValueError):
print("Wystąpił jakiś błąd!")

Ten kod próbuje dokonać konwersji dwóch różnych wartości na liczbę


zmiennoprzecinkową. Obie próby się nie udają, lecz każda z nich generuje wyjątek
innego typu. Wywołanie float(None) powoduje wyjątek typu TypeError, ponieważ
funkcja może tylko dokonywać konwersji łańcuchów znaków i liczb. Wywołanie
float("Hej!") powoduje powstanie wyjątku typu ValueError, ponieważ mimo że "Hej!"
jest łańcuchem, znaki w nim zawarte mają złą wartość (nie są cyframi). Dzięki klauzuli
except każdy z tych typów wyjątków jest obsługiwany.
Innym sposobem wyłapywania wielu wyjątków jest zastosowanie kilku klauzul
except. Po pojedynczej instrukcji try możesz użyć tyle klauzul except, ile chcesz:
print()
for value in (None, "Hej!"):
try:
print("Próba konwersji:", value, "-->", end=" ")
print(float(value))
except TypeError:
print("Możliwa jest tylko konwersja łańcucha lub liczby!")
except ValueError:
print("Możliwa jest tylko konwersja łańcucha cyfr!")

Teraz każdy typ wyjątku ma swój własny blok instrukcji. Więc kiedy zmienna value
ma wartość None, zostaje wygenerowany wyjątek typu TypeError i wyświetlony łańcuch
"Możliwa jest tylko konwersja łańcucha lub liczby!". Kiedy wartością zmiennej
value jest "Hej!", zostaje zgłoszony wyjątek ValueError i wyświetlony łańcuch
"Możliwa jest tylko konwersja łańcucha cyfr!".
Użycie wielu klauzul except umożliwia Ci zdefiniowanie zindywidualizowanych
reakcji na różne typy wyjątków wywołanych z tego samego bloku try. W tym przypadku
dzięki indywidualnej obsłudze każdego z typów wyjątku podaję bardziej konkretny
komunikat o błędzie.
218 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Wykorzystanie argumentu wyjątku


Kiedy zdarza się wyjątek, może mu towarzyszyć związana z nim wartość — jego
argument. Tym argumentem jest zwykle oficjalny komunikat od Pythona z opisem
wyjątku. Możesz otrzymać argument, jeśli po typie wyjątku podasz nazwę zmiennej
poprzedzoną słowem kluczowym as. W tym przypadku otrzymuję argument wyjątku
w zmiennej e i wypisuję jej wartość razem z moim zwykłym komunikatem o błędzie:
# pobierz argument wyjątku
try:
num = float(input("\nWprowadź liczbę: "))
except ValueError as e:
print("To nie była liczba! Lub wyrażając to na sposób Pythona...")
print(e)

Dodanie klauzuli else


Po wszystkich klauzulach except możesz dodać w instrukcji try pojedynczą klauzulę
else. Instrukcje bloku else są wykonywane tylko wtedy, gdy w bloku try nie zostanie
zgłoszony żaden wyjątek.
# try/except/else
try:
num = float(input("\nWprowadź liczbę: "))
except ValueError:
print("To nie była liczba!")
else:
print("Wprowadziłeś liczbę", num)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

W powyższym kodzie wartość zmiennej num jest wyświetlana w bloku else tylko
wtedy, gdy instrukcja przypisania w bloku try nie zgłosi wyjątku. Jest to doskonałe
rozwiązanie, ponieważ wartość zmiennej num zostanie wyświetlona tylko wówczas,
gdy instrukcja przypisania zakończyła się sukcesem i zmienna istnieje.

Powrót do gry Turniej wiedzy


Po opanowaniu podstaw plików i wyjątków nadszedł czas na zajęcie się grą Turniej
wiedzy zaprezentowaną na początku rozdziału. Jedną z fajnych cech tego programu jest
to, że odczytuje dane ze zwykłego pliku tekstowego, co umożliwia tworzenie własnych
odcinków gry kwizowej z wykorzystaniem edytora tekstu i szczypty kreatywności. Jak
zobaczysz w kodzie, plik tekstowy, z którego program odczytuje dane, kwiz.txt, musi
znajdować się w tym samym katalogu co plik programu. Aby utworzyć swój własny
odcinek z pełnym zestawem pytań, musisz tylko zastąpić ten plik takim, który będzie
zawierać Twoje własne dzieło.
Powrót do gry Turniej wiedzy 219

Objaśnienie struktury pliku z danymi


Zanim zacznę omawiać sam kod gry, powinieneś zrozumieć dokładnie strukturę
pliku kwiz.txt. Pierwszy wiersz pliku zajmuje tytuł odcinka. Reszta pliku składa się
z ośmiowierszowych bloków odpowiadających poszczególnym pytaniom. Możesz w nim
umieścić tyle bloków (a tym samym pytań), ile Ci się podoba. Oto ogólne przedstawienie
struktury bloku:
<kategoria>
<pytanie>
<odpowiedź 1>
<odpowiedź 2>
<odpowiedź 3>
<odpowiedź 4>
<prawidłowa odpowiedź>
<wyjaśnienie>

A oto początek pliku, jaki utworzyłem do tej gry:


Odcinek nie do odrzucenia
Plany na resztę życia
Za działalność mafijną skazano Cię na "kopę lat z hakiem" w więzieniu. /To oznacza, że
za kratkami spędzisz:
co najmniej 15 lat
co najmniej 25 lat
co najmniej 50 lat
co najmniej 60 lat
4
Kopa to sześćdziesiąt sztuk.
Ojciec Chrzestny chce Cię bliżej poznać
Powiedzmy, że masz audiencję u Ojca Chrzestnego soulu. Jak wypadałoby się /do niego
zwrócić?
Mr. Richard
Mr. Domino
Mr. Brown
Mr. Checker
3
James Brown jest nazywany „ojcem chrzestnym” soulu.

Aby zaoszczędzić miejsca w książce, pokazuję tylko pierwsze 17 wierszy pliku


obejmujące zakres dwóch pytań. Możesz obejrzeć zawartość całego pliku kwiz.txt,
który znajduje się na stronie WWW książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 7.
Pamiętaj, pierwszy wiersz w pliku, Odcinek nie do odrzucenia, jest w tej grze tytułem
odcinka. Następne osiem wierszy dotyczy pierwszego pytania. A kolejne osiem odnosi się
do pytania drugiego. Tak więc wiersz Plany na resztę życia to kategoria pierwszego
pytania. Kategoria jest sprytnym sposobem wprowadzenia w tematykę pytania. Kolejny
wiersz, Za działalność mafijną skazano Cię na "kopę lat z hakiem" w więzieniu.
/To oznacza, że za kratkami spędzisz:, to pierwsze pytanie w tej grze. Następne
cztery wiersze, co najmniej 15 lat, co najmniej 25 lat, co najmniej 50 lat i co
najmniej 60 lat, to cztery możliwe odpowiedzi, spośród których gracz dokona wyboru.
220 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Kolejny wiersz, 4, to numer poprawnej odpowiedzi. Więc w tym przypadku poprawną


odpowiedzią na pytanie jest ostatnia odpowiedź, co najmniej 60 lat. Kolejny wiersz,
Kopa to sześćdziesiąt sztuk., wyjaśnia, dlaczego poprawna odpowiedź jest poprawna.
Ten sam schemat odnosi się do reszty pytań.
Ważną rzeczą, która wymaga uwagi, jest to, że w dwóch z tych wierszy umieściłem
znak prawego ukośnika (/). Ma on reprezentować nowy wiersz, ponieważ Python nie
zawija automatycznie tekstu, kiedy go wypisuje. Kiedy program odczytuje wiersz z pliku,
zastępuje wszystkie prawe ukośniki znakiem nowego wiersza. Zobaczysz dokładnie,
jak program to robi, kiedy będę po kolei omawiał kod.

Funkcja open_file
Pierwszą czynnością, jaką wykonuję w programie, jest zdefiniowanie funkcji open_file(),
która w wywołaniu otrzymuje nazwę pliku i tryb (obydwa argumenty to łańcuchy znaków)
i zwraca odpowiadający im obiekt pliku. Używam instrukcji try z klauzulą except do
obsługi wyjątku IOError generowanego przez błędy wejścia-wyjścia, które wystąpiłyby
na przykład, gdyby plik nie istniał.
Wyłapanie wyjątku oznacza, że wystąpił problem z otwarciem pliku z kwizem.
Jeśli się to zdarzy, kontynuacja programu nie ma sensu, więc wyświetlam odpowiedni
komunikat i wywołuję funkcję sys.exit(). Funkcja ta zgłasza wyjątek, który skutkuje
zakończeniem programu. Powinieneś używać funkcji sys.exit() tylko jako środka
ostatecznego — w sytuacji, gdy musisz zakończyć program. Zauważ, że aby wywołać
funkcję sys.exit(), musiałem zaimportować moduł sys.
# Turniej wiedzy
# Gra sprawdzająca wiedzę ogólną, odczytująca dane ze zwykłego pliku tekstowego

import sys

def open_file(file_name, mode):


"""Otwórz plik."""
try:
the_file = open(file_name, mode)
except IOError as e:
print("Nie można otworzyć pliku", file_name, "Program zostanie zakończony.\n",
e)
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")
sys.exit()
else:
return the_file

Funkcja next_line()
Następnie definiuję funkcję next_line(), która otrzymuje obiekt pliku i zwraca kolejny
wiersz tekstu zawartego w pliku:
Powrót do gry Turniej wiedzy 221

def next_line(the_file):
"""Zwróć kolejny wiersz pliku kwiz po sformatowaniu go."""
line = the_file.readline()
line = line.replace("/", "\n")
return line

Stosuję jednak do tego wiersza jeden mały element formatowania przed jego
zwróceniem. Wszystkie prawe ukośniki zastępuję znakami nowego wiersza. Robię to,
ponieważ Python nie zawija automatycznie wypisywanego tekstu bez dzielenia wyrazów.
Moja procedura daje twórcy pliku tekstowego z kwizem pewną kontrolę nad formatowaniem.
Może on lub ona wskazać miejsca, gdzie powinny wystąpić przejścia do nowego wiersza,
tak aby słowa nie były dzielone między wiersze. Przyjrzyj się zawartości pliku kwiz.txt
i danym wyjściowym gry Turniej wiedzy, aby zobaczyć to w działaniu. Spróbuj usunąć
prawe ukośniki z pliku tekstowego i sprawdzić, jaki będzie tego efekt.

Funkcja next_block()
Funkcja next_block() odczytuje kolejny blok wierszy dotyczący jednego pytania. Pobiera
obiekt pliku i zwraca cztery łańcuchy znaków oraz listę łańcuchów znaków. Zwraca
łańcuchy reprezentujące kategorię, pytanie, poprawną odpowiedź oraz wyjaśnienie,
jak również listę czterech łańcuchów reprezentujących możliwe odpowiedzi na pytanie.
def next_block(the_file):
"""Zwróć kolejny blok danych z pliku kwiz."""
category = next_line(the_file)

question = next_line(the_file)

answers = []
for i in range(4):
answers.append(next_line(the_file))

correct = next_line(the_file)
if correct:
correct = correct[0]

explanation = next_line(the_file)

return category, question, answers, correct, explanation

Kiedy zostanie osiągnięty koniec pliku, odczyt wiersza zwróci pusty łańcuch. Więc
kiedy program dotrze do końca pliku kwiz.txt, zmienna category otrzyma pusty łańcuch.
Sprawdzam kategorię w funkcji main() programu. Kiedy staje się pustym łańcuchem,
następuje koniec gry.

Funkcja welcome()
Funkcja welcome() wita gracza i zapowiada tytuł odcinka. Funkcja otrzymuje tytuł
odcinka w postaci łańcucha i wyświetla go razem z komunikatem powitalnym.
222 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

def welcome(title):
"""Przywitaj gracza i pobierz jego nazwę."""
print("\t\t Witaj w turnieju wiedzy!\n")
print("\t\t", title, "\n")

Zainicjowanie gry
Następnie tworzę funkcję main(), która mieści w sobie główną pętlę gry. W pierwszej
części funkcji inicjuję grę poprzez otwarcie pliku z kwizem, pobranie tytułu odcinka
(pierwszy wiersz pliku), przywitanie gracza i ustawienie wyniku gracza na 0.
def main():
trivia_file = open_file("kwiz.txt", "r")
title = next_line(trivia_file)
welcome(title)
score = 0

Zadanie pytania
Po czym wczytuję pierwszy blok wierszy dotyczących pierwszego pytania do zmiennych.
Następnie uruchamiam pętlę while, która będzie kontynuować zadawanie pytań, dopóki
zmienna category nie będzie reprezentować pustego łańcucha. Pusty łańcuch jako
wartość zmiennej category oznacza, że został osiągnięty koniec pliku z kwizem i nie
nastąpi wejście do ciała pętli. Zadaję pytanie, wyświetlając kategorię pytania, samo
pytanie i cztery możliwe odpowiedzi.
# pobierz pierwszy blok
category, question, answers, correct, explanation = next_block(trivia_file)
while category:
# zadaj pytanie
print(category)
print(question)
for i in range(4):
print("\t", i + 1, "-", answers[i])

Pobranie odpowiedzi
Następnie pobieram odpowiedź gracza:
# uzyskaj odpowiedź
answer = input("Jaka jest Twoja odpowiedź?: ")

Sprawdzenie odpowiedzi
Potem porównuję odpowiedź gracza z odpowiedzią poprawną. Jeśli są zgodne,
gracz otrzymuje gratulacje i jego (lub jej) wynik jest zwiększany o jeden. Jeśli się okażą
niezgodne, gracz zostaje poinformowany o tym, że dokonał niewłaściwego wyboru.
W obydwu przypadkach wyświetlam potem wyjaśnienie, które uzasadnia słuszność
prawidłowej odpowiedzi. W końcu wyświetlam aktualny wynik gracza.
Podsumowanie 223

# sprawdź odpowiedź
if answer == correct:
print("\nOdpowiedź prawidłowa!", end=" ")
score += 1
else:
print("\nOdpowiedź niepoprawna.", end=" ")
print(explanation)
print("Wynik:", score, "\n\n")

Pobranie następnego pytania


Potem wywołuję funkcję next_block() i pobieram zestaw łańcuchów znaków dotyczących
kolejnego pytania. Jeśli nie ma już więcej pytań, zmienna category otrzyma pusty
łańcuch i wykonywanie pętli zostanie przerwane.
# pobierz kolejny blok
category, question, answers, correct, explanation = next_block(trivia_file)

Zakończenie gry
Po zakończeniu pętli zamykam plik z pytaniami i wyświetlam wynik gracza:
trivia_file.close()

print("To było ostatnie pytanie!")


print("Twój końcowy wynik wynosi", score)

Uruchomienie funkcji main()


Ostatnie wiersze kodu uruchamiają funkcję main() i zamykają okno programu:
main()
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale poznałeś pliki i wyjątki. Dowiedziałeś się, jak odczytywać dane z plików
tekstowych. Zobaczyłeś, jak się odczytuje pojedynczy znak albo cały plik na raz. Poznałeś
kilka różnych sposobów odczytywania zawartości pliku tekstowego po jednym pełnym
wierszu na raz, które prawdopodobnie jest najczęściej stosowane. Dowiedziałeś się także,
jak zapisywać dane do plików tekstowych — od pojedynczego znaku do listy łańcuchów
— po czym dowiedziałeś się, jak zapisywać w plikach bardziej skomplikowane dane
poprzez ich marynowanie oraz jak zarządzać grupą zamarynowanych obiektów zapisanych
w pojedynczym pliku binarnym przy użyciu półki. Potem zobaczyłeś, jak można obsługiwać
wyjątki zgłoszone w trakcie wykonywania programu. Zobaczyłeś, jak wyłapywać konkretne
wyjątki i jak pisać kod do ich obsługi. Na koniec dowiedziałeś się, jak połączyć obsługę
plików i wyjątków poprzez konstrukcję programu gry kwizowej, który umożliwia
każdemu posiadaczowi edytora tekstu tworzenie swoich własnych odcinków kwizu.
224 Rozdział 7. Pliki i wyjątki. Gra Turniej wiedzy

Sprawdź swoje umiejętności


1. Ulepsz grę Turniej wiedzy w taki sposób, aby każde pytanie miało przypisaną
indywidualnie określoną wartość punktową. Wynik uzyskany przez gracza
powinien stanowić sumę wartości punktowych wszystkich pytań, na które gracz
odpowiedział poprawnie.
2. Ulepsz grę Turniej wiedzy w taki sposób, aby w pliku była utrzymywana lista
najlepszych wyników. Program powinien rejestrować nazwę gracza i jego wynik,
jeśli ten mieści się na liście. Przechowuj najlepsze wyniki przy użyciu
„zamarynowanego” obiektu.
3. Zmień sposób implementacji obsługi listy najlepszych wyników, którą utworzyłeś
w poprzednim zadaniu. Tym razem do przechowywania listy użyj zwykłego
pliku tekstowego.
4. Utwórz odcinek gry kwizowej, która testuje wiedzę gracza o obsłudze plików
i wyjątków.
8
Obiekty programowe.
Program
Opiekun zwierzaka

P rogramowanie obiektowe (ang. object-oriented programming — OOP) jest


odmiennym sposobem myślenia o programowaniu. Jest to nowoczesna metodologia,
która została chętnie przyjęta przez branżę producentów oprogramowania i jest stosowana
przy tworzeniu większości nowego komercyjnego oprogramowania. Podstawowym
elementem konstrukcyjnym w programowaniu OOP jest obiekt programowy — często
nazywany po prostu obiektem. W tym rozdziale postawisz swoje pierwsze kroki na
drodze do zrozumienia metodologii OOP, kiedy będziesz poznawać obiekty.
W szczególności nauczysz się:
 tworzyć klasy definiujące obiekty,
 pisać metody i tworzyć atrybuty do obiektów,
 tworzyć obiekty jako instancje klas,
 ograniczać dostęp do atrybutów obiektu.

Wprowadzenie do programu Opiekun zwierzaka


Program Opiekun zwierzaka powierza użytkownikowi opiekę nad jego wirtualnym
pupilem. Użytkownik nadaje zwierzakowi imię i jest całkowicie odpowiedzialny
za utrzymywanie go w stanie szczęśliwości, co nie jest wcale łatwym zadaniem.
Użytkownik musi karmić zwierzaka i bawić się z nim, aby podtrzymywać jego dobry
nastrój. Użytkownik może słuchać zwierzaka, aby dowiedzieć się o jego samopoczuciu,
które może zmieniać się od bycia szczęśliwym do wściekłości. Program został
przedstawiony na rysunkach od 8.1 do 8.3.
226 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Rysunek 8.1. Musisz nazwać swojego zwierzaka

Rysunek 8.2. Jeśli nie nakarmisz swojego zwierzaka lub nie pobawisz się z nim,
jego nastrój się pogorszy

Chociaż możliwe byłoby napisanie tego programu bez wykorzystania obiektów


programowych, utworzyłem zwierzaka jako obiekt. Ostatecznie takie podejście sprawia,
że program staje się łatwiejszy w obsłudze i modyfikacji. Poza tym umożliwia ono
bezbolesne skalowanie. Kiedy już się utworzy jednego zwierzaka, utworzenie kolejnych
dziesięciu i zarządzanie nimi nie wymaga wielkiego trudu. Czy daleko stąd do farmy
zwierzaków? (Przekonasz się, że nie, kiedy spróbujesz rozwiązać zadania zamieszczone
na końcu rozdziału).
Podstawy programowania obiektowego 227

Rysunek 8.3. Ale dzięki właściwej opiece, Twojemu zwierzakowi wróci jego pierwotny,
pogodny nastrój

Podstawy programowania obiektowego


Istnieje dość powszechna opinia, że programowanie OOP jest skomplikowane, ale ja uważam,
że jest właściwie prostsze niż niektóre z koncepcji, które już poznałeś. W gruncie rzeczy
programowanie OOP umożliwia Ci przedstawianie w swoich programach różnych rzeczy
w sposób bardziej zbliżony do świata realnego.
Tym, co często chcesz odwzorować w swoich programach — poczynając od
rachunku bieżącego, a kończąc na statku kosmicznym z obcej planety — są obiekty
wzięte z prawdziwego życia. Programowanie obiektowe pozwala Ci przedstawiać te
obiekty z prawdziwego życia jako obiekty programowe. Podobnie jak obiekty ze świata
rzeczywistego, obiekty programowe łączą w sobie cechy (zwane w terminologii OOP
atrybutami) i zachowania (nazywane w języku OOP metodami). Gdybyś na przykład
miał utworzyć obiekt statku kosmicznego obcych przybyszy, jego atrybuty obejmowałyby
jego położenie i poziom energii, podczas gdy metody mogłyby obejmować jego zdolność
do poruszania się lub rażenia celów za pomocą posiadanej broni.
Obiekty są tworzone (czyli konkretyzowane w języku OOP) na podstawie definicji
zwanej klasą (która jest elementem programowym mogącym zawierać definicje atrybutów
i metod). Klasy to jakby plany czy wzorce. Klasa nie jest obiektem, lecz projektem obiektu.
I jak budowniczy może wznieść wiele domów na podstawie tego samego planu, tak
programista może utworzyć wiele obiektów na podstawie tej samej klasy. W rezultacie
każdy obiekt (zwany również instancją lub egzemplarzem) skonkretyzowany na podstawie
tej samej klasy będzie miał podobną strukturę. Więc jeśli dysponujesz klasą rachunku
bieżącego, mógłbyś jej użyć do utworzenia wielu obiektów rachunku bieżącego. I te różne
obiekty miałyby taką samą podstawową strukturę. Każdy z nich mógłby na przykład mieć
atrybut o nazwie saldo.
228 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Ale tak jak możesz wybrać dwa domy wybudowane na podstawie tego samego planu
i pomalować je w różny sposób, możesz również mieć dwa obiekty tej samej klasy i nadać
każdemu z nich jego własny, unikalny zestaw wartości atrybutów. Więc mógłbyś mieć
jeden obiekt rachunku bieżącego z atrybutem salda o wartości 100, a drugi z atrybutem
salda o wartości 1 000 000.

Wskazówka
Nie martw się, jeśli cała ta terminologia OOP nie jest jeszcze dla Ciebie całkowicie
jasna. Chciałem Ci tylko przedstawić w ogólnym zarysie pojęcie obiektów. Podobnie
jak w przypadku wszystkich nowych koncepcji z zakresu programowania, czytanie
o nich nie wystarczy. Lecz po zapoznaniu się z pewną ilością prawdziwego kodu
w języku Python, który definiuje klasy i tworzy obiekty (i po utworzeniu pewnej
ilości własnego kodu) wkrótce zrozumiesz, o co chodzi w OOP.

Tworzenie klas, metod i obiektów


Aby skonstruować obiekt, musisz najpierw mieć plan, czyli klasę. Klasy prawie zawsze
zawierają metody reprezentujące czynności, które obiekt może wykonywać. Można
co prawda utworzyć klasę bez żadnych metod, ale nie byłoby to zbyt interesujące.

Prezentacja programu Prosty zwierzak


Program Prosty zwierzak zawiera Twój pierwszy przykład klasy napisanej w Pythonie.
Definiuję w nim wyjątkowo prosty typ zwierzaka, który potrafi tylko jedno — mówić
„cześć”. Chociaż tego rodzaju zwierzak mógłby się wydawać zbyt prosty, przynajmniej
jest uprzejmy. Wynik działania tego programu został przedstawiony na rysunku 8.4.

Rysunek 8.4. Kiedy program wywołuje metodę talk() obiektu klasy Critter,
zwierzak pozdrawia świat

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 8.; nazwa pliku
to prosty_zwierzak.py. Sam program jest dość krótki.
# Prosty zwierzak
# Demonstruje podstawową klasę i obiekt
Tworzenie klas, metod i obiektów 229

class Critter(object):
"""Wirtualny pupil"""
def talk(self):
print("Cześć! Jestem egzemplarzem klasy Critter.")

# część główna
crit = Critter()
crit.talk()

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Definiowanie klasy
Program rozpoczyna się od definicji klasy, czyli projektu mojego pierwszego zwierzaka.
Pierwszym wierszem tej definicji jest nagłówek klasy:
class Critter(object):

Użyłem słowa kluczowego class, po którym wpisałem wybraną przez siebie nazwę
klasy: Critter. Zauważ, że moja nazwa klasy rozpoczyna się od dużej litery. Python tego
nie wymaga, ale jest to standardowa konwencja, więc i Ty powinieneś rozpoczynać
wszystkie swoje nazwy klas od dużej litery.
Następnie poleciłem Pythonowi, aby oparł moją klasę na podstawowym,
wbudowanym typie o nazwie object. Możesz oprzeć nową klasę na typie object
lub na dowolnej uprzednio zdefiniowanej klasie, lecz jest to temat z rozdziału 9.,
„Programowanie obiektowe. Gra Blackjack”. W tym rozdziale klasą bazową wszystkich
moich klas jest typ object.
Następny wiersz to łańcuch dokumentacyjny klasy. Dobry łańcuch dokumentacyjny
opisuje rodzaj obiektów, do tworzenia których klasa może być wykorzystywana.
Mój łańcuch dokumentacyjny jest dość prosty:
"""Wirtualny pupil"""

Definiowanie metody
Ostatnia część kodu klasy definiuje metodę. Bardzo to przypomina definicję funkcji:
def talk(self):
print("Cześć! Jestem egzemplarzem klasy Critter.")

Właściwie możesz myśleć o metodach jako o funkcjach związanych z obiektem.


(Spotkałeś się z tym już wcześniej na przykładzie metod łańcucha lub listy). Metoda
talk() wyświetla łańcuch "Cześć! Jestem egzemplarzem klasy Critter.".
Zwróć uwagę, że metoda talk() ma jeden parametr, self, (którego akurat nie
używa). Każda metoda instancji — metoda, którą ma każdy obiekt klasy — musi mieć
specjalny pierwszy parametr noszący zgodnie z konwencją nazwę self. Ten parametr
dostarcza metodzie sposobu odwoływania się do samego obiektu. Na razie nie martw się
o parametr self, zobaczysz go w działaniu w nieco dalszej części tego rozdziału.
230 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Pułapka
Jeśli utworzysz metodę instancji bez jakichkolwiek parametrów, wygenerujesz
błąd przy jej wywołaniu. Pamiętaj, wszystkie metody instancji muszą zawierać
specjalny pierwszy parametr noszący zgodnie z konwencją nazwę self.

Konkretyzacja obiektu
Po napisaniu kodu klasy konkretyzacja nowego obiektu zajęła mi tylko jeden wiersz:
crit = Critter()

W tym wierszu tworzony jest nowiutki obiekt klasy Critter, który zostaje przypisany
do zmiennej crit. Zwróć uwagę na nawiasy występujące po nazwie klasy Critter w instrukcji
przypisania. Ich użycie przy tworzeniu nowego obiektu ma kluczowe znaczenie.
Możesz przypisać nowo skonkretyzowany obiekt do zmiennej o dowolnej nazwie.
Nazwa ta nie musi być oparta na nazwie klasy. Powinieneś jednak unikać na ogół
używania jako nazwy obiektu pisanej małą literą nazwy klasy, ponieważ może to
prowadzić do zamieszania.

Wywoływanie metody
Mój nowy obiekt posiada metodę o nazwie talk(). Metoda ta jest podobna do dowolnej
innej metody spośród tych, z jakimi już się spotkałeś. Zasadniczo jest to funkcja, która
należy do obiektu. Mogę wywoływać tę metodę tak samo jak każdą inną przy użyciu
notacji z kropką:
crit.talk()

Wiersz ten wywołuje metodę talk() obiektu klasy Critter przypisanego do zmiennej
crit. Metoda ta po prostu wyświetla łańcuch znaków "Cześć! Jestem egzemplarzem
klasy Critter.".

Używanie konstruktorów
Zobaczyłeś, jak można tworzyć metody takie jak talk(), lecz możesz napisać specjalną
metodę, zwaną konstruktorem, która jest wywoływana automatycznie zaraz po
utworzeniu nowego obiektu. Metoda odgrywająca rolę konstruktora jest niezwykle
pożyteczna. Prawdę mówiąc, będziesz często pisać jedną taką metodę do każdej
tworzonej przez siebie klasy. Konstruktor jest często wykorzystywany do nadania
wartości początkowych atrybutom obiektu, chociaż w niżej przedstawionym programie
nie użyję jej w tym celu.
Używanie konstruktorów 231

Prezentacja programu Zwierzak z konstruktorem


Program Zwierzak z konstruktorem definiuje nową klasę Critter, która zawiera prosty
konstruktor. Program pokazuje również, jak łatwo można tworzyć wiele obiektów
na podstawie tej samej klasy. Na rysunku 8.5 pokazano przykładowe uruchomienie
programu.

Rysunek 8.5. Zostają utworzone dwa oddzielne zwierzaki. Każdy z nich mówi „cześć”

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 8.; nazwa pliku
to zwierzak_z_konstruktorem.py.
# Zwierzak z konstruktorem
# Demonstruje konstruktory

class Critter(object):
"""Wirtualny pupil"""
def __init__(self):
print("Urodził się nowy zwierzak!")

def talk(self):
print("\nCześć! Jestem egzemplarzem klasy Critter.")

# część główna
crit1 = Critter()
crit2 = Critter()

crit1.talk()
crit2.talk()

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")


232 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Tworzenie konstruktora
Pierwszym nowym fragmentem kodu w definicji klasy jest konstruktor (zwany również
metodą inicjalizacji):
def __init__(self):
print("Urodził się nowy zwierzak!")

Zwykle sam tworzysz nazwy swoich metod, ale w tym miejscu użyłem szczególnej
nazwy metody, rozpoznawanej przez Python. Nadając metodzie nazwę __init__,
poinformowałem Pythona, że jest to mój konstruktor. Jako konstruktor metoda __init__
jest wywoływana automatycznie przez każdy nowo tworzony obiekt klasy Critter
natychmiast po zaistnieniu obiektu. Jak widać w drugim wierszu kodu metody, oznacza
to, że każdy nowo utworzony obiekt klasy Critter automatycznie ogłasza światu swoje
powstanie poprzez wyświetlenie łańcucha "Urodził się nowy zwierzak!".

Wskazówka
Python posiada kolekcję wbudowanych „metod specjalnych”, których nazwy
rozpoczynają się od dwóch znaków podkreślenia i tak samo się kończą, tak
jak w przypadku konstruktora __init__.

Tworzenie wielu obiektów


Po napisaniu kodu klasy utworzenie wielu obiektów to pestka. W głównej części
programu tworzę dwa:
# część główna
crit1 = Critter()
crit2 = Critter()

W rezultacie zostają utworzone dwa obiekty. Każdy z nich zaraz po konkretyzacji


wyświetla łańcuch "Urodził się nowy zwierzak!" poprzez swoją metodę — konstruktora.
Każdy obiekt to osobny, w pełni ukształtowany zwierzak. Aby to udowodnić,
wywołuję ich metody talk():
crit1.talk()
crit2.talk()

Mimo że te dwa wiersze kodu wypisują dokładnie takie same łańcuchy znaków,
"\nCześć! Jestem egzemplarzem klasy Critter.", każdy z nich jest wynikiem działania
innego obiektu.

Wykorzystywanie atrybutów
Możesz sprawić, że atrybuty obiektu zostaną automatycznie utworzone i zainicjalizowane
tuż po ich utworzeniu, poprzez wykorzystanie konstruktora. Jest to duża wygoda i coś,
co często będziesz stosował.
Wykorzystywanie atrybutów 233

Prezentacja programu Zwierzak z atrybutem


Program Zwierzak z atrybutem tworzy nowy typ obiektu z atrybutem name (nazwa).
Klasa Critter zawiera konstruktor, który tworzy i inicjalizuje atrybut name. Program
wykorzystuje nowy atrybut do personalizacji pozdrowienia artykułowanego przez
zwierzaka. Rysunek 8.6 pokazuje program w działaniu.

Rysunek 8.6. Tym razem każdy obiekt klasy Critter ma atrybut name,
który wykorzystuje, mówiąc „cześć”

Oto kod tego programu:


# Zwierzak z atrybutem
# Demonstruje tworzenie atrybutów obiektu i uzyskiwanie do nich dostępu

class Critter(object):
"""Wirtualny pupil"""
def __init__(self, name):
print("Urodził się nowy zwierzak!")
self.name = name

def __str__(self):
rep = "Obiekt klasy Critter\n"
rep += "name: " + self.name + "\n"
return rep

def talk(self):
print("Cześć! Jestem", self.name, "\n")

# część główna
crit1 = Critter("Reksio")
crit1.talk()

crit2 = Critter("Pucek")
234 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

crit2.talk()

print("Wyświetlenie obiektu crit1:")


print(crit1)

print("Bezpośrednie wyświetlenie wartości atrybutu crit1.name:")


print(crit1.name)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Inicjalizacja atrybutów
Konstruktor w tym programie wyświetla komunikat "Urodził się nowy zwierzak!"
zupełnie tak samo jak konstruktor w programie Zwierzak z konstruktorem, ale kolejny
wiersz metody robi coś nowego. Tworzy atrybut name dla nowego obiektu i nadaje mu
wartość parametru name. Więc wykonanie znajdującej się w głównej części programu
instrukcji:
crit1 = Crittter("Reksio")

skutkuje utworzeniem nowego obiektu klasy Critter z atrybutem name, któremu została
nadana wartość "Reksio". Na koniec obiekt zostaje przypisany do zmiennej crit1.
Żebyś mógł dokładnie zrozumieć, jak to wszystko działa, wyjawię, czym jest
tajemniczy parametr self. Jako pierwszy parametr każdej metody, self automatycznie
otrzymuje jako swoją wartość referencję do obiektu wywołującego metodę. To oznacza,
że dzięki parametrowi self metoda może sięgnąć do wywołującego ją obiektu i uzyskać
dostęp do jego atrybutów i metod (a nawet utworzyć nowe atrybuty dla tego obiektu).

Wskazówka
Możesz nadać pierwszemu parametrowi w nagłówku metody inną nazwę niż
self, ale nie powinieneś tego robić. Nazwa self jest charakterystyczna dla
Pythona i inni programiści będą jej oczekiwać.

Wróćmy więc do konstruktora. Parametr self automatycznie otrzymuje referencję


do nowego obiektu klasy Critter, podczas gdy parametr name otrzymuje wartość
"Reksio". Następnie wiersz:
self.name = name

tworzy atrybut name dla obiektu i nadaje mu wartość parametru name, którą jest łańcuch
"Reksio".
Z kolei instrukcja przypisania w głównej części programu przypisuje ten nowy obiekt
do zmiennej crit1. To oznacza, że zmienna crit1 odwołuje się do nowego obiektu z jego
własnym atrybutem o nazwie name i wartości "Reksio". Tak więc zwierzak został
utworzony ze swoim własnym imieniem!
Wiersz głównej części programu:
crit2 = Critter("Pucek")
Wykorzystywanie atrybutów 235

uruchamia taki sam podstawowy łańcuch zdarzeń. Lecz tym razem nowy obiekt klasy
Critter zostaje utworzony ze swoim własnym atrybutem name ustawionym na "Pucek".
A obiekt zostaje przypisany do zmiennej crit2.

Uzyskiwanie dostępu do atrybutów


Atrybuty nie miałyby żadnego sensu, gdyby nie można było ich używać, więc napisałem
bardziej spersonalizowaną metodę talk(), która wykorzystuje atrybut name obiektu klasy
Critter. Teraz, kiedy zwierzak mówi „cześć”, przedstawia się swoim imieniem.
Zmusiłem mojego pierwszego zwierzaka do powiedzenia „cześć” poprzez wywołanie
metody talk w wierszu:
crit1.talk()

Metoda talk() otrzymuje poprzez swój parametr self przesłaną automatycznie


referencję do obiektu:
def talk(self):

Następnie funkcja print wyświetla tekst Cześć! Jestem Reksio, uzyskując dostęp
do atrybutu name obiektu poprzez odwołanie self.name:
print("Cześć! Jestem", self.name, "\n")

Te same podstawowe zdarzenia zachodzą, kiedy potem wywołuję tę metodę


dla mojego drugiego obiektu:
crit2.talk()

Lecz tym razem metoda talk() wyświetla tekst Cześć! Jestem Pucek, ponieważ
wartością atrybutu name obiektu crit2 jest łańcuch "Pucek".
Domyślnie możesz uzyskiwać dostęp do atrybutów obiektu oraz modyfikować ich
wartość z zewnątrz jego klasy. W głównej części programu skorzystałem z bezpośredniego
dostępu do atrybutu name obiektu crit1:
print(crit1.name)

Powyższy wiersz kodu wyświetla łańcuch "Reksio". Na ogół, aby uzyskać dostęp
do atrybutu obiektu z zewnątrz klasy tego obiektu, można użyć notacji z kropką.
Wpisz nazwę zmiennej, po niej kropkę, a po kropce nazwę atrybutu.

Wskazówka
Zwykle starasz się unikać korzystania z bezpośredniego dostępu do atrybutów
obiektu poza definicją jego klasy. Dowiesz się o tym więcej w dalszej części tego
rozdziału, w podrozdziale „Hermetyzacja obiektów”.
236 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Wyświetlanie obiektu
Standardowo, gdybym chciał wyświetlić obiekt za pomocą kodu print(crit1), Python
zwróciłby coś, co wygląda dość zagadkowo:
<__main__.Critter object at 0x00A0BA90>

Dowiaduję się, że wyświetliłem obiekt klasy Critter w głównej części swojego


programu, ale nie otrzymuję żadnych użytecznych informacji o samym obiekcie.
Istnieje jednak sposób na zmianę tego stanu rzeczy. Poprzez dołączenie metody
specjalnej __str__() do definicji klasy możesz utworzyć reprezentację każdego z jej
obiektów w postaci łańcucha znaków, który zostanie wypisany, ilekroć wystąpi polecenie
wyświetlenia obiektu. Jako reprezentacja obiektu zostanie wyświetlony łańcuch znaków
zwrócony przez metodę __str__(), bez względu na jego zawartość.
Metoda __str__(), którą napisałem, zwraca łańcuch, który zawiera wartość atrybutu
name obiektu. Więc kiedy wykonywany jest wiersz:
print(crit1)

pojawia się poniższy, bardziej użyteczny tekst:


Obiekt klasy Critter
name: Reksio

Sztuczka
Nawet jeśli w ogóle nie zamierzasz wyświetlać obiektu w swoim programie,
utworzenie metody __str__() nadal nie jest złym pomysłem. Może się okazać,
że możliwość zobaczenia wartości atrybutów obiektu pomoże Ci zrozumieć
działanie programu (lub brak spodziewanego działania).

Wykorzystanie atrybutów klasy


i metod statycznych
Dzięki atrybutom różne obiekty tej samej klasy mogą zawierać swoje własne, unikalne
wartości. Mógłbyś na przykład posiadać 10 różnych, biegających wokół zwierzaków,
z których każdy ma swoje własne imię. Ale możesz mieć pewne informacje, które nie
odnoszą się do poszczególnych obiektów, lecz do całej klasy. Mógłbyś, powiedzmy,
chcieć śledzić na bieżąco liczbę utworzonych przez siebie zwierzaków. Można by było
dodać do każdego obiektu klasy Critter atrybut o nazwie total. Ale wówczas, ilekroć
byłby tworzony nowy obiekt, musiałbyś aktualizować wartość atrybutu total każdego
istniejącego obiektu. Byłoby to prawdziwym utrapieniem. Na szczęście Python daje
możliwość utworzenia pojedynczej wartości, która będzie związana z samą klasą, zwanej
atrybutem klasy. Jeśli postrzegamy klasę jako plan, to atrybut klasy możemy sobie
wyobrazić jako samoprzylepną karteczkę przyklejoną do tego planu. Istnieje tylko jeden
jej egzemplarz, bez względu na to, ile rzeczy wytworzysz na podstawie planu.
Wykorzystanie atrybutów klasy i metod statycznych 237

Mogłoby się również okazać, że potrzebujesz metody, która jest związana z klasą;
w tej sytuacji Python oferuje metodę statyczną. Ponieważ metody statyczne są związane
z klasą, często wykorzystuje się w nich atrybuty klasy.

Prezentacja programu Zwierzak z klasą


Nie, program Zwierzak z klasą nie dotyczy zwierzaka, który chodził do prywatnej szkoły
przygotowującej do życia w wielkim świecie i szydzi z innych zwierzaków, które nie
wiedzą, jak posługiwać się widelcem. Ten program wiąże się ze stosowaniem atrybutów
i metod, które należą raczej do klasy niż do konkretnego obiektu. Program definiuje
atrybut klasy, który rejestruje ogólną liczbę skonkretyzowanych obiektów klasy Critter.
Klasa zawiera również metodę statyczną, która wyświetla tę liczbę. Rysunek 8.7 pokazuje
wynik działania programu.

Rysunek 8.7. Zwierzaki rodzą się na prawo i lewo! Program śledzi je wszystkie poprzez
pojedynczy atrybut klasy, którego wartość wyświetla za pomocą metody statycznej

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 8.; nazwa pliku
to zwierzak_z_klasa.py.
# Zwierzak z klasą
# Demonstruje atrybuty klasy i metody statyczne

class Critter(object):
"""Wirtualny pupil"""
total = 0

@staticmethod
def status():
print("\nOgólna liczba zwierzaków wynosi", Critter.total)
238 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

def __init__(self, name):


print("Urodził się zwierzak!")
self.name = name
Critter.total += 1

#część główna
print("Uzyskanie dostępu do atrybutu klasy Critter.total:", end=" ")
print(Critter.total)

print("\nTworzenie zwierzaków.")
crit1 = Critter("zwierzak 1")
crit2 = Critter("zwierzak 2")
crit3 = Critter("zwierzak 3")

Critter.status()

print("\nUzyskanie dostępu do atrybutu klasy poprzez obiekt:", end= " ")


print(crit1.total)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Tworzenie atrybutu klasy


Drugi wiersz mojej definicji klasy:
total = 0

tworzy atrybut klasy o nazwie total i przypisuje mu wartość 0. Każda instrukcja


przypisania tego rodzaju — nowa zmienna otrzymuje wartość poza kodem metody —
tworzy atrybut klasy. Instrukcja przypisania jest wykonywana tylko raz, kiedy Python
po raz pierwszy napotyka definicję klasy. To oznacza, że atrybut klasy już istnieje, zanim
zostanie utworzony choć jeden obiekt. Więc możesz wykorzystywać atrybut klasy nawet
w sytuacji, gdy żadne jej obiekty nie zostały powołane do istnienia.

Uzyskiwanie dostępu do atrybutu klasy


Uzyskiwanie dostępu do atrybutu klasy jest proste. Korzystam z dostępu do nowego
atrybutu klasy w kilku różnych miejscach programu. W głównej części programu
wypisuję jego wartość za pomocą instrukcji
print Critter.total

W metodzie statycznej status() wyświetlam wartość atrybutu klasy Critter za


pomocą wiersza kodu:
print("\nOgólna liczba zwierzaków wynosi", Critter.total)

W konstruktorze zwiększam wartość tego atrybutu klasy, wykorzystując instrukcję:


Critter.total += 1
Wykorzystanie atrybutów klasy i metod statycznych 239

Dzięki tej instrukcji za każdym razem, gdy konkretyzowany jest nowy obiekt,
wartość atrybutu jest zwiększana o 1.
Generalnie w celu uzyskania dostępu do atrybutu klasy stosuj notację z kropką.
Wpisz nazwę klasy, umieść po niej kropkę, a po kropce podaj nazwę atrybutu.
Wreszcie możesz uzyskać dostęp do atrybutu klasy poprzez obiekt tej klasy.
Właśnie to zrobiłem w głównej części programu, w następującym wierszu:
print(crit1.total)

Powyższa instrukcja wyświetla wartość atrybutu klasy: total (a nie atrybutu samego
obiektu). Można odczytać wartość atrybutu klasy, wykorzystując dowolny obiekt, który
należy do tej klasy. Więc mógłbym użyć instrukcji print(crit2.total) lub
print(crit3.total) i otrzymałbym w tym przypadku taki sam wynik.

Pułapka
Chociaż możesz wykorzystać obiekt określonej klasy do uzyskania dostępu do
atrybutu klasy, nie możesz przypisać nowej wartości do atrybutu klasy poprzez
obiekt. Jeśli chcesz zmienić wartość atrybutu klasy, uzyskaj do niego dostęp
poprzez nazwę klasy.

Tworzenie metody statycznej


Pierwszą metodą w klasie jest status():
def status():
print("\nOgólna liczba zwierzaków wynosi", Critter.total)

Napisałem tę definicję jako część tworzenia metody statycznej. Zwróć uwagę,


że definicja nie zawiera zmiennej self w swojej liście parametrów. Jest tak dlatego,
że podobnie jak wszystkie metody statyczne ma być wywoływana poprzez klasę, a nie
poprzez obiekt. Więc do metody nie zostanie przekazana referencja do obiektu i tym
samym nie potrzebuje ona parametru w rodzaju self służącego do przyjęcia takiej
referencji. Metody statyczne mogą oczywiście mieć parametry, lecz w tym konkretnym
przypadku nie potrzebowałem żadnego.
Chociaż powyższa definicja tworzy metodę o nazwie status, bezpośrednio przed
nią wstawiłem dekorator —możesz sobie to wyobrazić jako dekorowanie czy też
modyfikowanie funkcji lub metody. Ten dekorator ostatecznie tworzy metodę statyczną
o tej samej nazwie:
@staticmethod

Dzięki tym trzem wierszom kodu klasa dysponuje metodą statyczną status(), która
wyświetla ogólną liczbę obiektów klasy Critter poprzez wypisanie wartości atrybutu
klasy o nazwie total.
Tworząc swoją własną metodę statyczną, postępuj zgodnie z moim przykładem.
Zacznij od dekoratora @staticmethod i umieść po nim definicję metody klasy.
A ponieważ jest to metoda dla całej klasy, nie uwzględnisz w niej parametru self,
który jest niezbędny tylko w metodach obiektu.
240 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Wywoływanie metody statycznej


Wywoływanie metody statycznej jest proste. W pierwszym wierszu głównej części
programu wywołuję metodę statyczną:
Critter.status()

Jak mógłbyś się domyślić, zostanie wyświetlone 0, ponieważ nie zostały utworzone
żadne obiekty. Ale zwróć uwagę, że mogę wywołać tę metodę nawet wówczas, gdy nie
istnieje ani jeden obiekt. Ponieważ metody statyczne są wywoływane poprzez samą klasę,
nie muszą istnieć żadne obiekty tej klasy, zanim będziesz mógł te metody wywołać.
Następnie tworzę trzy obiekty. Potem ponownie wywołuję metodę status(), która
wyświetla komunikat stwierdzający, że istnieją trzy zwierzaki. Wszystko działa jak należy,
ponieważ w trakcie wykonywania kodu konstruktora przy tworzeniu każdego z tych
obiektów wartość atrybutu klasy total jest zwiększana o 1.

Hermetyzacja obiektów
Po raz pierwszy zetknąłeś się z koncepcją hermetyzacji w kontekście funkcji w podrozdziale
„Pojęcie hermetyzacji” w rozdziale 6. Zobaczyłeś, że funkcje są poddane hermetyzacji
i ukrywają szczegóły swoich wewnętrznych mechanizmów przed tą częścią programu,
która je wywołuje (zwaną klientem funkcji). Dowiedziałeś się, że klient dobrze
zdefiniowanej funkcji komunikuje się z nią jedynie poprzez jej parametry i wartości
zwrotne. Na ogół obiekty powinny być traktowane w taki sam sposób. Klienty powinny
komunikować się z obiektami poprzez parametry metod i ich wartości zwrotne. Generalnie
kod klienta powinien unikać bezpośredniej zmiany wartości atrybutu obiektu.
Jak zawsze, najlepiej pomaga konkretny przykład. Załóżmy, że masz do czynienia
z obiektem klasy Checking_Account (rachunek bieżący) z atrybutem balance (saldo).
Powiedzmy, że Twój program musi obsługiwać wypłaty z kont, które zmniejszają wartość
atrybutu balance obiektu o pewną kwotę. Aby dokonać wypłaty, kod klienta mógłby
po prostu odjąć określoną liczbę od wartości atrybutu balance. Taki bezpośredni dostęp
jest łatwy dla klienta, ale może spowodować problemy. Kod klienta może odjąć taką
liczbę, że saldo balance stanie się ujemne, co mogłoby być uważane za nie do zaakceptowania
(szczególnie przez bank). O wiele lepiej jest mieć do dyspozycji metodę o nazwie withdraw()
(wypłać), która pozwoli klientowi na zgłoszenie żądania wypłaty poprzez przekazanie jej
kwoty do metody. Wtedy sam obiekt może obsłużyć to żądanie. Jeśli kwota okaże się zbyt
duża, obiekt może sobie z tym poradzić, prawdopodobnie odrzucając transakcję. Obiekt
chroni swoje bezpieczeństwo, umożliwiając jedynie pośredni dostęp do swoich atrybutów
poprzez metody.
Używanie atrybutów i metod prywatnych 241

Używanie atrybutów i metod prywatnych


Domyślnie wszystkie atrybuty i metody obiektu są publiczne, co oznacza, że klient
może mieć do nich bezpośredni dostęp bądź je wywoływać. Aby wesprzeć hermetyzację,
możesz zdefiniować atrybut lub metodę jako prywatne, co oznaczałoby, że tylko inne
metody tego samego obiektu mogłyby łatwo uzyskać dostęp do takiego atrybutu lub
wywołać taką metodę.

Prezentacja programu Prywatny zwierzak


Program Prywatny zwierzak konkretyzuje obiekt, który ma zarówno prywatne, jak
i publiczne atrybuty i metody. Na rysunku 8.8 pokazano jego przykładowe uruchomienie.

Rysunek 8.8. Dostęp do atrybutu prywatnego i do metody prywatnej został uzyskany


w sposób pośredni

Omówię kod tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 8.; nazwa pliku to prywatny_zwierzak.py.

Tworzenie atrybutów prywatnych


Aby ograniczyć bezpośredni dostęp klientów do atrybutów obiektów, możesz użyć atrybutów
prywatnych. W konstruktorze tworzę dwa atrybuty, jeden publiczny i jeden prywatny:
# Prywatny zwierzak
# Demonstruje zmienne i metody prywatne

class Critter(object):
"""Wirtualny pupil"""
def __init__(self, name, mood):
242 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

print("Urodził się nowy zwierzak!")


self.name = name # atrybut publiczny
self.__mood = mood # atrybut prywatny

Dwa znaki podkreślenia, które rozpoczynają nazwę drugiego atrybutu, stanowią


dla Pythona informację, że jest to atrybut prywatny. Jeśli chcesz utworzyć swój własny
atrybut prywatny, wystarczy, że jego nazwę rozpoczniesz od dwóch znaków podkreślenia.

Wskazówka
Możesz utworzyć prywatny atrybut klasy poprzez umieszczenie na początku jego
nazwy dwóch znaków podkreślenia.

Dostęp do atrybutów prywatnych


Dostęp do prywatnego atrybutu obiektu z wnętrza definicji klasy obiektu nie podlega
dyskusji. (Pamiętaj, że atrybuty prywatne zostały wymyślone po to, aby odwieść
programistę od używania bezpośredniego dostępu do atrybutów w kodzie klienckim).
Ja wykorzystuję dostęp do atrybutu prywatnego w metodzie talk():
def talk(self):
print("\nJestem", self.name)
print("W tej chwili czuję się", self.__mood, "\n")

Ta metoda wyświetla wartość prywatnego atrybutu obiektu, który reprezentuje stan


nastroju zwierzaka.
Gdybym spróbował uzyskać dostęp do tego atrybutu spoza definicji klasy Critter,
miałbym kłopot. Oto sesja interaktywna, która powinna pokazać, co mam na myśli:
>>> crit = Critter(name = "Reksio", mood = "szczęśliwy")
Urodził się nowy zwierzak!
>>> print(crit.mood)
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
print(crit.mood)
AttributeError: 'Critter' object has no attribute 'mood'

Poprzez zgłoszenie wyjątku AttributeError Python informuje, że obiekt crit nie ma


atrybutu mood. Jeśli uważasz, że możesz przechytrzyć Pythona przez dodanie dwóch
początkowych znaków podkreślenia, jesteś w błędzie. To właśnie wypróbowałem
w następnej części mojej sesji interaktywnej:
>>> print(crit.__mood)
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
print(crit.__mood)
AttributeError: 'Critter' object has no attribute '__mood'

To także skutkuje zgłoszeniem wyjątku AttributeError. Python znów twierdzi,


że atrybut nie istnieje. Czy to oznacza, że wartość atrybutu prywatnego jest całkowicie
niedostępna na zewnątrz definicji jego klasy? Cóż, nie jest to prawda. Python ukrywa
Używanie atrybutów i metod prywatnych 243

atrybut za specjalną konwencją nazewniczą, chociaż dostęp do niego jest wciąż


technicznie możliwy. I właśnie to zrobiłem w kolejnej części mojej sesji interaktywnej:
>>> print(crit._Critter__mood)
szczęśliwy

Powyższa instrukcja wyświetla wartość tego nieuchwytnego atrybutu prywatnego,


którego wartością jest w tym przypadku łańcuch "szczęśliwy".
Skoro dostęp do atrybutów prywatnych jest technicznie możliwy, możesz pomyśleć:
do czego są one potrzebne? Cóż, w języku Python prywatność stanowi wskaźnik, że atrybut
lub metoda są przeznaczone tylko do wewnętrznego użytku obiektu. Dodatkowo pomaga
ona zapobiec nieumyślnemu dostępowi do takiego atrybutu czy metody.

Wskazówka
Nigdy nie powinieneś próbować bezpośredniego dostępu do prywatnych atrybutów
lub metod obiektu z zewnątrz definicji jego klasy.

Tworzenie metod prywatnych


Możesz utworzyć metodę prywatną w ten sam prosty sposób, w jaki tworzy się prywatny
atrybut: poprzez umieszczenie na początku jego nazwy dwóch znaków podkreślenia.
To właśnie w kolejnej definicji metody zawartej w tej klasie:
def __private_method(self):
print("To jest metoda prywatna.")

Jest to metoda prywatna, lecz każda z pozostałych metod klasy ma do niej łatwy
dostęp. Podobnie jak w przypadku atrybutów prywatnych, z dostępu do metod
prywatnych powinny z założenia korzystać własne metody obiektu.

Dostęp do metod prywatnych


Tak jak w przypadku atrybutów prywatnych, uzyskiwanie dostępu do prywatnych metod
obiektu w obrębie definicji jego klasy jest proste. W metodzie public_method()
wykorzystuję dostęp do prywatnej metody klasy:
def public_method(self):
print("To jest metoda publiczna.")
self.__private_method()

Metoda ta wyświetla łańcuch "To jest metoda publiczna", a potem wywołuję


metodę prywatną obiektu.
Podobnie jak prywatne atrybuty, metody prywatne nie są przeznaczone do tego,
aby klienty korzystały z bezpośredniego dostępu do nich. Wracając do mojej sesji
interaktywnej, próbuję uzyskać dostęp do prywatnej metody obiektu crit:
>>> crit.private_method()
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
244 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

crit.private_method()
AttributeError: 'Critter' object has no attribute 'private_method'

Ta próba powoduje zgłoszenie dobrze Ci znanego wyjątku AttributeError. Python


stwierdza, że obiekt crit nie zawiera metody o podanej nazwie. Python ukrywa metodę
przez zastosowanie takiej samej, specjalnej konwencji nazewniczej. Jeśli spróbuję
ponownie, dodając dwa początkowe znaki podkreślenia do nazwy metody, zderzę się
z tym samym komunikatem o błędzie:
>>> crit.__private_method()
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
crit.__private_method()
AttributeError: 'Critter' object has no attribute '__private_method'

Jednak dostęp do metod prywatnych z dowolnego miejsca w programie jest,


podobnie jak w przypadku atrybutów prywatnych, technicznie możliwy. Udowadnia
to końcowa część mojej sesji interaktywnej:
>>> crit._Critter__private_method()
To jest metoda prywatna.

Lecz jak już pewnie wiesz, klient nigdy nie powinien próbować bezpośredniego
dostępu do prywatnych metod obiektu.

Wskazówka
Możesz utworzyć prywatną metodę statyczną, rozpoczynając nazwę metody
dwoma znakami podkreślenia.

Respektowanie prywatności obiektu


W głównej części programu zachowuję się jak należy i nie próbuję dobierać się
do prywatnych atrybutów czy metod. Tworzę natomiast obiekt i wywołuję jego dwie
metody publiczne:
# część główna
crit = Critter(name = "Reksio", mood = "szczęśliwy")
crit.talk()
crit.public_method()

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Metoda __init__() obiektu klasy Critter, która jest automatycznie wywoływana


natychmiast po utworzeniu obiektu, oznajmia światu, że urodził się nowy zwierzak.
Metoda talk() obiektu crit informuje nas o samopoczuciu zwierzaka. Metoda
public_method() obiektu crit wyświetla łańcuch "To jest metoda publiczna.",
a następnie wywołuje metodę prywatną obiektu crit, która wypisuje łańcuch
"To jest metoda prywatna.". Wreszcie program kończy działanie.
Kontrolowanie dostępu do atrybutów 245

Kiedy należy stosować prywatność?


Czy więc teraz, gdy wiesz, jak stosować prywatność, powinieneś każdy atrybut w każdej
klasie definiować jako prywatny, aby chronić go przed złym światem zewnętrznym?
No nie. Prywatność jest podobna do dobrej przyprawy — używana z umiarem może
bardzo poprawić jakość Twojego kodu. Jeśli nie chcesz, aby klient wywoływał jakąś
metodę, zdefiniuj ją jako prywatną. Jeśli krytyczne znaczenie ma to, aby klient nigdy
nie korzystał z bezpośredniego dostępu do pewnego atrybutu, możesz uczynić go
prywatnym. Rozpowszechniona wśród wielu programistów Pythona filozofia polega
na zaufaniu, że klienty będą korzystać z metod obiektu i nie będą bezpośrednio zmieniać
jego atrybutów.

Wskazówka
Gdy piszesz klasę:
 twórz metody, aby zmniejszyć potrzebę korzystania z bezpośredniego dostępu
do atrybutów obiektu przez klienty;
 stosuj prywatność do tych atrybutów i metod, których rola w funkcjonowaniu
obiektów jest czysto wewnętrzna.
Kiedy używasz obiektu:
 minimalizuj bezpośrednie odczytywanie atrybutów obiektu;
 unikaj bezpośredniego zmieniania wartości atrybutów obiektu;
 nigdy nie staraj się uzyskać bezpośredniego dostępu do prywatnych
atrybutów i metod obiektu.

Kontrolowanie dostępu do atrybutów


Czasem zamiast odmawiać dostępu do atrybutu, możesz chcieć go tylko ograniczyć.
Możesz na przykład chcieć, aby kod klienta mógł odczytywać wartość jakiegoś atrybutu,
ale nie mógł jej zmieniać. Python udostępnia kilka narzędzi pomagających zrealizować
tego rodzaju wymaganie, a wśród nich właściwości. Umożliwiają one precyzyjne
zarządzanie dostępem do atrybutu i zmianami jego wartości.

Prezentacja programu Zwierzak z właściwością


Program Zwierzak z właściwością pozwala kodowi klienta na odczytywanie wartości
atrybutu obiektu klasy Critter, który odwołuje się do nazwy zwierzaka, lecz nakłada
ograniczenia, kiedy kod klienta usiłuje zmienić wartość, do której odwołuje się atrybut.
Jeśli kod klienta próbuje przypisać pusty łańcuch, program wyświetla odpowiedni
komunikat i nie dopuszcza do zmiany. Na rysunku 8.9 pokazano efekt działania tego
programu.
246 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Rysunek 8.9. Właściwość kontroluje dostęp do atrybutu obiektu klasy Critter


dotyczącego jego nazwy

Omówię kod tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 8.; nazwa pliku to zwierzak_z_wlasciwoscia.py.

Tworzenie właściwości
Jednym ze sposobów kontroli dostępu do prywatnego atrybutu jest utworzenie
właściwości — obiektu z metodami, które umożliwiają pośredni dostęp do atrybutów
i często nakładają pewien rodzaj ograniczenia na ten dostęp. Po napisaniu konstruktora
klasy Critter tworzę właściwość o nazwie name, aby umożliwić pośredni dostęp
do atrybutu prywatnego __name:
# Zwierzak z właściwością
# Demonstruje właściwości

class Critter(object):
"""Wirtualny pupil"""
def __init__(self, name):
print("Urodził się nowy zwierzak!")
self.__name = name

@property
def name(self):
return self.__name

Tworzę tę właściwość poprzez napisanie metody zwracającej wartość, do której chcę


zapewnić pośredni dostęp (w tym przypadku wartość prywatnego atrybutu __name), oraz
poprzedzam definicję metody dekoratorem @property. Właściwość ma taką samą nazwę
jak metoda — w tym przypadku name. Teraz mogę wykorzystać właściwość name
Kontrolowanie dostępu do atrybutów 247

dowolnego obiektu klasy Critter do pobrania wartości jego prywatnego atrybutu __name
wewnątrz lub na zewnątrz definicji klasy, przy użyciu dobrze Ci znanej notacji z kropką.
(Przykłady wykorzystania tej własności poznasz w następnym punkcie, „Dostęp do
właściwości”).
Aby utworzyć swoją własną właściwość, napisz metodę zwracającą wartość, do której
chcesz zapewnić pośredni dostęp, i poprzedź jej definicję dekoratorem @property.
Właściwość będzie mieć taką samą nazwę jak metoda. Jeśli umożliwiasz dostęp do
atrybutu prywatnego, powinieneś zastosować konwencję, która polega na nadaniu
właściwości nazwy utworzonej z nazwy tego atrybutu przez pominięcie początkowych
znaków podkreślenia, tak jak to zrobiłem w swoim programie.
Tworząc właściwość, możesz zapewnić dostęp w trybie odczytu do prywatnego
atrybutu. Właściwość może jednak zapewnić dostęp do zapisu, a nawet może nałożyć
pewne ograniczenia na ten dostęp. Umożliwiam dostęp w trybie zapisu, z pewnymi
ograniczeniami, do prywatnego atrybutu __name poprzez właściwość name:
@name.setter
def name(self, new_name):
if new_name == "":
print("Pusty łańcuch znaków nie może być imieniem zwierzaka.")
else:
self.__name = new_name
print("Zmiana imienia się powiodła.")

Rozpoczynam ten kod od dekoratora @name.setter. Poprzez użycie atrybutu setter


właściwości name sygnalizuję, że następująca po dekoratorze definicja metody określi
sposób ustawiania wartości tej właściwości. Swój własny dekorator służący do ustawiania
wartości właściwości możesz utworzyć, naśladując mój przykład: rozpocznij od symbolu
@ i wpisz po nim nazwę właściwości, kropkę (.) i słowo setter.
Po dekoratorze definiuję metodę o nazwie name, która jest wywoływana, gdy kod
klienta próbuje ustawić nową wartość przy użyciu właściwości. Łatwo zauważysz, że ta
metoda została nazwana name, tak samo jak sama właściwość; tak być musi. Kiedy się
ustala metodę typu setter w ten sposób, musi ona mieć taką samą nazwę jak
odpowiadająca jej właściwość.
W tej metodzie parametr new_name otrzymuje wartość reprezentującą nowe imię
zwierzaka. Jeśli ta wartość okaże się pustym łańcuchem znaków, atrybut __name pozostaje
niezmieniony i wyświetlany jest komunikat mówiący o tym, że próba zmiany imienia się
nie powiodła. W przeciwnym wypadku metoda ustawia wartość prywatnego atrybutu
__name na new_name i wyświetla komunikat stwierdzający, że zmiana imienia się udała.
Więc kiedy będziesz tworzyć metodę do ustawiania wartości właściwości, musisz
pamiętać, że jej definicja musi zawierać parametr przeznaczony do przyjęcia tej nowej
wartości, tak jak w tym przykładzie.
248 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Dostęp do właściwości
Dzięki utworzeniu właściwości name mogę pobrać imię zwierzaka, używając notacji
z kropką. Demonstruje to kolejna część programu:
def talk(self):
print("\nCześć! Jestem", self.name)

# część główna
crit = Critter("Reksio")
crit.talk()
Wyrażenie self.name wykorzystuje dostęp do właściwości name i wywołuje pośrednio
metodę, która zwraca wartość atrybutu __name. W tym akurat przypadku jest nią łańcuch
"Reksio". Ale właściwość name obiektu mogę wykorzystywać nie tylko wewnątrz definicji
jego klasy, lecz mogę jej używać także poza tą definicją, co też robię w następnym
fragmencie kodu:
print("\nImię mojego zwierzaka to:", end= " ")
print(crit.name)

Chociaż ten kod znajduje się na zewnątrz klasy Critter, dzieją się zasadniczo te same
rzeczy — wyrażenie crit.name realizuje dostęp do właściwości name obiektu klasy Critter
i pośrednio wywołuje metodę, która zwraca wartość __name. Także w tym przypadku jest
nią łańcuch "Reksio".
Następnie próbuję zmienić imię zwierzaka:
print("\nPróbuję zmienić imię mojego zwierzaka na Pucek...")
crit.name = "Pucek"

Instrukcja przypisania crit.name = "Pucek" wykorzystuje dostęp do właściwości name


obiektu i pośrednio wywołuje metodę, która się stara ustawić wartość atrybutu __name.
W tym przypadku do parametru new_name tej metody zostaje przekazana referencja do
łańcucha "Pucek", a ponieważ "Pucek" nie jest pustym łańcuchem, atrybut __name obiektu
przyjmuje wartość "Pucek" i zostaje wyświetlony komunikat "Zmiana imienia się powiodła.".
Następnie wyświetlam imię zwierzaka, wykorzystując właściwość name:
print("\nImię mojego zwierzaka to:", end= " ")
print(crit.name)

Program wyświetla komunikat Imię mojego zwierzaka to: Pucek.


Po czym próbuję zmienić imię zwierzaka na pusty łańcuch znaków:
print("\nPróbuję zmienić imię mojego zwierzaka na pusty łańcuch znaków...")
crit.name = ""

Tak jak poprzednio przypisanie wykorzystuje dostęp do właściwości name obiektu


i pośrednio wywołuje metodę, która próbuje ustawić wartość atrybutu __name. W tym
przypadku do metody poprzez parametr new_name jest przekazywana referencja do
pustego łańcucha znaków. W efekcie metoda wyświetla komunikat Pusty łańcuch znaków
nie może być imieniem zwierzaka i atrybut __name obiektu pozostaje niezmieniony.
Powrót do programu Opiekun zwierzaka 249

Na koniec, aby udowodnić, że imię zwierzaka nie zostało zastąpione pustym


łańcuchem znaków, wyświetlam je po raz ostatni:
print("Imię mojego zwierzaka to:", end= " ")
print(crit.name)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Powyższy kod wyświetla komunikat wskazujący, że zwierzak ma nadal na imię Pucek.

Powrót do programu Opiekun zwierzaka


Finalny program Opiekun zwierzaka łączy w sobie części klas, które poznawałeś w trakcie
lektury całego tego rozdziału. Zawiera on również system menu, którego już używałeś,
a który pozwala użytkownikowi na interakcję z jego własnym zwierzakiem. Omówię kod
tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć na stronie
internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 8.;
nazwa pliku to opiekun_zwierzaka.py.

Klasa Critter
Klasa Critter stanowi projekt obiektu, który reprezentuje zwierzaka należącego do
użytkownika. Klasa ta nie jest skomplikowana, a większość jej zawartości powinna Ci się
wydawać znajoma. Jest to jednak dość długi kawałek kodu, więc zmaganie się z nim
fragment po fragmencie jest sensownym podejściem.

Konstruktor
Konstruktor klasy inicjalizuje trzy publiczne atrybuty obiektu klasy Critter: name (imię),
hunger (głód) i boredom (nuda). Zauważ, że zarówno hunger, jak i boredom mają wartość
domyślną 0, co pozwala zwierzakowi być na początku w bardzo dobrym nastroju.
# Opiekun zwierzaka
# Wirtualny pupil, którym należy się opiekować

class Critter(object):
"""Wirtualny pupil"""
def __init__(self, name, hunger = 0, boredom = 0):
self.name = name
self.hunger = hunger
self.boredom = boredom

Jako programista Pythona przyjmuję w tej metodzie luźniejsze podejście i pozostawiam


atrybuty z ich domyślnym publicznym statusem. Zamierzam utworzyć wszystkie metody,
jakich (jak podejrzewam) klient będzie potrzebował, co powinno zachęcić klienta do
interakcji z obiektem klasy Critter tylko za pośrednictwem tych metod.
250 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

Metoda __pass_time()
Metoda __pass_time() jest prywatną metodą, która zwiększa poziom głodu i nudy
zwierzaka. Jest wywoływana na końcu każdej metody, w której zwierzak coś robi (je, bawi
się lub mówi), aby zasymulować upływ czasu. Zdefiniowałem tę metodę jako prywatną,
ponieważ powinna być wywoływana tylko przez inną metodę klasy. Zgodnie z moim
założeniem czas mija dla zwierzaka tylko wtedy, gdy ten coś robi (na przykład je, bawi się
lub mówi).
def __pass_time(self):
self.hunger += 1
self.boredom += 1

Właściwość mood
Właściwość mood reprezentuje nastrój zwierzaka. Przedstawiona niżej metoda oblicza
poziom tego nastroju. Sumuje ona wartości atrybutów hunger i boredom obiektu klasy
Critter oraz na podstawie tej sumy zwraca łańcuch reprezentujący nastrój —
"szczęśliwy", "zadowolony", "podenerwowany" lub "wściekły".
We właściwości mood interesujące jest to, że nie umożliwia ona dostępu do atrybutu
prywatnego. Jest tak, ponieważ łańcuch znaków, który reprezentuje nastrój zwierzaka nie
jest przechowywany jako część obiektu klasy Critter, lecz jest tworzony na bieżąco.
Właściwość mood udostępnia po prostu łańcuch zwracany przez metodę.
@property
def mood(self):
unhappiness = self.hunger + self.boredom
if unhappiness < 5:
m = "szczęśliwy"
elif 5 <= unhappiness <= 10:
m = "zadowolony"
elif 11 <= unhappiness <= 15:
m = "podenerwowany"
else:
m = "wściekły"
return m

Metoda talk()
Metoda talk() oznajmia światu, jaki jest nastrój zwierzaka, wykorzystując dostęp
do właściwości mood obiektu klasy Critter. Następnie wywołuje ona metodę prywatną
__pass_time().
def talk(self):
print("Nazywam się", self.name, "i jestem", self.mood, "teraz.\n")
self.__pass_time()

Metoda eat()
Metoda eat() zmniejsza poziom głodu zwierzaka o liczbę przekazaną poprzez parametr
food. Jeśli nie zostanie przekazana żadna wartość, parametr food otrzymuje domyślną
Powrót do programu Opiekun zwierzaka 251

wartość 4. Poziom głodu zwierzaka jest utrzymywany pod kontrolą i nie może spaść
poniżej wartości 0. Na koniec metoda wywołuje metodę prywatną __pass_time().
def eat(self, food = 4):
print("Mniam, mniam. Dziękuję.")
self.hunger -= food
if self.hunger < 0:
self.hunger = 0
self.__pass_time()

Metoda play()
Metoda play() zmniejsza poziom nudy zwierzaka o liczbę przekazaną poprzez parametr
fun. Jeśli nie zostanie przekazana żadna wartość, parametr fun otrzymuje domyślną
wartość 4. Poziom nudy zwierzaka jest utrzymywany pod kontrolą i nie może spaść
poniżej wartości 0. Na koniec metoda wywołuje metodę prywatną __pass_time().
def play(self, fun = 4):
print("Hura!")
self.boredom -= fun
if self.boredom < 0:
self.boredom = 0
self.__pass_time()

Utworzenie zwierzaka
Kod głównej części programu umieściłem w osobnej funkcji, main(). Na początku
programu pobieram nazwę zwierzaka od użytkownika. Następnie konkretyzuję nowy
obiekt klasy Critter. Ponieważ nie przekazuję wartości do parametrów hunger i boredom,
początkowa wartość obu atrybutów wynosi 0, a zwierzak rozpoczyna swe życie szczęśliwy
i zadowolony.
def main():
crit_name = input("Jak chcesz nazwać swojego zwierzaka?: ")
crit = Critter(crit_name)

Utworzenie systemu menu


Następnie utworzyłem znany Ci system menu. Jeśli użytkownik wprowadzi 0, program
kończy działanie. Jeśli użytkownik wprowadzi 1, zostaje wywołana metoda talk()
obiektu. Jeśli użytkownik wprowadzi liczbę 2, zostaje wywołana metoda eat() obiektu.
Jeśli zaś użytkownik wprowadzi liczbę 3, zostaje wywołana metoda play(). Jeśli użytkownik
wprowadzi jakąś inną wartość, zostanie poinformowany, że jego wybór był nieprawidłowy.
choice = None
while choice != "0":
print \
("""
Opiekun zwierzaka
252 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka

0 - zakończ
1 - słuchaj swojego zwierzaka
2 - nakarm swojego zwierzaka
3 - pobaw się ze swoim zwierzakiem
""")

choice = input("Wybierasz: ")


print()

# wyjdź z pętli
if choice == "0":
print("Do widzenia.")

# słuchaj swojego zwierzaka


elif choice == "1":
crit.talk()

# nakarm swojego zwierzaka


elif choice == "2":
crit.eat()

# pobaw się ze swoim zwierzakiem


elif choice == "3":
crit.play()

# nieznany wybór
else:
print("\nNiestety,", choice, "nie jest prawidłowym wyborem.")

Uruchomienie programu
Kolejny wiersz kodu wywołuje funkcję main() i rozpoczyna program. W ostatnim wierszu
program oczekuje na decyzję użytkownika przed zakończeniem swojego działania.
main()
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale wprowadziłem Cię w inny sposób programowania z użyciem obiektów
programowych. Dowiedziałeś się, że obiekty programowe mogą łączyć w sobie funkcje
i dane (w terminologii OOP — metody i atrybuty) i pod wieloma względami naśladują
obiekty świata realnego. Zobaczyłeś, jak pisać klasy stanowiące projekty obiektów.
Dowiedziałeś się o specjalnej metodzie zwanej konstruktorem, która jest wywoływana
automatycznie, gdy konkretyzowany jest nowy obiekt. Zobaczyłeś, jak tworzyć
i inicjalizować atrybuty obiektu za pomocą konstruktora. Dowiedziałeś się, jak tworzyć
elementy wspólne dla całej klasy, takie jak atrybuty klasy i metody statyczne. Potem
dowiedziałeś się, na czym polega hermetyzacja obiektów. Poznałeś sposoby pomagające
Podsumowanie 253

w jej zapewnieniu, łącznie z użyciem atrybutów i właściwości prywatnych. Dowiedziałeś


się również, że w dobrym projekcie obiektu wiele się czyni w kierunku zapewnienia
hermetyzacji. Na koniec zobaczyłeś te wszystkie koncepcje w działaniu przy tworzeniu
wymagającego wirtualnego pupila, który domaga się ciągłej opieki.

Sprawdź swoje umiejętności


1. Ulepsz program Opiekun zwierzaka, pozwalając użytkownikowi na określenie
ilości pożywienia podawanego zwierzakowi i czasu poświęconego na zabawę
z nim. Wartości te powinny wpływać na szybkość spadku poziomu głodu
i nudy u zwierzaka.
2. Napisz program, który symuluje telewizor, tworząc go jako obiekt. Użytkownik
powinien mieć możliwość wprowadzenia numeru kanału oraz zwiększenia
bądź zmniejszenia głośności. Zastosuj mechanizm zapewniający utrzymywanie
numeru kanału i poziomu głośności we właściwych zakresach.
3. Utwórz w programie Opiekun zwierzaka mechanizm „tylnych drzwi”,
który pokazuje dokładne wartości atrybutów obiektu. Zrealizuj to poprzez
wyświetlenie obiektu po wprowadzeniu przez użytkownika ukrytej wartości,
która nie została uwzględniona w menu. (Wskazówka: dodaj specjalną metodę
__str__() do klasy Critter).
4. Utwórz program Farma zwierzaków, konkretyzując kilka obiektów klasy
Critter i śledząc ich stan poprzez listę. Naśladuj program Opiekun zwierzaka,
lecz zamiast wymagać od użytkownika zajmowania się pojedynczym zwierzakiem,
zażądaj od niego opieki nad całą farmą. Każda pozycja menu powinna umożliwić
użytkownikowi wykonanie pewnej czynności obejmującej wszystkie zwierzaki
(„nakarm wszystkie zwierzaki”, „pobaw się ze wszystkimi zwierzakami” czy
„słuchaj wszystkich zwierzaków”). Aby program był ciekawszy, nadaj każdemu
zwierzakowi wybrany losowo początkowy poziom głodu i nudy.
254 Rozdział 8. Obiekty programowe. Program Opiekun zwierzaka
9
Programowanie obiektowe.
Gra Blackjack

W ostatnim rozdziale poznawałeś obiekt programowy. W niemal każdym programie,


z jakim się spotkałeś, występował pojedynczy obiekt. To znakomity sposób na
rozpoczęcie opanowywania podstaw działania obiektów, lecz prawdziwa siła programowania
obiektowego może zostać doceniona dopiero wtedy, gdy zobaczy się grupę obiektów
we wzajemnej współpracy. W tym rozdziale nauczysz się tworzyć wiele obiektów
i definiować między nimi relacje, tak aby mogły wchodzić ze sobą w interakcję.
W szczególności nauczysz się:
 tworzyć obiekty różnych klas w tym samym programie,
 umożliwiać obiektom wzajemne komunikowanie się,
 tworzyć bardziej złożone obiekty jako kombinacje prostszych,
 tworzyć klasy pochodne istniejących klas,
 rozszerzać definicje istniejących klas,
 przesłaniać definicje metod istniejących klas.

Wprowadzenie do gry Blackjack


Finalnym projektem tego rozdziału jest uproszczona wersja karcianej gry blackjack.
Zasady gry są następujące. Graczom są rozdawane karty o pewnych wartościach punktowych.
Każdy gracz stara się uzyskać sumę 21 punktów, tak aby jej nie przekroczyć. Karty
numerowane (2 – 10) są liczone zgodnie z wartością widniejącą na ich licu. As ma
wartość 1 albo 11 (w zależności od tego, co jest dla gracza korzystniejsze), a każdy walet,
dama i król ma wartość 10 punktów.
Komputer rozdaje karty i współzawodniczy z graczami w liczbie od jednego do
siedmiu. Otwierając rundę, komputer rozdaje wszystkim uczestnikom gry (nie wyłączając
samego siebie) po dwie karty. Gracze widzą wszystkie swoje karty, a komputer wyświetla
nawet ich sumę. Jedna z kart rozdającego pozostaje jednak tymczasowo ukryta.
256 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Następnie gracz otrzymuje szansę dobrania dodatkowych kart. Każdy z graczy może
jednorazowo dobrać jedną kartę i powtarzać tę czynność tak długo, jak chce. Lecz kiedy
suma punktów gracza przekroczy 21 (jest to tak zwana „fura”), gracz przegrywa. Jeśli
każdy z graczy dostanie furę, komputer odsłania swoją pierwszą kartę i runda się kończy.
W przeciwnym wypadku gra toczy się dalej. Komputer musi dobierać dodatkowe karty,
dopóki suma jego punktów jest mniejsza niż 17. Jeśli komputer dostanie furę, wszyscy
gracze, którzy sami jej nie dostali, zostają zwycięzcami. W przeciwnym razie suma
punktów każdego z graczy pozostających w grze jest porównywana z sumą uzyskaną
przez komputer. Jeśli suma punktów uzyskana przez gracza jest większa, gracz wygrywa.
Jeśli jest mniejsza, przegrywa. Jeśli obie sumy są jednakowe, gracz remisuje
z komputerem. Gra została pokazana na rysunku 9.1.

Rysunek 9.1. Jeden z graczy wygrywa; drugi nie ma takiego szczęścia

Wysyłanie i odbieranie komunikatów


W pewnym sensie program zorientowany obiektowo przypomina ekosystem, a obiekty
są podobne do żywych organizmów. Aby utrzymać ekosystem w kwitnącym stanie,
organizmy muszą na siebie wzajemnie oddziaływać. Ta sama zasada dotyczy
programowania obiektowego. Aby program był użyteczny, obiekty muszą ze sobą
współdziałać według dobrze zdefiniowanych reguł. Zgodnie z terminologią OOP obiekty
współdziałają poprzez przesyłanie komunikatów wzajemnie do siebie. Na poziomie
praktycznym polega to na wywoływaniu metod jednego obiektu przez drugi. Może to
wyglądać trochę niegrzecznie, lecz w istocie rzeczy jest o wiele bardziej uprzejme niż
bezpośrednie wykorzystywanie przez jeden obiekt dostępu do atrybutów drugiego.
Wysyłanie i odbieranie komunikatów 257

Prezentacja programu Pogromca Obcych


Program Pogromca Obcych symuluje grę akcji, w której gracz niszczy kosmitę. W tym
programie bohater razi najeźdźcę, który umiera (ale dopiero po wygłoszeniu wspaniałej
mowy pożegnalnej). Program dokonuje tego wszystkiego, kiedy jeden obiekt przesyła
do drugiego komunikat. Na rysunku 9.2 pokazano wynik działania programu.

Rysunek 9.2. Opis walki jest rezultatem wymiany komunikatu między obiektami

Pod względem technicznym program konkretyzuje obiekt klasy Player (gracz),


przypisując go do zmiennej hero (bohater), oraz obiekt klasy Alien (obcy, kosmita),
przypisując go do zmiennej invader (najeźdźca). Kiedy zostaje wywołana metoda blast()
(zniszcz) obiektu hero z argumentem invader, obiekt hero wywołuje metodę die()
(umieraj) obiektu invader. W języku potocznym oznacza to, że kiedy gracz niszczy
kosmitę, wysyła do niego komunikat nakazujący mu umrzeć. Rysunek 9.3 zawiera
wizualne przedstawienie wymiany komunikatu.

Rysunek 9.3. Obiekt klasy Player reprezentowany przez zmienną hero wysyła komunikat
do obiektu invader klasy Alien
258 Rozdział 9. Programowanie obiektowe. Gra Blackjack

W świecie rzeczywistym
Schemat, który utworzyłem, aby pokazać dwa obiekty wymieniające między sobą
komunikat, jest dość prosty. Ale w przypadku wielu obiektów i wielu zachodzących
między nimi relacji, podobne schematy mogą się stać skomplikowane.
W rzeczywistości istnieją rozmaite formalne metody służące do odwzorowywania
projektów programowych. Jedną z najpopularniejszych jest Zunifikowany Język
Modelowania (ang. Unified Modeling Language — UML) — język notacyjny,
który jest szczególnie użyteczny przy wizualizacji systemów obiektowych.

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 9.; nazwa pliku
to pogromca_obcych.py.
# Pogromca Obcych
# Demonstruje współdziałanie obiektów

class Player(object):
""" Gracz w grze strzelance. """
def blast(self, enemy):
print("Gracz razi wroga.\n")
enemy.die()

class Alien(object):
""" Obcy w grze strzelance. """
def die(self):
print("Obcy z trudem łapie oddech, 'To już koniec. Ale wielki koniec... \n" \
"Tak, już robi się ciemno. Powiedz moim dwóm milionom larw, że je
kochałem... \n" \
"Żegnaj, okrutny Wszechświecie.'")

# main
print("\t\tŚmierć Obcego\n")

hero = Player()
invader = Alien()
hero.blast(invader)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Przesyłanie komunikatu
Zanim sprawisz, że jeden obiekt prześle komunikat do drugiego, musisz najpierw
dysponować dwoma obiektami! Więc tworzę dwa obiekty w głównej części programu.
Najpierw tworzę obiekt klasy Player i przypisuję go do zmiennej hero. Następnie tworzę
obiekt klasy Alien i przypisuję go do zmiennej invader.
Od następnego wiersza kod staje się interesujący. Poprzez wyrażenie
hero.blast(invader) wywołuję metodę blast() obiektu hero i przekazuję invader —
obiekt klasy Alien — jako argument. Po zbadaniu definicji metody blast() widzisz,
że metoda przyjmuje ten obiekt poprzez swój parametr enemy (wróg). Więc kiedy
Tworzenie kombinacji obiektów 259

wykonywana jest metoda blast(), parametr enemy zawiera referencję do obiektu klasy
Alien. Po wyświetleniu komunikatu metoda blast() wywołuje metodę die() obiektu
klasy Alien poprzez wyrażenie enemy.die(). W zasadzie obiekt klasy Player przesyła
komunikat do obiektu klasy Alien poprzez wywołanie jego metody die().

Odebranie komunikatu
Obiekt klasy Alien odbiera komunikat od obiektu klasy Player w tym sensie, że
wywoływana jest jego metoda die(). Wówczas metoda die() obiektu klasy Alien
wyświetla melodramatyczne pożegnanie.

Tworzenie kombinacji obiektów


W realnym świecie interesujące obiekty są zazwyczaj zbudowane z innych, niezależnych
obiektów. Na przykład pojazd uczestniczący w wyścigach równoległych może być
postrzegany jako pojedynczy obiekt, który się składa z oddzielnych obiektów, takich jak
karoseria, opony i silnik. Innym razem możesz patrzeć na obiekt jak na kolekcję innych
obiektów. Na przykład ogród zoologiczny może być postrzegany jako kolekcja zwierząt.
Zatem możesz naśladować te rodzaje związków między obiektami w programowaniu
OOP. Mógłbyś napisać klasę Drag_Racer (pojazd wyścigowy), która zawiera atrybut
engine będący referencją do obiektu klasy Race_Engine (silnik pojazdu wyścigowego).
Lub też napisać klasę Zoo, która ma atrybut animals będący listą różnych obiektów klasy
Animal (zwierzę). Łączenie obiektów w taki sposób umożliwia Ci tworzenie z prostszych
obiektów bardziej złożonych.

Prezentacja programu Gra w karty


Program Gra w karty wykorzystuje obiekty do reprezentowania poszczególnych kart
do gry, których mógłbyś użyć w grze blackjack lub świnie (w zależności od Twoich
upodobań… oraz tolerancji na tracenie pieniędzy). Program idzie dalej, tworząc
reprezentację zestawu kart na ręku w postaci obiektu, który jest kolekcją obiektów karty.
Wyniki działania programu pokazano na rysunku 9.4.
Omówię kod tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 9.; nazwa pliku to gra_w_karty.py.

Utworzenie klasy Card


Pierwszą rzeczą, jaką robię w programie, jest utworzenie klasy Card dla obiektów,
które reprezentują karty do gry.
260 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Rysunek 9.4. Każdy obiekt klasy Hand (ręka) jest kolekcją obiektów klasy Card (karta)

# Gra w karty
# Demonstruje tworzenie kombinacji obiektów

class Card(object):
""" Karta do gry. """
RANKS = ["A", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "J", "Q", "K"]
SUITS = ["c", "d", "h", "s"]

def __init__(self, rank, suit):


self.rank = rank
self.suit = suit

def __str__(self):
rep = self.rank + self.suit
return rep

Każdy obiekt klasy Card ma atrybut rank, który reprezentuje rangę karty. Możliwe
jej wartości zostały wyszczególnione w atrybucie klasy o nazwie RANKS. Symbol "A"
reprezentuje asa, symbole od "2" do "10" reprezentują odpowiadające im wartości
liczbowe, symbol "J" reprezentuje waleta (ang. jack), "Q" — damę (ang. queen),
a "K" — króla (ang. king).
Każda karta ma również atrybut suit, który reprezentuje kolor karty. Możliwe
wartości tego atrybutu zostały wymienione w atrybucie klasy o nazwie SUITS. Litera "c"
reprezentuje trefle (ang. clubs), "d" oznacza kara (ang. diamonds), "h" symbolizuje kiery
(ang. hearts), a "s" reprezentuje piki (ang. spades). Więc obiekt z atrybutem rank
o wartości "A" i atrybutem suit o wartości "d" reprezentuje asa karo.
Metoda specjalna __str__() zwraca po prostu konkatenację atrybutów rank i suit,
aby obiekt mógł zostać wyświetlony.
Tworzenie kombinacji obiektów 261

Utworzenie klasy Hand


Następną rzeczą, jaką robię w programie, jest utworzenie klasy Hand do generowania
obiektów będących kolekcją obiektów klasy Card:
class Hand(object):
""" Ręka - karty do gry w ręku gracza. """
def __init__(self):
self.cards = []

def __str__(self):
if self.cards:
rep = ""
for card in self.cards:
rep += str(card) + " "
else:
rep = "<pusta>"
return rep

def clear(self):
self.cards = []

def add(self, card):


self.cards.append(card)

def give(self, card, other_hand):


self.cards.remove(card)
other_hand.add(card)

Nowy obiekt klasy Hand zawiera atrybut cards, który został zamierzony jako lista
obiektów klasy Card. Więc każdy pojedynczy obiekt klasy Hand ma atrybut, który jest listą
być może wielu innych obiektów.
Metoda specjalna __str__() zwraca łańcuch znaków, który reprezentuje całą rękę
(karty w ręku gracza). Metoda ta iteruje po wszystkich obiektach klasy Card zawartych
w obiekcie klasy Hand i dołącza (poprzez konkatenację) do tworzonego łańcucha znaków
ich łańcuchowe reprezentacje. Jeśli obiekt klasy Hand nie zawiera żadnych obiektów klasy
Card, zwracany jest łańcuch "<pusta>".
Metoda clear() kasuje listę kart poprzez przypisanie pustej listy do atrybutu cards
obiektu.
Metoda add() dodaje obiekt do atrybutu cards.
Metoda give() usuwa obiekt karty z obiektu klasy Hand i dołącza go do innego
obiektu klasy Hand poprzez wywołanie metody add() tego drugiego obiektu. Mówiąc
inaczej, pierwszy obiekt klasy Hand wysyła do drugiego obiektu tej samej klasy komunikat
z poleceniem dodania obiektu klasy Card.

Operowanie obiektami klasy Card


# część główna
card1 = Card(rank = "A", suit = "c")
262 Rozdział 9. Programowanie obiektowe. Gra Blackjack

print("Wyświetlam obiekt karty (klasy Card):")


print(card1)

card2 = Card(rank = "2", suit = "c")


card3 = Card(rank = "3", suit = "c")
card4 = Card(rank = "4", suit = "c")
card5 = Card(rank = "5", suit = "c")
print("\nWyświetlam resztę obiektów po jednym na raz:")
print(card2)
print(card3)
print(card4)
print(card5)

Obiekt klasy Card utworzony jako pierwszy ma atrybut rank o wartości równej "A"
oraz atrybut suit o wartości "c". Kiedy wyświetlam ten obiekt, na ekranie pojawia się
tekst Ac. Taki sam schemat ma zastosowanie do pozostałych obiektów.

Grupowanie obiektów klasy Card


przy użyciu obiektu klasy Hand
Następnie tworzę obiekt klasy Hand, przypisuję go do zmiennej my_hand i wyświetlam go:
my_hand = Hand()
print("\nWyświetlam zawartość mojej ręki przed dodaniem jakichkolwiek kart:")
print(my_hand)

Ponieważ atrybut cards obiektu to pusta lista, wykonanie na obiekcie operacji print
skutkuje wyświetleniem tekstu <pusta>.
Następnie dodaję do obiektu my_hand pięć obiektów klasy Card i wypisuję go
ponownie:
my_hand.add(card1)
my_hand.add(card2)
my_hand.add(card3)
my_hand.add(card4)
my_hand.add(card5)
print("\nWyświetlam zawartość mojej ręki po dodaniu 5 kart:")
print(my_hand)

Tym razem zostaje wyświetlony tekst Ac 2c 3c 4c 5c.


Potem tworzę drugi obiekt klasy Hand, your_hand. Wykorzystując metodę give()
obiektu my_hand, przekazuję dwie karty z obiektu my_hand do obiektu your_hand.
Po czym wyświetlam obie ręce:
your_hand = Hand()
my_hand.give(card1, your_hand)
my_hand.give(card2, your_hand)
print("\nPrzekazuję pierwsze dwie karty z mojej ręki do Twojej.")
print("Twoja ręka:")
print(your_hand)
print("Moja ręka:")
print(my_hand)
Wykorzystanie dziedziczenia do tworzenia nowych klas 263

Jak mógłbyś się spodziewać, obiekt your_hand zostaje wyświetlony jako Ac 2c,
podczas gdy my_hand pojawia się na ekranie jako 3c 4c 5c.
W końcu wywołuję metodę clear() obiektu my_hand i wyświetlam go po raz ostatni:
my_hand.clear()
print("\nMoja ręka po usunięciu z niej kart:")
print(my_hand)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Tak jak być powinno, wyświetlony zostaje tekst <pusta>.

Wykorzystanie dziedziczenia
do tworzenia nowych klas
Jednym z kluczowych elementów programowania obiektowego jest dziedziczenie, które
umożliwia oparcie nowej klasy na już istniejącej. Dzięki temu nowa klasa automatycznie
otrzymuje (czyli dziedziczy) wszystkie metody i atrybuty istniejącej klasy — jest to jakby
skorzystanie za darmo z całej pracy włożonej w pisanie klasy bazowej!

Pułapka
W Pythonie jest możliwe utworzenie nowej klasy, która bezpośrednio dziedziczy
po więcej niż jednej klasie. Jest to nazywane dziedziczeniem wielokrotnym.
Jednak takie podejście wprowadza szereg komplikacji. Więc zapewne będzie
najlepiej, jeśli jako początkujący programista będziesz unikać stosowania
dziedziczenia wielokrotnego.

Rozszerzanie klasy poprzez dziedziczenie


Dziedziczenie jest szczególnie użyteczne, kiedy chcesz utworzyć bardziej
wyspecjalizowaną wersję istniejącej klasy. Jak już wcześniej się dowiedziałeś, dzięki
dziedziczeniu po istniejącej klasie nowa klasa otrzymuje wszystkie metody i atrybuty tej
pierwszej. Lecz możesz także dodawać metody i atrybuty do nowej klasy, aby poszerzyć
funkcjonalność jej obiektów.
Wyobraź sobie na przykład, że Twoja klasa Drag_Racer definiuje pojazd wyścigowy
z metodami stop() i go(). Mógłbyś utworzyć nową klasę dla wyspecjalizowanego typu
pojazdu wyścigowego — który potrafi czyścić swoją przednią szybę (kiedy pędzisz
z prędkością 400 kilometrów na godzinę, masz na szybie masę zmiażdżonych owadów)
— opierając ją na istniejącej klasie Drag_Racer. Twoja nowa klasa odziedziczyłaby
automatycznie po klasie Drag_Racer metody stop() i go(). Więc pozostałby Ci tylko
zdefiniowanie nowej metody odpowiedzialnej za czyszczenie przedniej szyby, i nowa
klasa byłaby gotowa.
264 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Prezentacja programu Gra w karty 2.0


Program Gra w karty 2.0 został oparty na programie Gra w karty. W nowej wersji
wprowadziłem klasę Deck opisującą talię kart do gry. Jednak w odróżnieniu od innych
klas, z którymi się spotkałeś, klasa Deck została oparta na istniejącej klasie Hand.
W rezultacie klasa Deck automatycznie dziedziczy wszystkie metody klasy Hand. Tworzę
klasę Deck w ten właśnie sposób, ponieważ talię kart można faktycznie potraktować jako
wyspecjalizowaną rękę kart. Jest to ręka, lecz z dodatkowymi zachowaniami. Talia potrafi
wykonać wszystkie czynności ręki. Jest to kolekcja kart. Mogę przekazać kartę do innej
ręki itd. Lecz co najważniejsze, talia potrafi robić kilka rzeczy, które wykraczają poza
możliwości ręki. Talia może zostać potasowana i może rozdawać karty wielu rękom.
Program Gra w karty 2.0 tworzy talię, która rozdaje karty dwóm różnym rękom.
Na rysunku 9.5 przedstawiono wyniki działania programu. Wyniki, jakie Ty uzyskasz,
wykonując ten program, będą się prawdopodobnie różnić od pokazanych na rysunku,
ponieważ talia jest tasowana w sposób losowy.

Rysunek 9.5. Klasa Deck dziedziczy wszystkie metody klasy Hand

Omówię kod tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 9.; nazwa pliku to gra_w_karty2.py.
Rozszerzanie klasy poprzez dziedziczenie 265

Utworzenie klasy bazowej


Rozpoczynam nowy program podobnie jak jego starą wersję. Pierwsze dwie klasy Card
i Hand są takie same jak przedtem:
# Gra w karty 2.0
# Demonstruje dziedziczenie - rozszerzanie klasy

class Card(object):
""" Karta do gry. """
RANKS = ["A", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "J", "Q", "K"]
SUITS = ["c", "d", "h", "s"]

def __init__(self, rank, suit):


self.rank = rank
self.suit = suit

def __str__(self):
rep = self.rank + self.suit
return rep

class Hand(object):
""" Ręka - karty do gry w ręku gracza. """
def __init__(self):
self.cards = []

def __str__(self):
if self.cards:
rep = ""
for card in self.cards:
rep += str(card) + "\t"
else:
rep = "<pusta>"
return rep

def clear(self):
self.cards = []

def add(self, card):


self.cards.append(card)

def give(self, card, other_hand):


self.cards.remove(card)
other_hand.add(card)

Dziedziczenie po klasie bazowej


Kolejną moją czynnością jest utworzenie klasy Deck. Z nagłówka tej klasy możesz
odczytać, że jest oparta na klasie Hand:
class Deck(Hand):
266 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Klasa Hand nazywa się klasą bazową, ponieważ klasa Deck jest na niej oparta. Klasa
Deck jest uważana za klasę pochodną, ponieważ część swojej definicji czerpie z klasy Hand.
W wyniku tej relacji klasa Deck dziedziczy wszystkie metody klasy Hand. Więc nawet
gdybym nie zdefiniował ani jednej nowej metody w tej klasie, obiekty klasy Deck miałyby
wciąż wszystkie metody zdefiniowane w klasie Hand:
 __init__(),
 __str__(),
 clear(),
 add(),
 give().

Jeśli to pomoże, na potrzeby tego prostego przykładu możesz sobie nawet wyobrazić,
że skopiowałeś wszystkie metody klasy Hand i wkleiłeś je prosto do klasy Deck dzięki
dziedziczeniu.

Rozszerzenie klasy pochodnej


Możesz rozszerzyć klasę pochodną poprzez zdefiniowanie w niej nowych metod. Robię
to właśnie w definicji klasy Deck:
class Deck(Hand):
""" Talia kart do gry. """
def populate(self):
for suit in Card.SUITS:
for rank in Card.RANKS:
self.add(Card(rank, suit))

def shuffle(self):
import random
random.shuffle(self.cards)

def deal(self, hands, per_hand = 1):


for rounds in range(per_hand):
for hand in hands:
if self.cards:
top_card = self.cards[0]
self.give(top_card, hand)
else:
print("Nie mogę dalej rozdawać. Zabrakło kart!")

Więc oprócz wszystkich metod, jakie klasa Deck dziedziczy, ma ona następujące nowe
metody:
 populate(),
 shuffle(),
 deal().
Rozszerzanie klasy poprzez dziedziczenie 267

Z perspektywy kodu klienckiego każda metoda jest tak samo ważna jak pozostałe —
niezależnie od tego, czy została odziedziczona po klasie Hand, czy też zdefiniowana
w klasie Deck. A wszystkie metody obiektu klasy Deck są wywoływane w taki sam sposób,
przy użyciu notacji z kropką.

Korzystanie z klasy pochodnej


Pierwszą rzeczą, jaką robię w głównej części programu, jest skonkretyzowanie nowego
obiektu klasy Deck:
# część główna
deck1 = Deck()

Przyglądając się tej klasie, zauważysz, że nie definiuję w niej konstruktora. Lecz
klasa Deck dziedziczy konstruktor klasy Hand, więc ta właśnie metoda jest automatycznie
wywoływana na rzecz nowo utworzonego obiektu klasy Deck. W rezultacie nowy
obiekt klasy Deck uzyskuje atrybut cards, który zostaje zainicjalizowany jako pusta lista,
zupełnie tak samo, jakby to miało miejsce w przypadku każdego nowo utworzonego
obiektu klasy Hand. Na koniec nowy obiekt zostaje przypisany do zmiennej deck1.
Teraz wyposażony w nową (lecz pustą) talię wyświetlam ją:
print("Utworzyłem nową talię.")
print("Talia:")
print(deck1)

Nie zdefiniowałem także w klasie Deck metody specjalnej __str__(), ale tak jak
poprzednio, klasa Deck dziedziczy tę metodę po klasie Hand. Ponieważ talia nie zawiera
kart, kod wyświetla tekst <pusta>. Jak dotąd talia wydaje się całkowicie przypominać
rękę. Tak się dzieje, ponieważ talia jest wyspecjalizowanym typem ręki. Pamiętaj,
że talia może wykonywać wszystkie czynności ręki plus coś więcej.
Pusta talia jest mało interesująca, więc wywołuję metodę populate() obiektu,
która umieszcza w talii tradycyjne 52 karty:
deck1.populate()

Wreszcie zrobiłem z talią coś, czego nie mógłbym zrobić z ręką. To dlatego,
że metoda populate() jest nową metodą, którą zdefiniowałem w klasie Deck. Metoda
populate() tworzy w pętli 52 możliwe kombinacje wartości zawartych na listach
Card.SUITS i Card.RANKS (każda karta z prawdziwej talii jest w ten sposób reprezentowana).
Dla każdej kombinacji metoda tworzy nowy obiekt klasy Card, który dodaje do talii.
Następnie wyświetlam talię:
print("\nDodałem do talii komplet kart.")
print("Talia:")
print(deck1)

Tym razem zostają wyświetlone wszystkie 52 karty! Ale jeśli przyjrzysz się uważnie,
dostrzeżesz, że ich porządek jest oczywisty. Aby było ciekawiej, tasuję tę talię:
deck1.shuffle()
268 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Definiuję metodę shuffle() w klasie Deck. Importuję moduł random, a następnie


wywołuję funkcję random.shuffle() z atrybutem cards obiektu jako argumentem. Jak
można odgadnąć, funkcja random.shuffle() przemieszcza elementy listy, ustawiając je
w przypadkowej kolejności. Więc wszystkie elementy atrybutu cards zostają potasowane.
Doskonale.
Teraz, kiedy porządek kart jest przypadkowy, wyświetlam talię ponownie:
print("\nPotasowałem talię kart.")
print("Talia:")
print(deck1)

Następnie tworzę dwa obiekty klasy Hand i umieszczam je na liście, którą przypisuję
do zmiennej hands:
my_hand = Hand()
your_hand = Hand()
hands = [my_hand, your_hand]

Potem rozdaję karty, dodając po pięć kart do każdej ręki:


deck1.deal(hands, per_hand = 5)

Metoda deal() jest nową metodą, którą definiuję w klasie Deck. Przyjmuje ona dwa
argumenty: listę rąk i liczbę kart, jaką należy rozdać każdej ręce. Metoda daje każdej ręce
po jednej karcie z talii. Jeśli w talii brakuje kart, wyświetla komunikat Nie mogę dalej
rozdawać. Zabrakło kart!. Metoda powtarza ten proces tyle razy, ile wynosi liczba kart,
która ma zostać przekazana każdej ręce. Więc wykonanie powyższego wiersza kodu
skutkuje przekazaniem każdej ręce (my_hand i your_hand) pięciu kart z talii deck1.
Aby zobaczyć wyniki rozdania,x wyświetlam jeszcze raz obie ręce i talię:
print("\nRozdałem sobie i Tobie po 5 kart.")
print("Moja ręka:")
print(my_hand)
print("Twoja ręka:")
print(your_hand)
print("Talia:")
print(deck1)

Patrząc na wyświetlone dane, możesz sprawdzić, że każda ręka ma 5 kart, a talia liczy
ich tylko 42.
Na koniec doprowadzam talię z powrotem do jej stanu początkowego poprzez
usunięcie z niej kart:
deck1.clear()
print("\nUsunąłem zawartość talii.")

A potem wyświetlam talię po raz ostatni:


print("Talia:", deck1)
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")
Modyfikowanie zachowania odziedziczonych metod 269

Modyfikowanie zachowania odziedziczonych


metod
Zobaczyłeś, jak można rozszerzyć klasę poprzez dodanie nowych metod do klasy
pochodnej. Ale możesz również przedefiniować sposób działania odziedziczonej metody
klasy bazowej w klasie pochodnej. Ten sposób postępowania jest znany jako przesłanianie
metody. Kiedy przesłaniasz metodę klasy bazowej, masz dwie możliwości wyboru.
Możesz utworzyć metodę o zupełnie nowej funkcjonalności, lecz możesz również
włączyć w nią funkcjonalność przesłanianej metody klasy bazowej.
Weźmy ponownie za przykład klasę Drag_Racer. Powiedzmy, że metoda stop()
oznacza po prostu użycie hamulców pojazdu wyścigowego. Jeśli chcesz utworzyć nową
klasę pojazdu wyścigowego, który potrafi się zatrzymać jeszcze szybciej (dzięki rozwinięciu
spadochronu za pojazdem), możesz utworzyć na podstawie klasy Drag_Racer nową klasę
pochodną Parachute_Racer i przesłonić metodę stop() klasy bazowej. Mógłbyś napisać
nową metodę stop(), która wywoływałaby metodę stop() pierwotnej klasy Drag_Racer
(która wykorzystuje hamulce pojazdu), a potem definiowałaby czynność rozwijania przez
pojazd spadochronu.

Prezentacja programu Gra w karty 3.0


Program Gra w karty 3.0 tworzy dwie nowe klasy kart do gry jako pochodne klasy Card,
z którą już miałeś do czynienia. Pierwsza nowa klasa definiuje karty, które nie mogą
zostać wyświetlone. Dokładniej, jeśli próbujesz wyświetlić obiekt tej klasy, pojawia się
tekst <niewyświetlalna>. Kolejna klasa definiuje karty, które mogą być odkryte lub
zakryte. Kiedy wyświetlasz obiekt tej klasy, są dwa możliwe wyniki. Jeśli karta jest
odkryta, zostaje wyświetlona w taki sam sposób jak obiekt klasy Card. Lecz jeśli karta jest
zakryta, zostaje wyświetlony tekst XX. Na rysunku 9.6 pokazano przykładowe
uruchomienie tego programu.

Rysunek 9.6. W wyniku przesłonięcia odziedziczonej metody __str__()


obiekty różnych klas pochodnych są różnie wyświetlane
270 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Omówię kod tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 9.; nazwa pliku to gra_w_karty3.py.

Utworzenie klasy bazowej


Aby utworzyć nową klasę pochodną, musisz zacząć od klasy bazowej. W tym programie
wykorzystuje tę samą klasę Card, którą zdążyłeś poznać i polubić:
# Gra w karty 3.0
# Demonstruje dziedziczenie - przesłanianie metod

class Card(object):
""" Karta do gry. """
RANKS = ["A", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "J", "Q", "K"]
SUITS = ["c", "d", "h", "s"]

def __init__(self, rank, suit):


self.rank = rank
self.suit = suit

def __str__(self):
rep = self.rank + self.suit
return rep

Przesłonięcie metod klasy bazowej


Następnie tworzę nową klasę pochodną opartą na klasie Card, reprezentującą utajnione
karty. Nagłówek klasy wygląda dość standardowo:
class Unprintable_Card(Card):

Dzięki temu nagłówkowi wiesz, że klasa Unprintable_Card dziedziczy wszystkie


metody klasy Card. Lecz mogę zmienić zachowanie odziedziczonej metody poprzez jej
zdefiniowanie w klasie pochodnej. I to właśnie zrobiłem w pozostałej części definicji
metody:
""" Karta, której ranga i kolor nie są ujawniane przy jej wyświetleniu. """
def __str__(self):
return "<utajniona>"

Klasa Unprintable_Card dziedziczy metodę __str__() po klasie Card. Lecz definiuję


także nową metodę __str__() w klasie Unprintable_Card, która przesłania (czyli
zastępuje) metodę odziedziczoną. Zawsze, gdy tworzysz w klasie pochodnej metodę o
takiej samej nazwie jak nazwa metody odziedziczonej, przesłaniasz tę odziedziczoną
metodę w nowej klasie. Więc kiedy wyświetlasz obiekt klasy Unprintable_Card, na
ekranie pojawia się tekst <utajniona>.
Modyfikowanie zachowania odziedziczonych metod 271

Klasa pochodna nie ma żadnego wpływu na klasę bazową. Z perspektywy klasy


bazowej jest obojętne, czy tworzysz na jej podstawie klasę pochodną lub czy w nowej
klasie przesłaniasz odziedziczoną metodę. Klasa bazowa funkcjonuje nadal tak jak
zawsze. Oznacza to, że gdy wyświetlisz obiekt klasy Card, pojawi się na ekranie
w dotychczasowej postaci.

Wywoływanie metod klasy bazowej


Czasem, gdy przesłaniasz metodę klasy bazowej, chcesz włączyć w nową metodę
funkcjonalność metody odziedziczonej. Chcę na przykład utworzyć nowy typ kart do gry
w postaci klasy opartej na klasie Card. Chciałbym, aby obiekt tej nowej klasy posiadał
atrybut, który wskazywałby, czy karta jest odkryta, czy też nie. To oznacza, że muszę
przesłonić konstruktor odziedziczony po klasie Card nowym konstruktorem, który
tworzy atrybut bycia odkrytą kartą. Chciałbym jednak, aby mój nowy konstruktor
tworzył i ustawiał atrybuty rank i suit w taki sam sposób, jak to już robi konstruktor
klasy Card. Zamiast przepisywać kod konstruktora z klasy Card, mógłbym go wywołać
z wnętrza mojego nowego konstruktora. Wtedy byłby on odpowiedzialny za utworzenie
i inicjalizację atrybutów rank i suit w obiekcie mojej nowej klasy. W kodzie mojego
nowego konstruktora mógłbym dodać atrybut, który wskazuje, czy karta jest odkryta,
czy też nie. Dokładnie takie podejście zastosowałem w klasie Positionable_Card:
class Positionable_Card(Card):
""" Karta, która może być odkryta lub zakryta. """
def __init__(self, rank, suit, face_up = True):
super(Positionable_Card, self).__init__(rank, suit)
self.is_face_up = face_up

Nowa funkcja w konstruktorze, super(), pozwala Ci wywołać metodę klasy bazowej


(zwanej także nadklasą, ang. superclass). Wiersz super(Positionable_Card,
self).__init__(rank, suit) wywołuje metodę __init__() klasy Card (nadklasy klasy
Positionable_Card). Pierwszy argument wywołania funkcji super(), Positionable_Card,
mówi, że chcę wywołać metodę nadklasy (czyli klasy bazowej) klasy Positionable_Card,
którą jest klasa Card. Następny argument, self, przekazuje referencję do nowo
utworzonego obiektu klasy Positionable_Card, aby kod klasy Card uzyskał dostęp
do obiektu i mógł do niego dodać atrybuty rank i suit. Następna część instrukcji,
__init__(rank, suit), informuje Pythona, że chcę wywołać konstruktor klasy Card
i przekazać mu wartości zmiennych rank i suit.
Kolejna metoda w klasie Positionable_Card również przesłania metodę
odziedziczoną po klasie Card i wywołuje tę przesłoniętą metodę:
def __str__(self):
if self.is_face_up:
rep = super(Positionable_Card, self).__str__()
else:
rep = "XX"
return rep
272 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Ta metoda __str__() sprawdza najpierw, czy atrybut face_up obiektu ma wartość


True (co oznacza, że karta jest odkryta). Jeśli tak jest, ciągiem znaków reprezentującym
kartę staje się łańcuch zwrócony przez metodę __str__() klasy Card wywołaną
w kontekście obiektu klasy Positionable_Card. Innymi słowy, jeśli karta jest odkryta,
zostaje wyświetlona tak samo jak każdy obiekt klasy Card. Jeśli jednak karta nie jest
odkryta, jako jej reprezentacja zostaje zwrócony łańcuch "XX".
Ostatnia metoda w klasie nie przesłania metody odziedziczonej. Rozszerza ona
po prostu definicję tej nowej klasy:
def flip(self):
self.is_face_up = not self.is_face_up

Metoda odwraca kartę poprzez zanegowanie wartości atrybutu face_up obiektu.


Jeśli wartością atrybutu face_up jest True, wywołanie metody flip() obiektu ustawia ten
atrybut na False. Jeśli natomiast wartością atrybutu face_up jest False, wywołanie
metody flip() obiektu ustawia ten atrybut na True.

Używanie klas pochodnych


W głównej części programu tworzę trzy obiekty: jeden klasy Card, drugi klasy
Unprintable_Card oraz trzeci klasy Positionable_Card:
#część główna
card1 = Card("A", "c")
card2 = Unprintable_Card("A", "d")
card3 = Positionable_Card("A", "h")

Następnie wyświetlam obiekt klasy Card:


print("Wyświetlenie obiektu klasy Card:")
print(card1)

Przebiega to tak samo jak w poprzednich programach i zostaje wyświetlony tekst Ac.
Kolejną moją czynnością jest wyświetlenie obiektu klasy Unprintable_Card:
print("\nWyświetlenie obiektu klasy Unprintable_Card:")
print(card2)

Mimo że obiekt ma atrybut rank z ustawioną wartością "A" oraz atrybut suit
z ustawioną wartością "d", operacja print na obiekcie powoduje wyświetlenie tekstu
<utajniona>, ponieważ klasa Unprintable_Card przesłania odziedziczoną metodę
__str__() swoją własną, która zawsze zwraca łańcuch "<utajniona>".
Kolejne dwa wiersze kodu wyświetlają obiekt klasy Positionable_Card:
print("\nWyświetlenie obiektu klasy Positionable_Card:")
print(card3)

Ponieważ atrybut face_up obiektu ma wartość True, metoda __str__() tego obiektu
wywołuje metodę __str__() klasy Card i zostaje wyświetlony tekst Ah.
Następnie wywołuję metodę flip() obiektu klasy Positionable_Card:
Polimorfizm 273

print("Odwrócenie stanu obiektu klasy Positionable_Card (odkrycie-zakrycie karty).")


card3.flip()

W rezultacie atrybut face_up obiektu otrzymuje wartość False.


Kolejne dwa wiersze wyświetlają obiekt klasy Positionable_Card ponownie:
print("Wyświetlenie obiektu klasy Positionable_Card:")
print(card3)

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Tym razem zostaje wyświetlony tekst XX, ponieważ atrybut face_up obiektu ma
wartość False.

Polimorfizm
Polimorfizm to zdolność do traktowania różnego typu rzeczy tak samo i sprawienia,
aby każda z nich reagowała na swój własny sposób. W kontekście programowania
obiektowego polimorfizm oznacza, że możesz wysłać ten sam komunikat do obiektów
różnych klas powiązanych poprzez dziedziczenie oraz osiągnąć różne, odpowiednie dla
konkretnego obiektu wyniki. Kiedy na przykład wywołasz metodę __str__() obiektu
klasy Unprintable_Card, która jest klasą pochodną klasy Card, otrzymasz inny wynik niż
wtedy, kiedy wywołasz metodę __str__() obiektu klasy Card. Efekt tego polimorficznego
zachowania polega na tym, że możesz wyświetlić obiekt, jeśli nawet nie wiesz, czy jest on
obiektem klasy Unprintable_Card, czy też obiektem klasy Card. Niezależnie od klasy
obiektu, gdy jest on wyświetlany, zostaje wywołana metoda __str__() i na ekranie
pojawia się właściwa jego reprezentacja w postaci łańcucha znaków.

Tworzenie modułów
Po raz pierwszy dowiedziałeś się o modułach w rozdziale 3., w punkcie „Import
modułu random”, w którym się zetknąłeś z modułem random. Lecz potężnym aspektem
programowania w Pythonie jest możliwość tworzenia, używania, a nawet współdzielenia
swoich własnych modułów. Tworzenie własnych modułów przynosi znaczące korzyści.
Po pierwsze, tworząc własne moduły, możesz wykorzystywać kod wielokrotnie,
co może Ci zaoszczędzić czasu i wysiłków. Mógłbyś na przykład wykorzystać ponownie
klasy Card, Hand i Deck, z którymi się do tej pory spotkałeś, do utworzenia wielu różnych
typów gier w karty bez potrzeby wymyślania za każdym razem na nowo podstawowej
funkcjonalności karty, talii i ręki.
Po drugie, dzięki rozbiciu programu na moduły logiczne możesz radzić sobie łatwiej
z dużymi programami. Do tej pory programy, z którymi miałeś do czynienia, zawierały
się w jednym pliku. Ponieważ były dość krótkie, nie stanowiło to wielkiego problemu.
Lecz wyobraź sobie program, który składa się z tysięcy (czy nawet dziesiątek tysięcy)
274 Rozdział 9. Programowanie obiektowe. Gra Blackjack

wierszy. Praca nad programem tego rozmiaru zapisanym w jednym, ogromnym pliku
byłaby prawdziwą zmorą (nawiasem mówiąc, profesjonalne projekty mogą z łatwością
osiągać takie rozmiary). Rozbicie kodu na moduły ułatwia członkom zespołu
zajmującego się inżynierią oprogramowania podzielenie między siebie pracy
nad oddzielnymi częściami projektu.
Po trzecie, tworząc moduły, możesz się dzielić swoim talentem z innymi.
Jeśli stworzysz jakiś użyteczny moduł, możesz przesłać go pocztą elektroniczną
do przyjaciela, który może go wykorzystać na bardzo podobnej zasadzie jak dowolny
wbudowany moduł Pythona.

Prezentacja programu Prosta gra


Program Prosta gra, jak sugeruje sama jego nazwa, jest prosty. Najpierw program pyta,
ilu graczy chce wziąć udział w grze, a potem przechodzi do pobrania nazwy każdego
z nich. W końcu program przypisuje każdemu graczowi wygenerowany losowo wynik
i wyświetla wszystkie rezultaty. Niezbyt to ekscytujące, ale celem programu jest nie tyle
sama gra, co przedstawienie mechanizmu jej działania. Program wykorzystuje zupełnie
nowy moduł zawierający utworzone przeze mnie funkcje oraz klasę. Na rysunku 9.7
przedstawiono wyniki wygenerowane przez program.

Rysunek 9.7. Kilka funkcji i klasa, które zostały wykorzystane w programie,


pochodzą z utworzonego przez programistę modułu

Pisanie modułów
W zwykłym przypadku pokazałbym Ci kod kolejnego programu Prosta gra, lecz w tym
punkcie omówię napisany przeze mnie moduł, który wykorzystuje ta Prosta gra.
Moduł tworzy się w taki sam sposób, jak się pisze dowolny inny program w Pythonie.
Gdy jednak tworzysz moduł, powinieneś zbudować kolekcję powiązanych ze sobą
Tworzenie modułów 275

składników oprogramowania, takich jak funkcje czy klasy, i zapisać ją w pojedynczym


pliku, aby mogła zostać zaimportowana w nowym programie.
Utworzyłem podstawowy moduł o nazwie gry, który zawiera dwie funkcję i klasę,
które mogłyby się przydać przy tworzeniu gry. Jego kod możesz znaleźć na stronie
internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 9.;
nazwa pliku to gry.py.
# Gry
# Demonstruje tworzenie modułu

class Player(object):
""" Uczestnik gry. """
def __init__(self, name, score = 0):
self.name = name
self.score = score

def __str__(self):
rep = self.name + ":\t" + str(self.score)
return rep

def ask_yes_no(question):
"""Zadaj pytanie, na które można odpowiedzieć tak lub nie."""
response = None
while response not in ("t", "n"):
response = input(question).lower()
return response

def ask_number(question, low, high):


"""Poproś o podanie liczby z określonego zakresu."""
response = None
while response not in range(low, high):
response = int(input(question))
return response

if __name__ == "__main__":
print("Uruchomiłeś ten moduł bezpośrednio (zamiast go zaimportować).")
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Ten moduł został nazwany gry, ponieważ został zapisany w pliku o nazwie gry.py.
Nazwy modułów utworzonych przez programistę (używane w instrukcji import) są
oparte na nazwach plików, które je zawierają.
Większość kodu modułu jest prosta. Klasa Player (gracz) definiuje obiekt z dwoma
atrybutami, name (nazwa) i score (wynik), których wartości są ustawiane w konstruktorze.
Istnieje jeszcze tylko jedna metoda, __str__(), która zwraca łańcuchową reprezentację
obiektu, aby obiekty mogły być wyświetlane.
Kolejne dwie funkcje, ask_yes_no() i ask_number() poznałeś już wcześniej,
w rozdziale 6., w punktach „Funkcja ask_yes_no()” i „Funkcja ask_number()”.
W następnej części programu wprowadzam nową koncepcję związaną z modułami.
Warunek instrukcji if postaci __name__ == "__main__" jest prawdziwy, jeśli program
276 Rozdział 9. Programowanie obiektowe. Gra Blackjack

został uruchomiony bezpośrednio. Natomiast jest fałszywy, jeśli plik został zaimportowany
jako moduł. Więc gdy plik gry.py jest uruchamiany bezpośrednio, zostaje wyświetlony
komunikat informujący użytkownika, że plik powinien być zaimportowany, a nie
uruchomiony bezpośrednio.

Import modułów
Teraz, kiedy już się zapoznałeś z modułem gry, przedstawię kod programu Prosta gra.
Omówię kod tego programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 9.; nazwa pliku to prosta_gra.py.
# Prosta gra
# Demonstruje import modułów

import gry, random

Moduł utworzony przez programistę importuje się w taki sam sposób jak moduł
wbudowany, za pomocą instrukcji import. W rzeczywistości importuję moduł gry razem
ze znanym Ci modułem random w tej samej instrukcji import.

Pułapka
Jeśli moduł utworzony przez programistę nie znajduje się w tym samym katalogu
co program, który go importuje, Python nie potrafi go znaleźć. Są sposoby obejścia
tego ograniczenia. Można nawet zainstalować moduł utworzony przez programistę
w taki sposób, aby był dostępny tak jak moduły wbudowane w całym systemie,
ale wymaga to specjalnej procedury instalacyjnej, której omówienie wykracza
poza zakres tej książki. Więc na razie musisz się upewnić, że każdy moduł, jaki
chcesz zaimportować, znajduje się w tym samym katalogu co programy, które
go importują.

Użycie zaimportowanych funkcji i klas


Zaimportowane moduły wykorzystuję w pozostałej części programu Prosta gra.
Po przywitaniu graczy i skonfigurowaniu prostej pętli pytam o liczbę graczy,
którzy będą uczestniczyć w grze:
print("Witaj w najprostszej grze na świecie!\n")

again = None
while again != "n":
players = []
num = gry.ask_number(question = "Podaj liczbę graczy (2 - 5): ", low = 2, high = 5)

Pobieram liczbę graczy poprzez wywołanie funkcji ask_number z modułu gry.


Tak samo jak w przypadku innych zaimportowanych modułów, aby wywołać funkcję,
używam notacji z kropką, podając najpierw nazwę modułu, a potem nazwę funkcji.
Powrót do gry Blackjack 277

Następnie pobieram nazwę każdego z graczy i generuję losowo jego wynik mieszczący się
w zakresie od 1 do 100 poprzez wywołanie funkcji randrange() z modułu random. Potem
tworzę obiekt gracza przy użyciu nazwy gracza i jego wyniku. Ponieważ klasa Player
została zdefiniowana w module gry, znów używam notacji z kropką i przed nazwą klasy
umieszczam nazwę modułu. Następnie dołączam ten nowy obiekt gracza do listy graczy:
for i in range(num):
name = input("Nazwa gracza: ")
score = random.randrange(100) + 1
player = gry.Player(name, score)
players.append(player)

Następnie wyświetlam wynik każdego uczestnika gry:


print("\nOto wyniki gry:")
for player in players:
print(player)

Na koniec pytam, czy gracze chcą wziąć udział w jeszcze jednej rundzie gry.
Do uzyskania odpowiedzi wykorzystuję funkcję ask_yes_no() z modułu gry.
again = gry.ask_yes_no("\nCzy chcesz zagrać ponownie? (t/n): ")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Powrót do gry Blackjack


W tym momencie jesteś już ekspertem w używaniu klas Pythona do tworzenia kart
do gry, rąk i talii. Więc nadszedł czas, aby wykorzystać tę biegłość i zobaczyć, jak można
połączyć te klasy w większym programie w celu utworzenia kompletnej gry karcianej
w stylu kasyna (bez tandetnego zielonego filcu).

Moduł karty
W celu napisania gry Blackjack utworzyłem ostateczny moduł karty oparty
na programach Gra w karty. Klasy Hand i Deck są dokładnie takie same jak w programie
Gra w karty 2.0. Nowa klasa Card reprezentuje taką samą funkcjonalność jak klasa
Positionable_Card z programu Gra w karty 3.0. Kod programu możesz znaleźć na
stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 9.; nazwa pliku to karty.py.
# Moduł karty
# Podstawowe klasy do gry w karty

class Card(object):
""" Karta do gry. """
RANKS = ["A", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "J", "Q", "K"]
SUITS = ["c", "d", "h", "s"]
278 Rozdział 9. Programowanie obiektowe. Gra Blackjack

def __init__(self, rank, suit, face_up = True):


self.rank = rank
self.suit = suit
self.is_face_up = face_up

def __str__(self):
if self.is_face_up:
rep = self.rank + self.suit
else:
rep = "XX"
return rep

def flip(self):
self.is_face_up = not self.is_face_up

class Hand(object):
""" Ręka - wszystkie karty trzymane przez gracza. """
def __init__(self):
self.cards = []

def __str__(self):
if self.cards:
rep = ""
for card in self.cards:
rep += str(card) + "\t"
else:
rep = "<pusta>"
return rep

def clear(self):
self.cards = []

def add(self, card):


self.cards.append(card)

def give(self, card, other_hand):


self.cards.remove(card)
other_hand.add(card)

class Deck(Hand):
""" Talia kart. """
def populate(self):
for suit in Card.SUITS:
for rank in Card.RANKS:
self.add(Card(rank, suit))

def shuffle(self):
import random
random.shuffle(self.cards)

def deal(self, hands, per_hand = 1):


for rounds in range(per_hand):
for hand in hands:
Powrót do gry Blackjack 279

if self.cards:
top_card = self.cards[0]
self.give(top_card, hand)
else:
print("Nie mogę dalej rozdawać. Zabrakło kart!")

if __name__ == "__main__":
print("To moduł zawierający klasy do gry w karty.")
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Projektowanie klas
Zanim rozpoczniesz kodowanie projektu zawierającego wiele klas, pomocne może się
okazać sporządzenie na papierze ich planu. Mógłbyś zrobić ich listę i dołączyć do każdej
klasy krótki opis. Tabela 9.1 pokazuje moją pierwszą próbę sporządzenia takiej listy
do gry Blackjack.

Tabela 9.1. Klasy programu Blackjack


Klasa Klasa Opis
bazowa
BJ_Card karty.Card Karta do gry w blackjacka. Zdefiniuj atrybut value
reprezentujący wartość punktową karty.
BJ_Deck karty.Deck Talia kart blackjacka. Kolekcja obiektów klasy BJ_Card.
BJ_Hand karty.Hand Ręka w blackjacku. Zdefiniuj atrybut total reprezentujący
sumę punktów wszystkich kart ręki. Zdefiniuj atrybut name
reprezentujący właściciela ręki.
BJ_Player BJ_Hand Uczestnik gry w blackjacka.
BJ_Dealer BJ_Hand Rozdający karty w blackjacku.
BJ_Game object Gra w blackjacka. Zdefiniuj atrybut deck reprezentujący
obiekt klasy BJ_Deck. Zdefiniuj atrybut dealer reprezentujący
obiekt klasy BJ_Dealer.
Zdefiniuj atrybut players reprezentujący listę obiektów klasy
BJ_Player.

Powinieneś spróbować ująć w niej wszystkie klasy, których Twoim zdaniem będziesz
potrzebował, ale nie martw się tym, że Twoje opisy klas nie są kompletne, ponieważ
nigdy takie nie będą (moje też takie nie są). Lecz przygotowanie takiej listy powinno
pomóc Ci uzyskać dobry przegląd typów obiektów, nad którymi będziesz pracować
w swoim projekcie.
Oprócz słownych opisów swoich klas mógłby Ci się przydać rysunek drzewa
hierarchii klas, by przedstawić w wizualny sposób powiązania między Twoimi klasami.
To właśnie zrobiłem na rysunku 9.8.
280 Rozdział 9. Programowanie obiektowe. Gra Blackjack

Rysunek 9.8. Hierarchia dziedziczenia klas w grze Blackjack

Diagram hierarchii klas podobny do przedstawionego na rysunku 9.8 może Ci dać


ogólne spojrzenie na to, w jaki sposób wykorzystujesz dziedziczenie.

Pisanie pseudokodu do głównej pętli gry


Moją kolejną czynnością w fazie planowania gry było opisanie w pseudokodzie sposobu
rozegrania pojedynczej rundy. Sądziłem, że to pomoże mi zobaczyć, jak obiekty będą na
siebie wzajemnie oddziaływać. Oto pseudokod, jaki udało mi się sporządzić:
Rozdaj na początek wszystkim graczom i rozdającemu po 2 karty
Dla każdego gracza
Dopóki gracz prosi o dodatkową kartę i nie ma fury
Wydaj graczowi dodatkową kartę
Jeśli nie ma już graczy pozostających w grze
Pokaż 2 karty rozdającego
W przeciwnym razie
Dopóki rozdający musi dobierać karty i nie ma fury
Wydaj rozdającemu dodatkową kartę
Jeśli rozdający ma furę
Dla każdego gracza pozostającego w grze
Gracz wygrywa
W przeciwnym razie
Dla każdego gracza pozostającego w grze
Jeśli suma punktów gracza jest większa niż suma punktów rozdającego
Gracz wygrywa
W przeciwnym razie, jeśli suma gracza jest mniejsza niż suma rozdającego
Gracz przegrywa
W przeciwnym razie
Gracz remisuje

Import modułów. Karty i gry


Teraz, kiedy zapoznałeś się z planowaniem, nadszedł czas na analizę kodu. W pierwszej
części programu Blackjack importuję dwa moduły, karty i gry. Omówię kod tego
programu, dzieląc go na fragmenty, ale jego całość możesz znaleźć na stronie internetowej
tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 9.; nazwa pliku to
blackjack.py.
Powrót do gry Blackjack 281

# Blackjack
# Od 1 do 7 graczy współzawodniczy z rozdającym

import karty, gry

Moduł gry utworzyłem, jak pamiętasz, w programie Prosta gra, we wcześniejszej


części tego rozdziału.

Klasa BJ_Card
Klasa BJ_Card rozszerza definicję tego, czym jest karta, dziedzicząc po klasie karty.Card.
W klasie BJ_Card tworzę nową właściwość, value, reprezentującą wartość karty wyrażoną
w punktach:
class BJ_Card(karty.Card):
""" Karta do blackjacka. """
ACE_VALUE = 1

@property
def value(self):
if self.is_face_up:
v = BJ_Card.RANKS.index(self.rank) + 1
if v > 10:
v = 10
else:
v = None
return v

Metoda ta zwraca liczbę z zakresu od 1 do 10, która reprezentuje wartość


karty w blackjacku. Pierwsza część obliczenia jest wykonywana w wyrażeniu
BJ_Card.RANKS.index(self.rank) + 1. Wyrażenie to pobiera wartość atrybutu rank
obiektu (powiedzmy "6") i wyznacza odpowiadający jej numer indeksu na liście
BJ_Card.RANKS przy użyciu metody listy o nazwie index() (w przypadku wartości
atrybutu równej "6" będzie to liczba 5). Na koniec do wyniku zostaje dodana liczba 1,
ponieważ komputer rozpoczyna indeksowanie od 0 (to sprawia, że wartość punktowa
obliczona dla karty o randze "6" jest poprawna i wynosi 6). Ponieważ jednak dla atrybutów
rank o wartościach "J", "Q" i "K" otrzymujemy liczby większe niż 10, wartość atrybutu
value, która jest większa od 10, jest ustawiana na 10. Jeśli wartość atrybutu face_up
obiektu jest równa False, cały ten proces zostaje ominięty i jest zwracana wartość None.

Klasa BJ_Deck
Klasa BJ_Deck jest wykorzystywana do tworzenia talii kart blackjacka. Klasa jest
prawie taka sama jak jej klasa bazowa, karty.Deck. Jedyna różnica sprowadza się
do przesłonięcia metody populate() klasy karty.Deck w taki sposób, aby nowy
obiekt klasy BJ_Deck otrzymał pełną listę obiektów klasy BJ_Card:
class BJ_Deck(karty.Deck):
282 Rozdział 9. Programowanie obiektowe. Gra Blackjack

""" Talia kart do blackjacka. """


def populate(self):
for suit in BJ_Card.SUITS:
for rank in BJ_Card.RANKS:
self.cards.append(BJ_Card(rank, suit))

Klasa BJ_Hand
Klasa BJ_Hand oparta na klasie karty.Hand jest wykorzystywana do tworzenia obiektów
reprezentujących układy kart blackjacka zwane rękami. Przesłaniam konstruktor klasy
cards.Hand i dodaję atrybut name, który ma reprezentować nazwę właściciela ręki:
class BJ_Hand(karty.Hand):
""" Ręka w blackjacku. """
def __init__(self, name):
super(BJ_Hand, self).__init__()
self.name = name

Następnie przesłaniam odziedziczoną metodę __str__(), aby wyświetlać sumaryczną


wartość punktową ręki:
def __str__(self):
rep = self.name + ":\t" + super(BJ_Hand, self).__str__()
if self.total:
rep += "(" + str(self.total) + ")"
return rep

Tworzę konkatenację atrybutu name obiektu z łańcuchem znaków zwróconym przez


metodę karty.Hand__str__() wywołaną na rzecz tego obiektu. Następnie, jeśli wartość
właściwości total obiektu jest różna od None, dołączam do tworzonej konkatenacji
łańcuchową reprezentację tejże wartości. Na koniec zwracam tak utworzony łańcuch
znaków.
Następnie tworzę właściwość o nazwie total, która reprezentuje sumaryczną wartość
punktową ręki kart blackjacka. Jeśli ręka blackjacka zawiera zakrytą kartę, wartość jej
właściwości total wynosi None. W przeciwnym razie wartość ta zostaje obliczona
poprzez zsumowanie wartości punktowych wszystkich kart ręki.
@property
def total(self):
# jeśli karta w ręce ma wartość None, to i wartość sumy wynosi None
for card in self.cards:
if not card.value:
return None

# zsumuj wartości kart, traktuj każdego asa jako 1


t = 0
for card in self.cards:
t += card.value

# ustal, czy ręka zawiera asa


contains_ace = False
Powrót do gry Blackjack 283

for card in self.cards:


if card.value == BJ_Card.ACE_VALUE:
contains_ace = True

# jeśli ręka zawiera asa, a suma jest wystarczająco niska,


# potraktuj asa jako 11
if contains_ace and t <= 11:
# dodaj tylko 10, ponieważ już dodaliśmy 1 za asa
t += 10

return t

Pierwsza część tej metody sprawdza, czy wartość właściwości value jakiejś karty
należącej do ręki blackjacka nie jest równa None (co oznaczałoby, że karta jest zakryta).
Jeśli ma to miejsce, metoda zwraca None. Następna część metody po prostu sumuje
wartości punktowe wszystkich kart ręki. Kolejna część ustala, czy ręka zawiera asa.
Jeśli go zawiera, ostatnia część metody decyduje, czy wartość punktowa karty powinna
wynosić 11, czy też 1.
Ostatnia metoda w klasie BJ_Hand to is_busted(). Zwraca ona wartość True,
jeśli wartość właściwości total obiektu jest większa od 21. W przeciwnym wypadku
zwraca wartość False.
def is_busted(self):
return self.total > 21

Zwróć uwagę, że w tej metodzie zwracam bezpośrednio wynik warunku self.total > 21
zamiast przypisania go do zmiennej, którą następnie miałbym zwrócić. Możesz tworzyć
tego rodzaju instrukcje return z dowolnym warunkiem (a właściwie wyrażeniem) i często
to daje w efekcie bardziej elegancką metodę.
Ten rodzaj metody, który zwraca albo True, albo False, występuje dość często. Często
jest używany (tak jak w tym przypadku) do reprezentowania dwóch możliwych stanów
obiektu, takich jak na przykład „włączony” lub „wyłączony”. Metody tego typu mają
prawie zawsze nazwę (o ile używamy angielskich nazw metod), która zaczyna się od
słowa „is” (jest), jak przykładowo is_on().

Klasa BJ_Player
Klasa BJ_Player, która jest pochodną klasy BJ_Hand, jest używana do reprezentowania
graczy w blackjacku:
class BJ_Player(BJ_Hand):
""" Gracz w blackjacku. """
def is_hitting(self):
response = gry.ask_yes_no("\n" + self.name + ", chcesz dobrać kartę? (T/N): ")
return response == "t"

def bust(self):
print(self.name, "ma furę.")
self.lose()
284 Rozdział 9. Programowanie obiektowe. Gra Blackjack

def lose(self):
print(self.name, "przegrywa.")

def win(self):
print(self.name, "wygrywa.")

def push(self):
print(self.name, "remisuje.")

Pierwsza metoda, is_hitting(), zwraca wartość True, jeśli gracz chce dobrać jeszcze
jedną kartę, i zwraca wartość False w przeciwnym wypadku. Metoda bust() oznajmia,
że gracz ma furę, i wywołuje metodę lose() obiektu. Metoda lose() ogłasza przegraną
gracza, metoda win() — jego zwycięstwo, a metoda push() ogłasza remis. Metody bust(),
lose(), win() oraz push() są tak proste, że mógłbyś się zastanawiać, dlaczego istnieją.
Umieściłem je w klasie, ponieważ tworzą wspaniałą strukturę szkieletową do obsługi
bardziej skomplikowanych kwestii, które powstają, kiedy gracze mają możliwość
stawiania pieniędzy (a będą ją mieć, gdy wykonasz jedno z zadań zamieszczonych
na końcu rozdziału).

Klasa BJ_Dealer
Klasa BJ_Dealer, która jest klasą pochodną klasy BJ_Hand, jest wykorzystywana
do reprezentowania rozdającego karty w blackjacku:
class BJ_Dealer(BJ_Hand):
""" Rozdający w blackjacku. """
def is_hitting(self):
return self.total < 17

def bust(self):
print(self.name, "ma furę.")

def flip_first_card(self):
first_card = self.cards[0]
first_card.flip()

Pierwsza metoda, is_hitting(), rozstrzyga, czy rozdający bierze dodatkowe karty.


Ponieważ rozdający musi dobrać kartę w sytuacji, gdy suma punktów jego ręki nie
przekracza liczby 17, metoda zwraca wartość True, jeśli właściwość total obiektu ma
mniejszą wartość niż 17; w przeciwnym wypadku zwraca wartość False. Metoda bust()
ogłasza, że rozdający ma furę. Metoda flip_first_card() odwraca pierwszą kartę
rozdającego.

Klasa BJ_Game
Klasa BJ_Game jest używana do utworzenia pojedynczego obiektu, który reprezentuję grę
w blackjacka. Klasa zawiera kod głównej pętli gry w swojej metodzie play(). Jednak
mechanizm gry jest na tyle skomplikowany, że kilka jego elementów utworzyłem poza tą
Powrót do gry Blackjack 285

metodą, w tym metodę __additional_cards, która zajmuje się wydawaniem graczowi


dodatkowych kart, oraz właściwość still_playing(), która zwraca listę wszystkich
graczy nadal biorących udział w rozgrywce.

Metoda __init__()
Konstruktor otrzymuje listę nazw i dla każdej nazwy tworzy obiekt gracza. Metoda
tworzy także rozdającego i talię.
class BJ_Game(object):
""" Gra w blackjacka. """
def __init__(self, names):
self.players = []
for name in names:
player = BJ_Player(name)
self.players.append(player)

self.dealer = BJ_Dealer("Rozdający")

self.deck = BJ_Deck()
self.deck.populate()
self.deck.shuffle()

Właściwość still_playing
Właściwość still_playing zwraca listę graczy, którzy nadal biorą udział w grze
(którzy w tej rundzie gry nie mieli fury):
@property
def still_playing(self):
sp = []
for player in self.players:
if not player.is_busted():
sp.append(player)
return sp

Metoda __additional_cards()
Metoda __additional_cards() rozdaje dodatkowe karty wszystkim graczom
i rozdającemu. Metoda otrzymuje obiekt poprzez swój parametr player, który może być
albo obiektem klasy BJ_Player, albo obiektem klasy BJ_Dealer. Metoda kontynuuje swoje
działanie, dopóki metoda is_busted() obiektu zwraca wartość False, a jego metoda
is_hitting() zwraca wartość True. Gdy metoda is_busted() obiektu zwróci wartość
True, zostanie wywołana jego metoda bust().
def __additional_cards(self, player):
while not player.is_busted() and player.is_hitting():
self.deck.deal([player])
print(player)
if player.is_busted():
player.bust()
286 Rozdział 9. Programowanie obiektowe. Gra Blackjack

W przypadku dwóch wywołań metod działa polimorfizm. Wywołanie metody


player.is_hitting() funkcjonuje jednakowo dobrze, czy parametr player odwołuje się do
obiektu klasy BJ_Player, czy też do obiektu klasy BJ_Dealer. Metoda __additional_cards()
nie musi w ogóle wiedzieć, z jakim typem obiektu współpracuje. To samo dotyczy wiersza
player.bust(). Ponieważ obie klasy, BJ_Player i BJ_Dealer, definiują swoje własne
metody bust(), wykonanie tego wiersza daje w każdym przypadku pożądany wynik.

Metoda play()
Metoda play() jest tą częścią programu, w której została zdefiniowana główna pętla gry,
i charakteryzuje ją uderzające podobieństwo do pseudokodu, który wcześniej
zaprezentowałem.
def play(self):
# rozdaj każdemu początkowe dwie karty
self.deck.deal(self.players + [self.dealer], per_hand = 2)
self.dealer.flip_first_card() # ukryj pierwszą kartę rozdającego
for player in self.players:
print(player)
print(self.dealer)

# rozdaj graczom dodatkowe karty


for player in self.players:
self.__additional_cards(player)

self.dealer.flip_first_card() # odsłoń pierwszą kartę rozdającego

if not self.still_playing:
# ponieważ wszyscy gracze dostali furę, pokaż tylko rękę rozdającego
print(self.dealer)
else:
# daj dodatkowe karty rozdającemu
print(self.dealer)
self.__additional_cards(self.dealer)

if self.dealer.is_busted():
# wygrywa każdy, kto jeszcze pozostaje w grze
for player in self.still_playing:
player.win()
else:
# porównaj punkty każdego gracza pozostającego w grze z punktami
rozdającego
for player in self.still_playing:
if player.total > self.dealer.total:
player.win()
elif player.total < self.dealer.total:
player.lose()
else:
player.push()

# usuń karty wszystkich graczy


for player in self.players:
Podsumowanie 287

player.clear()
self.dealer.clear()

Wszystkim graczom i rozdającemu zostają rozdane po dwie początkowe karty.


Pierwsza karta rozdającego jest odwrócona w celu ukrycia jej wartości. Następnie zostają
wyświetlone wszystkie ręce. Potem każdy gracz dostaje dodatkowe karty, dopóki o nie
prosi i dopóki nie uzbierał fury. Jeśli wszyscy gracze uzbierali furę, pierwsza karta
rozdającego zostaje odwrócona, a jego ręka wyświetlona. W innym przypadku gra jest
kontynuowana. Rozdający dobiera karty, dopóki suma punktów jego ręki jest mniejsza
niż 17. Jeśli rozdający uzbiera furę, wszyscy gracze, którzy pozostali w grze, wygrywają.
W przeciwnym razie wartość punktowa ręki każdego gracza pozostającego w grze jest
porównywana z sumą punktów ręki rozdającego. Jeśli suma punktów uzbieranych przez
gracza jest większa niż u rozdającego, gracz wygrywa. Jeśli jest mniejsza, gracz przegrywa.
Jeśli obie sumy są równe, gracz remisuje.

Funkcja main()
Funkcja main() wczytuje nazwy wszystkich graczy, wstawia je do listy i tworzy obiekt
klasy BJ_Game, używając tej listy jako argumentu. Następnie funkcja wywołuje metodę
play() obiektu i będzie to działanie kontynuować, dopóki gracze nie będą już chcieli grać.
def main():
print("\t\tWitaj w grze 'Blackjack'!\n")

names = []
number = gry.ask_number("Podaj liczbę graczy (1 - 7): ", low = 1, high = 8)
for i in range(number):
name = input("Wprowadź nazwę gracza: ")
names.append(name)
print()

game = BJ_Game(names)

again = None
while again != "n":
game.play()
again = gry.ask_yes_no("\nCzy chcesz zagrać ponownie?: ")

main()
input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Podsumowanie
W tym rozdziale zostałeś wprowadzony w świat programowania obiektowego.
Zobaczyłeś, jak można przesyłać komunikaty między obiektami. Dowiedziałeś się,
jak łączyć ze sobą obiekty w celu tworzenia bardziej złożonych obiektów. Zostałeś
288 Rozdział 9. Programowanie obiektowe. Gra Blackjack

wprowadzony w koncepcję dziedziczenia, procesu tworzenia nowych klas na podstawie


już istniejących. Zobaczyłeś, jak można rozszerzyć klasę pochodną poprzez dodanie
nowych metod. Zobaczyłeś również, jak można przesłonić odziedziczone metody.
Dowiedziałeś się, jak pisać i importować własne moduły. Pokazałem Ci przykład, jak
można stworzyć zarys swoich klas przed rozpoczęciem realizacji projektu. Na koniec
zobaczyłeś, jak te wszystkie koncepcje występują razem przy tworzeniu kasynowej gry
karcianej dla wielu graczy.

Sprawdź swoje umiejętności


1. Dodaj do gry Blackjack pewne bardzo potrzebne elementy kontroli błędów.
Przed rozpoczęciem nowej rundy upewnij się, że talia zawiera wystarczającą
liczbę kart. Jeśli tak nie jest, ponownie dołącz do niej komplet kart i potasuj jej
zawartość. Znajdź też inne miejsca, w których mógłbyś dodać sprawdzanie
błędów, i utwórz niezbędne zabezpieczenia.
2. Napisz jednokartową wersję gry w wojnę, w której każdy otrzymuje jedną kartę
i gracz z najwyższą kartą wygrywa.
3. Ulepsz projekt Blackjack, umożliwiając graczom stawianie pieniędzy. Śledź
budżety wszystkich graczy i usuwaj każdego gracza, któremu skończą się
pieniądze.
4. Stwórz prostą grę przygodową wykorzystującą obiekty, w której gracz może
podróżować pomiędzy różnymi, wzajemnie połączonymi miejscami.
10
Tworzenie interfejsów GUI.
Gra Mad Lib

D o tej pory wszystkie programy, z którymi się spotkałeś, wykorzystywały do interakcji


z użytkownikiem stary, zwykły tryb tekstowy. Lecz istnieją bardziej wyszukane
metody prezentowania i przyjmowania informacji. Graficzny interfejs użytkownika
(ang. graphical user interface — GUI) udostępnia wizualny sposób interakcji użytkownika
z komputerem. Wszystkie najpopularniejsze systemy operacyjne do użytku domowego
wykorzystują interfejs GUI, sprawiając, że interakcje użytkownika stają się prostsze
i bardziej konsekwentne. W tym rozdziale dowiesz się, jak tworzyć interfejsy GUI.
W szczególności nauczysz się:
 posługiwać się zestawem narzędzi do tworzenia interfejsów GUI,
 tworzyć i wypełniać ramki,
 tworzyć i wykorzystywać przyciski,
 tworzyć i wykorzystywać pola wejściowe i tekstowe,
 tworzyć i wykorzystywać pola wyboru,
 tworzyć i wykorzystywać przyciski opcji.

Wprowadzenie do programu Mad Lib


Projekt rozdziału, program Mad Lib, prosi użytkownika o pomoc w tworzeniu
opowiadania. Użytkownik podaje imię osoby, rzeczownik w liczbie mnogiej i czasownik.
Użytkownik może również wybrać przymiotniki spośród kilku podanych oraz jedną
nazwę części ciała. Program pobiera te wszystkie informacje i wykorzystuje je do stworzenia
opowiadania. Program został przedstawiony na rysunkach od 10.1 do 10.3. Jak widzisz,
program Mad Lib używa interfejsu GUI do interakcji z użytkownikiem.
290 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Rysunek 10.1. Przyjemny interfejs GUI czeka na kreatywność użytkownika

Rysunek 10.2. Użytkownik wprowadził wszystkie niezbędne informacje

Rysunek 10.3. Po kliknięciu przycisku Kliknij, aby wyświetlić opowiadanie,


w polu tekstowym zostaje wyświetlone literackie arcydzieło
Przyjrzenie się interfejsowi GUI 291

Przyjrzenie się interfejsowi GUI


Zanim opiszę, jak się programuje interfejs GUI, chciałbym zdefiniować wszystkie jego
elementy, z którymi się zetkniesz w tym rozdziale. Rysunek 10.4 również pokazuje
program Mad Lib, z tym że teraz różne elementy interfejsu zostały oznaczone nazwami.

Rysunek 10.4. Nauczysz się tworzyć te wszystkie elementy GUI

Aby tworzyć interfejs GUI w Pythonie, musisz skorzystać z zestawu narzędzi GUI
(ang. GUI toolkit). Istnieje ich wiele do wyboru, lecz w tym rozdziale używam
popularnego, niezależnego od platformy zestawu narzędzi Tkinter.

Wskazówka
Jeśli używasz innego systemu operacyjnego niż Windows, będziesz być może
potrzebował pobrać i zainstalować dodatkowe oprogramowanie, by móc
korzystać z zestawu narzędzi Tkinter. Aby dowiedzieć się na ten temat więcej,
odwiedź w serwisie internetowym Pythona stronę poświęconą interfejsowi
Tkinter o adresie http://docs.python.org/3/library/tkinter.html.

Elementy GUI tworzy się poprzez konkretyzację obiektów klas z modułu tkinter,
który jest częścią zestawu narzędzi Tkinter. W tabeli 10.1 opisuję wszystkie elementy GUI
z rysunku 10.4 i podaję odpowiadające im klasy z modułu tkinter.

Wskazówka
Nie ma potrzeby zapamiętywania tych wszystkich klas modułu tkinter.
Chciałem Ci tylko zaprezentować ogólny przegląd klas, o których się będziesz
uczyć w tym rozdziale.
292 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Tabela 10.1. Wybrane elementy interfejsu GUI


Element tkinter Opis klasy
Ramka Frame Stanowi pojemnik na inne elementy interfejsu GUI.
Etykieta Label Wyświetla nieedytowalny tekst lub ikony.
Przycisk Button Wykonuje pewne działanie po uaktywnieniu go
przez użytkownika.
Pole wejściowe Entry Przyjmuje i wyświetla jeden wiersz tekstu.
Pole tekstowe Text Przyjmuje i wyświetla tekst złożony z wielu wierszy.
Pole wyboru Checkbutton Pozwala użytkownikowi na wybranie lub niewybranie opcji.
Przycisk opcji Radiobutton Występując w grupie, pozwala użytkownikowi na wybranie
jednej z wielu opcji.

Programowanie sterowane zdarzeniami


Programy obsługujące GUI są tradycyjnie sterowane zdarzeniami (ang. event-driven),
co oznacza, że reagują na różne działania niezależnie od porządku, w jakim te działania
występują. Programowanie sterowane zdarzeniami to nieco odmienny sposób myślenia
o tworzeniu kodu. Ale nie martw się, ponieważ jeśli kiedykolwiek przedtem korzystałeś
z interfejsu GUI (takiego jak przeglądarka internetowa), to już pracowałeś w systemie
sterowanym zdarzeniami.
Aby lepiej zrozumieć programowanie sterowane zdarzeniami, pomyśl o finalnym
projekcie z tego rozdziału, Mad Lib. Jeśli miałbyś napisać podobny program przy swoich
aktualnych umiejętnościach kodowania w Pythonie, prawdopodobnie zadałbyś
użytkownikowi serię pytań przy użyciu funkcji input(). Zapytałbyś o imię osoby, następnie
poprosiłbyś o wprowadzenie rzeczownika w liczbie mnogiej, potem czasownika itd.
W rezultacie użytkownik musiałby podać każdą informacje w ustalonej kolejności.
Lecz gdybyś napisał program sterowany zdarzeniami, który, powiedzmy, wykorzystywałby
interfejs GUI, użytkownik mógłby wprowadzać informacje w dowolnym porządku.
Również wybór czasu, w którym program rzeczywiście generuje opowiadanie, zależałby
od decyzji użytkownika.
Gdy piszesz program sterowany zdarzeniami, wiążesz (kojarzysz) zdarzenia
(rzeczy dotyczące obiektów programu, które mogą się wydarzyć) z procedurami obsługi
zdarzeń (ang. event handlers; kod który jest wykonywany, gdy wystąpi określone
zdarzenie). Szukając konkretnego przykładu, pomyśl ponownie o projekcie Mad Lib.
Kiedy użytkownik klika przycisk Kliknij, aby wyświetlić opowiadanie (zdarzenie),
program wywołuje metodę, która wyświetla opowiadanie (procedurę obsługi zdarzenia).
Aby to mogło mieć miejsce, muszę skojarzyć kliknięcie przycisku z metodą wyświetlającą
opowiadanie (wiążę te dwie rzeczy ze sobą).
Definiując wszystkie swoje obiekty, zdarzenia i procedury obsługi zdarzeń, ustalasz
sposób działania swojego programu. Następnie uruchamiasz program poprzez wejście
w pętlę obsługi zdarzeń, w której program oczekuje na wystąpienie opisanych przez
Zastosowanie okna głównego 293

Ciebie zdarzeń. Gdy któreś ze zdarzeń faktycznie wystąpią, program je obsłuży


w zaplanowany przez Ciebie sposób.
Nie martw się, jeśli ten nieco inny sposób podejścia do programowania, nie jest
jeszcze dla Ciebie całkowicie zrozumiały. Po zapoznaniu się z kilkoma działającymi
przykładami będziesz wiedział, jak obmyślać swoje własne programy sterowane
zdarzeniami.

Zastosowanie okna głównego


Podstawą programu wykorzystującego interfejs GUI jest jego okno główne (ang. root
window), w oparciu o które możesz dodawać pozostałe elementy GUI. Jeśli wyobrazisz
sobie GUI jako drzewo, to okno główne będzie jego korzeniem. Twoje drzewo może się
rozgałęziać we wszystkich kierunkach, lecz każda jego część bezpośrednio lub pośrednio
jest zakotwiczona w korzeniu.

Prezentacja programu Prosty interfejs GUI


Program Prosty interfejs GUI tworzy chyba najprostszy możliwy interfejs GUI —
pojedyncze okno. Rysunek 10.5 pokazuje wynik uruchomienia tego programu.

Rysunek 10.5. Program tworzy tylko samotne, puste okno. Cóż, od czegoś musisz zacząć

Pułapka
Uruchomienie programu wykorzystującego Tkinter bezpośrednio z IDLE spowoduje
zamrożenie albo Twojego programu, albo IDLE. Najprostszym rozwiązaniem jest
uruchomienie używającego Tkintera programu w sposób bezpośredni. W systemie
Windows możesz to zrobić poprzez dwukrotne kliknięcie ikony programu.

Oprócz okna przedstawionego na rysunku 10.5 program Prosty interfejs GUI może
wygenerować jeszcze jedno okno (zależy to od Twojego systemu operacyjnego) —
znajome okno konsoli przedstawione na rysunku 10.6.
294 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Rysunek 10.6. Program wykorzystujący GUI może także wygenerować okno konsoli

Sztuczka
Chociaż możesz uruchomić program wykorzystujący Tkinter poprzez dwukrotne
kliknięcie jego ikony, będziesz miał problem, jeśli w programie wystąpi błąd —
okno konsoli zostanie zamknięte, zanim zdążysz przeczytać komunikat o błędzie.
W systemie Windows możesz utworzyć plik wsadowy, który uruchamia Twój program
i wstrzymuje swoje działanie po zakończeniu programu, sprawiając, że okno
konsoli pozostaje otwarte, abyś mógł zobaczyć wszystkie komunikaty o błędach.
Jeśli na przykład Twoim programem jest prosty_gui.py, wystarczy, że utworzysz
plik wsadowy złożony z dwóch wierszy:
prosty_gui.py
pause
Następnie uruchom plik wsadowy, klikając dwukrotnie jego ikonę.
Aby utworzyć plik wsadowy:
 Otwórz edytor tekstu taki jak Notatnik (lecz nie Word ani Wordpad).
 Wpisz swój tekst.
 Zapisz plik z rozszerzeniem .bat (jak na przykład prosty_gui.bat) i upewnij się,
że po .bat nie ma rozszerzenia .txt.
Utworzyłem pliki wsadowe do wszystkich programów w tym rozdziale. Możesz je
znaleźć na stronie internetowej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 10. razem z programami Pythona.

Chociaż mógłbyś pomyśleć, że to okno konsoli towarzyszące Twojemu skądinąd


nienagannemu interfejsowi GUI psuje tylko doskonałe wrażenie, jakie on wywołuje,
nie bądź taki skory do pozbycia się go. Okno konsoli może dostarczyć cennej informacji
zwrotnej, jeśli (i kiedy) Twój korzystający z Tkintera program wygeneruje błędy.
Pamiętaj także, aby nie zamykać okna konsoli, ponieważ to spowoduje równoczesne
zamknięcie programu z interfejsem GUI.
Zastosowanie okna głównego 295

Sztuczka
Kiedy Twoje oprogramowanie GUI działa już bez zarzutu, mógłbyś chcieć zapobiec
pojawianiu się towarzyszącego mu okna konsoli. Na maszynie z systemem
Windows najłatwiej tego dokonać poprzez zmianę rozszerzenia nazwy Twojego
programu z .py na .pyw.

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa
pliku to prosty_gui.py.

Import modułu tkinter


W końcu nadszedł czas ubrudzić sobie ręce pisaniem jakiegoś kodu! Pierwszą moją
czynnością w programie Prosty interfejs GUI jest zaimportowanie modułu tkinter:
# Prosty interfejs GUI
# Demonstruje tworzenie okna

from tkinter import *

Powyższy kod importuje całą zawartość modułu tkinter bezpośrednio do zakresu


globalnego programu. Zazwyczaj unika się robienia podobnych rzeczy; jednak kilka
modułów, takich jak tkinter, zostało tak zaprojektowanych, że muszą być importowane
w ten sposób. Zobaczysz, jak się to przydaje w następnym wierszu kodu.

Utworzenie okna głównego


Aby utworzyć okno główne, konkretyzuję obiekt klasy Tk modułu tkinter:
# utwórz okno główne
root = Tk()

Zwróć uwagę, że nie musiałem poprzedzić nazwy klasy, Tk, przedrostkiem w postaci
nazwy modułu, tkinter. Właściwie mogę teraz korzystać z bezpośredniego dostępu
do dowolnej części modułu tkinter bez potrzeby używania nazwy modułu. Ponieważ
większość programów wykorzystujących tkinter, zawiera wiele odwołań do klas i stałych
zdefiniowanych w module, zaoszczędza to wiele pisania i sprawia, że kod jest łatwiejszy
do czytania.

Pułapka
W programie wykorzystującym Tkinter możesz utworzyć tylko jedno okno główne.
Jeśli utworzysz ich więcej, niechybnie zamrozisz swój program, ponieważ
utworzone okna główne będą walczyć o przejęcie sterowania.
296 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Modyfikowanie okna głównego


Następnie modyfikuję okno główne, używając paru jego metod:
# zmodyfikuj okno
root.title("Prosty interfejs GUI")
root.geometry("225x100")

Metoda title() ustawia tytuł okna głównego. Nie musisz nic robić oprócz przekazania,
w postaci łańcucha znaków, tytułu, który chcesz wyświetlić. Ustawiłem tytuł tak, aby na
pasku tytułu okna pojawił się tekst Prosty interfejs GUI.
Metoda geometry() ustawia rozmiar okna głównego wyrażony w pikselach. Metoda
pobiera łańcuch znaków (a nie liczby całkowite), który reprezentuje szerokość i wysokość
okna rozdzielone znakiem "x". Ustawiam szerokość okna na 225, a jego wysokość na 100.

Wejście w pętlę zdarzeń okna głównego


W końcu uruchamiam pętlę zdarzeń okna poprzez wywołanie metody mainloop()
obiektu root:
# uruchom pętlę zdarzeń
root.mainloop()

W rezultacie okno pozostaje otwarte, gotowe do obsługi zdarzeń. Ponieważ nie


zdefiniowałem żadnych zdarzeń, okno nie ma wiele pracy. Ale jest to pełnowartościowe
okno, którego rozmiar można zmieniać, które można zminimalizować lub zamknąć.
Możesz je przetestować poprzez dwukrotne kliknięcie ikony pliku wsadowego.

Używanie etykiet
Elementy GUI są nazywane widżetami (ang. widgets = window gadgets). Zapewne
najprostszym widżetem jest widżet Label, który reprezentuje nieedytowalny tekst
lub ikony (albo obie te rzeczy na raz). Widżet Label stanowi etykietę do części interfejsu
GUI. Jest często używany do opisywania innych widżetów. W przeciwieństwie do innych
widżetów etykiety nie są interaktywne. Użytkownik nie może kliknąć etykiety (w porządku,
użytkownik może to zrobić, lecz etykieta na to nie zareaguje). Mimo to etykiety mają
duże znaczenie i użyjesz przynajmniej jednej za każdym razem, gdy będziesz tworzył
interfejs GUI.

Prezentacja programu Metkownica


Program Metkownica tworzy okno główne i dodaje do niego etykietę. Widżet Label
po prostu oznajmia, że jest etykietą. Efekt uruchomienia programu przedstawiono
na rysunku 10.7.
Zastosowanie okna głównego 297

Rysunek 10.7. Etykieta może dostarczyć informacji o interfejsie GUI

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to metkownica.py.

Rozpoczęcie programu
Najpierw inicjuję program Metkownica poprzez import modułu tkinter i utworzenie
okna głównego:
# Metkownica
# Demonstruje etykietę

from tkinter import *

# utwórz okno główne


root = Tk()
root.title("Metkownica")
root.geometry("200x50")

Utworzenie ramki
Widżet Frame (ramka) może przechowywać inne widżety (takie jak widżety Label).
Ramka przypomina korkowe tworzywo tablicy ogłoszeń; możesz jej używać jako podłoża,
na którym można umieszczać inne rzeczy. Więc tworzę teraz nową ramkę:
# utwórz w oknie ramkę jako pojemnik na inne widżety
app = Frame(root)

Za każdym razem, gdy tworzysz nowy widżet musisz przekazać do jego konstruktora
obiekt nadrzędny (ang. master; obiekt, który będzie zawierał ten widżet). W tym przypadku
przekazuję obiekt root do konstruktora klasy Frame. W rezultacie nowa ramka zostaje
umieszczona wewnątrz okna głównego.
Następnie wywołuję metodę grid() nowego obiektu:
app.grid()

Metodę grid() posiadają wszystkie widżety. Jest ona związana z menedżerem


układów (ang. layout manager), który umożliwia rozplanowanie położenia widżetów.
Aby nie komplikować sprawy, odkładam omówienie menedżerów układów na nieco
dalszą część tego rozdziału.
298 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Utworzenie etykiety
Tworzę widżet Label poprzez konkretyzację obiektu klasy Label:
# utwórz w ramce etykietę
lbl = Label(app, text = "Jestem etykietą!")

Dzięki przekazaniu obiektu app do konstruktora obiektu klasy Label sprawiam,


że ramka, do której odwołuje się zmienna app, staje się obiektem nadrzędnym widżetu
Label. W rezultacie etykieta zostaje umieszczona w ramce.
Widżety mają opcje, które można ustawiać. Wiele z tych opcji wpływa na wygląd
widżetu. Przekazując łańcuch "Jestem etykietą!" do parametru text, sprawiam, że ten
łańcuch staje się wartością opcji text widżetu. W rezultacie, kiedy etykieta zostaje
wyświetlona, pojawia się tekst Jestem etykietą!.
Następnie wywołuję metodę grid() obiektu:
lbl.grid()

To zapewnia widoczność etykiety.

Wejście w pętlę zdarzeń okna głównego


Ostatnią, lecz nie mniej ważną czynnością, jaką wykonuję, jest wywołanie pętli zdarzeń
okna w celu uruchomienia interfejsu GUI:
# uruchom pętlę zdarzeń okna
root.mainloop()

Używanie przycisków
Widżet Button może zostać uaktywniony przez użytkownika w celu wykonanie pewnej
czynności. Ponieważ już wiesz, jak tworzyć etykiety, nauczenie się tworzenia przycisków
będzie dość proste.

Prezentacja programu Leniwe przyciski


W programie Leniwe przyciski tworzę kilka przycisków, które nic nie robią po ich
uaktywnieniu. To jak zainstalowanie nowego sprzętu oświetleniowego bez podłączenia
do niego przewodów. Sprzęt został zamontowany, lecz jeszcze nie funkcjonuje.
Na rysunku 10.8 przedstawiono ten program.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to leniwe_przyciski.py.
Używanie przycisków 299

Rysunek 10.8. Możesz klikać te leniwe przyciski, ile tylko chcesz; i tak nic nie zrobią

Rozpoczęcie programu
Najpierw inicjuję program poprzez zaimportowanie modułu tkinter oraz okna
głównego i ramki:
# Leniwe przyciski
# Demonstruje tworzenie przycisków

from tkinter import *

# utwórz okno główne


root = Tk()
root.title("Leniwe przyciski")
root.geometry("200x85")

# utwórz w oknie ramkę do pomieszczenia innych widżetów


app = Frame(root)
app.grid()

Utworzenie przycisków
Widżet Button tworzy się poprzez konkretyzację obiektu klasy Button. To właśnie
zrobiłem w kolejnych wierszach kodu:
# utwórz w ramce przycisk
bttn1 = Button(app, text = "Nic nie robię!")
bttn1.grid()

W powyższych wierszach kodu zostaje utworzony nowy przycisk z tekstem


Nic nie robię!. Obiektem nadrzędnym przycisku jest ramka, którą utworzyłem
wcześniej, co oznacza, że przycisk zostaje w niej umieszczony.
Moduł tkinter oferuje elastyczność, gdy chodzi o tworzenie, definiowanie
i zmienianie widżetów. Możesz utworzyć widżet i ustawić wszystkie jego opcje w jednym
wierszu kodu (tak jak ja to zrobiłem), ale możesz też ustawić lub zmienić opcje widżetu
później, po jego utworzeniu. Pokażę Ci, co mam na myśli, na przykładzie następnego
przycisku. Najpierw tworzę nowy przycisk:
# utwórz w ramce drugi przycisk
bttn2 = Button(app)
bttn2.grid()
300 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Zauważ, że jedyną wartością, jaką przekazuję do konstruktora obiektu, jest app, obiekt
nadrzędny przycisku. Więc wszystko, co zrobiłem, to dodanie do ramki pustego
przycisku. Mogę jednak to naprawić. Mogę zmodyfikować widżet po jego utworzeniu,
wykorzystując metodę configure() obiektu:
bttn2.configure(text = "Ja również!")

W tym wierszu ustawiam opcję text przycisku poprzez przypisanie łańcucha


"Ja również!", co skutkuje umieszczeniem na przycisku tekstu Ja również!.
Możesz wykorzystać metodę configure() widżetu do każdej jego opcji (i do każdego
typu widżetu). Możesz nawet użyć tej metody do zmiany wartości opcji, którą już ustawiłeś.
Następnie tworzę trzeci przycisk:
# utwórz w ramce trzeci przycisk
bttn3 = Button(app)
bttn3.grid()

Potem ustawiam opcję text przycisku przy użyciu innego interfejsu:


bttn3["text"]= "To samo mnie dotyczy!"

Uzyskuję dostęp do opcji text poprzez interfejs przypominający słownik. Ustawiam


wartość opcji text w postaci łańcucha "To samo mnie dotyczy!", co powoduje
umieszczenie tekstu To samo mnie dotyczy! na przycisku. Kiedy ustawiasz wartość
opcji, stosując ten słownikowy typ dostępu, kluczem opcji jest jej nazwa w postaci
łańcucha znaków.

Wejście w pętlę zdarzeń okna głównego


Jak zawsze, wywołuję pętlę zdarzeń okna głównego w celu uruchomienia interfejsu GUI:
# uruchom pętlę zdarzeń okna głównego
root.mainloop()

Tworzenie interfejsu GUI przy użyciu klasy


Jak już dowiedziałeś się z innych rozdziałów, zorganizowanie kodu w klasy może
znacznie ułatwić Twoje życie programisty. Pisanie większych programów wykorzystujących
GUI poprzez definiowanie swoich własnych klas często przynosi korzyści. Więc teraz
pokażę Ci, jak napisać program korzystający z GUI poprzez zorganizowanie kodu za
pomocą klasy.

Prezentacja programu Leniwe przyciski 2


Program Leniwe przyciski 2 jest prostym programem Leniwe przyciski napisanym na
nowo przy użyciu klasy. Od strony użytkownika program wygląda dokładnie tak samo,
Tworzenie interfejsu GUI przy użyciu klasy 301

lecz za kulisami dokonałem pewnej restrukturyzacji. Na rysunku 10.9 pokazano tak


dobrze Ci znany program w działaniu.

Rysunek 10.9. To jakieś déjà vu. Program wygląda tak samo jak jego poprzednik,
mimo że „pod maską” dokonano znaczących zmian

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to leniwe_przyciski2.py.

Import modułu tkinter


Mimo że w strukturze programu zaszły znaczące zmiany, importowanie modułu GUI
wygląda wciąż tak samo:
# Leniwe przyciski 2
# Demonstruje użycie klasy w programie wykorzystującym Tkinter

from tkinter import *

Zdefiniowanie klasy Application


Następnie tworzę nową klasę, Application, na bazie klasy Frame:
class Application(Frame):
""" Aplikacja oparta na GUI z trzema przyciskami. """

Zamiast konkretyzować obiekt klasy Frame, tworzę ostatecznie obiekt klasy


Application mający pomieścić wszystkie przyciski. To działa, ponieważ obiekt klasy
Application jest tylko wyspecjalizowanym typem obiektu klasy Frame.

Zdefiniowanie konstruktora
Potem definiuję w klasie Application konstruktor:
def __init__(self, master):
""" Inicjalizuj ramkę. """
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
302 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

W pierwszej kolejności wywołuję konstruktor nadklasy. Przekazuję do niego obiekt


nadrzędny obiektu klasy Application, żeby został prawidłowo ustawiony jako obiekt
odgrywający tę rolę. Na koniec wywołuję metodę create_widgets() obiektu klasy
Application, którą zdefiniuję w następnym kroku.

Zdefiniowanie metody służącej


do utworzenia widżetów
Definiuję metodę, która tworzy wszystkie trzy przyciski, create_widgets():
def create_widgets(self):
""" Utwórz trzy przyciski, które nic nie robią. """
# utwórz pierwszy przycisk
self.bttn1 = Button(self, text = "Nic nie robię!")
self.bttn1.grid()

# utwórz drugi przycisk


self.bttn2 = Button(self)
self.bttn2.grid()
self.bttn2.configure(text = "Ja również!")

# utwórz trzeci przycisk


self.bttn3 = Button(self)
self.bttn3.grid()
self.bttn3["text"] = "To samo mnie dotyczy!"

Kod wygląda dość podobnie do kodu, który tworzy przyciski w pierwotnym


programie Leniwe przyciski. Istotna różnica polega na tym, że bttn1, bttn2 i bttn3
są teraz atrybutami obiektu klasy Application. Inną ważną różnicą jest to, że używam
zmiennej self do przekazania obiektu nadrzędnego przycisków, żeby ich obiektem
nadrzędnym został wcześniej utworzony obiekt klasy Application.

Tworzenie obiektu klasy Application


W głównej części kodu tworzę okno główne oraz nadaję mu tytuł i właściwe rozmiary:
# część główna
root = Tk()
root.title("Leniwe przyciski 2")
root.geometry("210x85")

Potem konkretyzuję obiekt klasy Application z oknem głównym w roli obiektu


nadrzędnego. Konstruktor obiektu klasy Application wywołuje metodę create_widgets()
tego obiektu. Tworzy ona wtedy trzy przyciski z obiektem klasy Application jako ich
obiektem nadrzędnym.
Na koniec wywołuję pętlę zdarzeń okna głównego w celu uruchomienia interfejsu
GUI i podtrzymywania jego pracy:
root.mainloop()
Wiązanie widżetów z procedurami obsługi zdarzeń 303

Wiązanie widżetów
z procedurami obsługi zdarzeń
Programy z interfejsem GUI poznane do tej pory przez Ciebie nie robią zbyt wiele.
Przyczyna tego tkwi w braku kodu skojarzonego z uaktywnieniem zawartych w nich
widżetów. Przypominam, że widżety są podobne do sprzętu oświetleniowego, który
został zainstalowany, ale do którego nie podłączono przewodów z prądem. Teraz
nadszedł czas, aby ten prąd popłynął; w przypadku programowania GUI, przyszła pora
na napisanie procedur obsługi zdarzeń i powiązanie ich z samymi zdarzeniami.

Prezentacja programu Licznik kliknięć


Program Licznik kliknięć zawiera przycisk, który coś robi — wyświetla liczbę wskazującą,
ile razy został kliknięty przez użytkownika. W sensie technicznym aktualizowaniem
licznika kliknięć i zmienianiem tekstu wyświetlanego na przycisku zajmuje się procedura
obsługi zdarzeń przycisku. Program został zademonstrowany na rysunku 10.10.

Rysunek 10.10. Procedura obsługi zdarzeń przycisku aktualizuje liczbę wskazującą,


ile razy przycisk został kliknięty

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to licznik_klikniec.py.

Rozpoczęcie programu
W swoim pierwszym kroku tradycyjnie importuję moduł GUI:
# Licznik kliknięć
# Demonstruje powiązanie zdarzenia z procedurą obsługi zdarzeń

from tkinter import *

Potem rozpoczynam definiowanie klasy Application:


class Application(Frame):
""" Aplikacja z GUI, która zlicza kliknięcia przycisku. """
def __init__(self, master):
""" Inicjalizuj ramkę. """
super(Application, self).__init__(master)
self.grid()
self.bttn_clicks = 0 # liczba kliknięć przycisku
self.create_widget()
304 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Większość tego kodu widziałeś już przedtem. Nowym jego elementem jest wiersz
self.bttn_clicks = 0, który tworzy atrybut obiektu, który ma rejestrować liczbę kliknięć
przycisku przez użytkownika.

Dowiązanie procedury obsługi zdarzeń


Przy użyciu metody create_widget() tworzę pojedynczy przycisk:
def create_widget(self):
""" Utwórz przycisk, który wyświetla liczbę kliknięć. """
self.bttn = Button(self)
self.bttn["text"]= "Liczba kliknięć: 0"
self.bttn["command"] = self.update_count
self.bttn.grid()

Ustawiam jako wartość opcji command widżetu Button metodę update_count(). Dzięki
temu, kiedy użytkownik kliknie przycisk, zostanie wywołana ta metoda. Z perspektywy
technicznej to, co zrobiłem, jest powiązaniem zdarzenia (kliknięcia widżetu Button)
z jego procedurą obsługi (metodą update_count()).
W ogólności, aby powiązać uaktywnienie widgetu z procedurą obsługi zdarzeń,
należy ustawić opcję command tego widżetu.

Utworzenie procedury obsługi zdarzeń


Następnie piszę metodę update_count(), która obsługuje zdarzenie kliknięcia przycisku:
def update_count(self):
""" Zwiększ licznik kliknięć i wyświetl jego nową wartość. """
self.bttn_clicks += 1
self.bttn["text"] = "Liczba kliknięć: " + str(self.bttn_clicks)

Ta metoda zwiększa całkowitą liczbę kliknięć przycisku, a następnie zmienia tekst na


przycisku tak, aby odzwierciedlał nową wartość tej liczby. To wszystko, czego potrzeba,
aby przycisk zrobił coś pożytecznego (lub prawie pożytecznego).

Dokończenie programu
Do tej pory główna część kodu powinna Ci się już wydawać całkiem znajoma:
# część główna
root = Tk()
root.title("Licznik kliknięć")
root.geometry("200x50")

app = Application(root)

root.mainloop()
Używanie widżetów Text i Entry oraz menedżera układu Grid 305

Tworzę okno główne i ustawiam jego tytuł i rozmiary. Potem konkretyzuję nowy
obiekt klasy Application z oknem głównym jako obiektem nadrzędnym. Na końcu
uruchamiam pętlę zdarzeń okna głównego, aby interfejs GUI ożył na ekranie komputera.

Używanie widżetów Text i Entry


oraz menedżera układu Grid
W programowaniu z wykorzystaniem GUI wystąpią przypadki, gdy będziesz chciał,
aby użytkownik wprowadził jakiś tekst. Innym razem będziesz chciał wyświetlić tekst
użytkownikowi. W obu tych sytuacjach będziesz mógł skorzystać z widżetów obsługujących
tekst. Zapoznam Cię z dwoma ich rodzajami. Widżet Entry dobrze się nadaje do obsługi
jednego wiersza tekstu, podczas gdy widżet Text wspaniale się spisuje przy wielowierszowych
blokach tekstu. Możesz odczytywać zawartość widżetów obu tych typów, aby pobierać
dane wprowadzone przez użytkownika. Możesz również wstawiać do nich tekst, aby
dostarczać użytkownikowi informacji zwrotnych.
Kiedy już wrzucisz do ramki pewną liczbę widżetów, będziesz potrzebować sposobu
na ich zorganizowanie. Jak do tej pory, używałem menedżera układu Grid, lecz tylko
w maksymalnie ograniczony sposób. Menedżer układu Grid oferuje Ci dużo więcej
kontroli nad wyglądem Twojego interfejsu GUI. Menedżer pozwala Ci umieszczać
widżety w określonych pozycjach dzięki traktowaniu ramki jako siatki.

Prezentacja programu Długowieczność


Program Długowieczność ujawnia sekret zapewniający dożycie sędziwego wieku 100 lat,
jeśli użytkownik wprowadzi tajne hasło (niezwykle bezpieczne hasło „sekret”). Użytkownik
wprowadza hasło w polu wejściowym, a następnie klika przycisk Akceptuj. Jeśli hasło
okazuje się prawidłowe, program wyświetla klucz do długowieczności w polu tekstowym.
Program został zademonstrowany na rysunku 10.11 i 10.12.

Rysunek 10.11. Jeśli użytkownikowi nie uda się wprowadzić prawidłowego hasła,
program grzecznie odmawia wyjawienia swojego sekretu
306 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Rysunek 10.12. Po podaniu poprawnego hasła program dzieli się swoją bezcenną wiedzą
o sposobie na długie życie

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to dlugowiecznosc.py.

Rozpoczęcie programu
Rozpoczynam program całkiem podobnie jak kilka ostatnich:
# Długowieczność
# Demonstruje widżety Text i Entry oraz menedżer układu Grid

from tkinter import *

class Application(Frame):
""" Aplikacja z GUI, która może ujawnić sekret długowieczności. """
def __init__(self, master):
""" Inicjalizuj ramkę. """
super(Application, self).__init__(master)
self.grid()
self.create_widgets()

Importuję moduł tkinter i rozpoczynam definiowanie klasy Application.


W konstruktorze inicjalizuję nowy obiekt klasy Application, upewniam się,
że obiekt będzie widoczny, oraz wywołuję jego metodę create_widgets().

Umiejscowienie widżetu
za pomocą menedżera układu Grid
Następnie uruchamiam metodę create_widgets() i tworzę etykietę, która zawiera
instrukcję dla użytkownika:
def create_widgets(self):
""" Utwórz widżety typu Button, Text i Entry . """
# utwórz etykietę z instrukcją
self.inst_lbl = Label(self, text = "Wprowadź hasło do sekretu długowieczności")
Używanie widżetów Text i Entry oraz menedżera układu Grid 307

Jak dotąd nic nowego. Lecz w kolejnym wierszu wykorzystuję menedżer układu Grid,
aby dokładnie określić położenie tej etykiety:
self.inst_lbl.grid(row = 0, column = 0, columnspan = 2, sticky = W)

Metoda grid() obiektu widżetu może pobierać wartości wielu różnych parametrów,
lecz ja wykorzystuje tylko cztery z nich: row, column, columnspan i sticky.
Parametry row i column przyjmują jako swoje wartości liczby całkowite i definiują
miejsce ulokowania obiektu w obrębie jego nadrzędnego widżetu. W tym programie
możesz sobie wyobrazić ramkę w oknie głównym jako siatkę (ang. grid) podzieloną na
wiersze i kolumny. Przecięcie dowolnego wiersza i dowolnej kolumny wyznacza komórkę,
w której możesz umieścić widżet. Na rysunku 10.13 przedstawiłem rozmieszczenie dziewięciu
widżetów Button w dziewięciu różnych komórkach, podając numery wierszy i kolumn.

Rysunek 10.13. Każdy przycisk został umieszczony w oddzielnej komórce


w zależności od numeru wiersza i kolumny

W przypadku mojego widżetu Label przekazuję 0 do parametru row oraz 0 do parametru


column, co sprawia, że etykieta zostaje umieszczona w lewym górnym rogu ramki.
Jeśli widżet jest bardzo szeroki (tak jak widżet Label z długim wierszem instrukcji
w tym programie), możesz chcieć mu umożliwić zajęcie więcej niż jednej komórki, aby
Twoje pozostałe widżety były prawidłowo rozmieszczone. Parametr columnspan pozwala
na rozciągnięcie widżetu na więcej niż jedną kolumnę. Przekazuję do tego parametru
wartość 2, aby pozwolić, by długa etykieta objęła dwie kolumny. To oznacza, że etykieta
zajmuje dwie komórki, jedną w wierszu 0 i kolumnie 0, a drugą w wierszu 0 i kolumnie 1.
(Możesz również skorzystać z parametru rowspan, aby widżet mógł obejmować więcej
niż jeden wiersz).
Nawet po ustaleniu, jaką komórkę (lub komórki) widżet zajmuje, masz jeszcze
możliwość regulacji położenia widżetu wewnątrz komórki (lub komórek) poprzez użycie
parametru sticky, który jako swoje wartości przyjmuje symbole stron świata: N, S, E i W
(północ, południe, wschód i zachód). Widżet zostaje przesunięty do tego kwadrantu
komórki (lub komórek), który odpowiada danemu kierunkowi. Ponieważ przekazuję
W do parametru sticky obiektu klasy Label, etykieta zostaje przesunięta na zachód
(w lewo). Innym sposobem wyrażenia tego jest stwierdzenie, że etykieta została
wyrównana do lewej.
308 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Następnie tworzę etykietę, która pojawia się w kolejnym wierszu i jest także
wyrównana do lewej.
# utwórz etykietę do hasła
self.pw_lbl = Label(self, text = "Hasło: ")
self.pw_lbl.grid(row = 1, column = 0, sticky = W)

Tworzenie widżetu Entry


Następnie tworzę widżet nowego typu, Entry:
# utwórz widżet Entry do przyjęcia hasła
self.pw_ent = Entry(self)

Ten kod tworzy pole wejściowe, w którym użytkownik może wprowadzić hasło.
Pozycjonuję widżet Entry w taki sposób, aby znalazł się w komórce sąsiadującej
z prawej strony z etykietą hasła:
self.pw_ent.grid(row = 1, column = 1, sticky = W)

Potem tworzę przycisk, który umożliwi użytkownikowi zaakceptowanie swojego


hasła:
# utwórz przycisk 'Akceptuj'
self.submit_bttn = Button(self, text = "Akceptuj", command = self.reveal)

Wiążę uaktywnienie przycisku z metodą reveal(), która odsłania sekret


długowieczności, o ile użytkownik wprowadził prawidłowe hasło.
Umieszczam przycisk w następnym wierszu, całkiem na lewo:
self.submit_bttn.grid(row = 2, column = 0, sticky = W)

Utworzenie widżetu Text


Następnie tworzę widżet nowego typu, Text:
# utwórz widżet Text do wyświetlenia komunikatu
self.secret_txt = Text(self, width = 35, height = 5, wrap = WORD)

Przekazuję wartości do parametrów width i height w celu ustalenia wymiarów pola


tekstowego. Dodatkowo przekazuję wartość do parametru wrap, który decyduje o sposobie
zawijania tekstu w polu. Wartości, jakie może przyjąć ten parametr, to WORD, CHAR i NONE.
Wartość WORD, której używam w tym widżecie Text, sprawia, że po osiągnięciu prawej
krawędzi pola tekstowego są zawijane całe słowa. Wartość CHAR powoduje zawijanie
znaków, co oznacza, że po osiągnięciu prawego brzegu pola tekstowego kolejny znak
pojawia się po prostu w następnym wierszu. Wartość NONE oznacza brak zawijania.
W rezultacie możesz wpisywać tekst tylko w pierwszym wierszu pola tekstowego1.

1
O ile nie używasz klawisza Enter — przyp. tłum.
Używanie widżetów Text i Entry oraz menedżera układu Grid 309

Potem ustawiam pole tekstowe w taki sposób, aby pojawiło się w nowym wierszu
i objęło dwie kolumny:
self.secret_txt.grid(row = 3, column = 0, columnspan = 2, sticky = W)

Pobieranie i wstawianie tekstu


w widżetach xEntry i Text
Następnie piszę metodę reveal(), która sprawdza, czy użytkownik wprowadził prawidłowe
hasło. Jeśli test daje wynik pozytywny, metoda wyświetla tajemnicę długiego życia.
W przeciwnym wypadku użytkownik zostaje poinformowany, że hasło było nieprawidłowe.
Pierwszą moją czynnością jest pobranie tekstu z widżetu Entry poprzez wywołanie
metody get():
def reveal(self):
""" Wyświetl komunikat zależny od poprawności hasła. """
contents = self.pw_ent.get()

Metoda get() zwraca tekst zawarty w widżecie. Metodę get() mają zarówno obiekty
klasy Entry, jak i Text.
Sprawdzam, czy ten tekst jest równy wartości łańcucha "sekret". Jeśli jest to prawda,
nadaję zmiennej message wartość łańcucha znaków opisującego sekret dożycia 100 lat.
W przeciwnym wypadku wartością zmiennej message zostaje łańcuch znaków
informujący użytkownika, że wprowadził nieprawidłowe hasło.
if contents == "sekret":
message = "Oto tajemny przepis na dożycie 100 lat: dożyj 99 lat, " \
"a potem bądź BARDZO ostrożny."
else:
message = "To nie jest poprawne hasło, więc nie mogę się z Tobą " \
"podzielić swoim sekretem."

Teraz, gdy mam już łańcuch znaków, który chcę pokazać użytkownikowi, muszę
wstawić go do widżetu Text. Najpierw usuwam jakikolwiek tekst, który już się znajduje
w widżecie Text, poprzez wywołanie metody delete() widżetu:
self.secret_txt.delete(0.0, END)

Metoda delete() usuwa tekst z widżetów tekstowych. Metoda może pobrać pojedynczy
indeks albo punkt początkowy i końcowy. Przekazujesz do niej liczby zmiennoprzecinkowe
reprezentujące pary złożone z numeru wiersza i numeru kolumny, w których cyfra
znajdująca się na lewo od kropki dziesiętnej określa numer wiersza, a cyfra na prawo
od kropki dziesiętnej wskazuje numer kolumny. Na przykład w powyższym wierszu
kodu przekazuję wartość 0.0 jako punkt początkowy, co ma oznaczać, że metoda powinna
usunąć tekst, począwszy od pozycji o numerze wiersza 0 i numerze kolumny 0
(czyli od absolutnego początku) pola tekstowego.
310 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Moduł tkinter udostępnia kilka stałych, które mogą być pomocne w korzystaniu
z tego typu metody, takich jak stała END oznaczająca koniec tekstu. Więc w powyższym
wierszu kodu usuwam wszystko od pierwszej pozycji pola tekstowego do końca.
Obydwa widżety, Text i Entry, posiadają metodę delete().
Następnie do widżetu Text wstawiam łańcuch znaków, który chcę wyświetlić:
self.secret_txt.insert(0.0, message)

Metoda insert() służy do wstawienia łańcucha znaków do widżetu tekstowego.


Metoda pobiera pozycję wstawienia i łańcuch znaków. W powyższym wierszu kodu
przekazuję wartość 0.0 jako pozycję wstawienia, co oznacza, że metoda powinna
rozpocząć wstawianie znaków łańcucha od wiersza 0 i kolumny 0. Jako drugą wartość
przekazuję message, aby w polu tekstowym pojawił się odpowiedni komunikat. Obydwa
typy widżetów, Text i Entry, dysponują metodą insert().

Pułapka
Metoda insert() nie zastępuje tekstu w widżecie tekstowym — po prostu go
wstawia. Jeśli chcesz zastąpić istniejący tekst nowym, wywołaj najpierw metodę
delete() widżetu.

Dokończenie programu
Aby sfinalizować program, tworzę okno główne i ustawiam jego tytuł i rozmiary. Następnie
tworzę nowy obiekt klasy Application z oknem głównym jako obiektem nadrzędnym.
Na koniec inicjuję działanie aplikacji poprzez uruchomienie pętli okna głównego.
# część główna
root = Tk()
root.title("Długowieczność")
root.geometry("300x150")

app = Application(root)

root.mainloop()

Wykorzystanie pól wyboru


Pola wyboru umożliwiają użytkownikowi wybór dowolnej liczby opcji z określonej
grupy. Chociaż mechanizm ten oferuje użytkownikowi dużą elastyczność, w istocie
rzeczy daje programiście większą kontrolę poprzez ograniczenie możliwości wyboru
użytkownika do określonej listy opcji.

Prezentacja programu Wybór filmów


Program Wybór filmów umożliwia użytkownikowi wybranie ulubionych gatunków
filmowych z listy trzech propozycji: komedia, dramat i romans. Ponieważ program
Wykorzystanie pól wyboru 311

wykorzystuje pola wyboru, użytkownik może wybrać tyle (choć jest to bardzo niewiele)
gatunków, ile mu się podoba. Program wyświetla wynik wyborów użytkownika w polu
tekstowym (rysunek 10.14).

Rysunek 10.14. Wyniki wyboru użytkownika pojawiają się w polu tekstowym

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to wybor_filmow.py.

Rozpoczęcie programu
Rozpoczynam program Wybór filmów od importu zawartości modułu tkinter
i przystąpienia do tworzenia definicji klasy Application:
# Wybór filmów
# Demonstruje pola wyboru

from tkinter import *

class Application(Frame):
""" Aplikacja z GUI służąca do wyboru ulubionych gatunków filmów. """
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()

Dopuszczenie, aby jedyną referencję do widżetu


utrzymywał jego obiekt nadrzędny
Następnie tworzę etykietę, która opisuje program:
def create_widgets(self):
""" Utwórz widżety służące do wyboru gatunków filmów. """
# utwórz etykietę z opisem
312 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Label(self,
text = "Wybierz swoje ulubione gatunki filmów."
).grid(row = 0, column = 0, sticky = W)

Istnieje jedna ważna różnica pomiędzy tą etykietą a innymi etykietami, które


tworzyłem: nie przypisuję nowo utworzonego obiektu klasy Label do zmiennej. Zwykle
byłby to duży błąd, sprawiający, że obiekt stałby się bezużyteczny, ponieważ nie byłby
w żaden sposób połączony z programem. Ale dzięki modułowi tkinter obiekt Label
jest połączony z programem, podobnie jak wszystkie elementy GUI, poprzez swój obiekt
nadrzędny. To oznacza, że jeśli wiem, że nie potrzebuję bezpośredniego dostępu do
widżetu, nie muszę przypisywać tego obiektu do zmiennej. Główną korzyścią, jaka
wynika z takiego podejścia, jest krótszy i bardziej przejrzysty kod.
Do tej pory byłem dość konserwatywny i zawsze przypisywałem nowo utworzony
widżet do zmiennej. Lecz w tym przypadku wiem, że nie będę potrzebował dostępu do tej
etykiety, więc nie przypisuję obiektu klasy Label do zmiennej. Zamiast tego pozwalam,
aby jego obiekt nadrzędny utrzymywał jedyną referencję do niego.
Następnie tworzę kolejną etykietę w bardzo podobny sposób:
# utwórz etykietę z instrukcją
Label(self,
text = "Zaznacz wszystkie, które chciałbyś wybrać:"
).grid(row = 1, column = 0, sticky = W)

Etykieta ta zawiera instrukcję informującą użytkownika, że może wybrać tyle


gatunków filmów, ile uważa za stosowne.

Utworzenie pól wyboru


Następnie tworzę pola wyboru, po jednym do każdego gatunku filmów. Najpierw biorę
się do pola wyboru komedii.
Każde pole wyboru potrzebuje specjalnego, związanego z nim obiektu, który
automatycznie odzwierciedla stan pola wyboru. Ten specjalny obiekt musi być instancją
klasy BooleanVar z modułu tkinter. Więc przed utworzeniem pola wyboru komedii
konkretyzuję obiekt klasy BooleanVar i przypisuję go do nowego atrybutu obiektu
Application, likes_comedy:
# utwórz pole wyboru komedii
self.likes_comedy = BooleanVar()

W świecie rzeczywistym
Zmienna typu Boolean to specjalny rodzaj zmiennej, której wartością może być
tylko prawda albo fałsz. Programiści często nazywają ją po prostu zmienną Boolean.
Termin ten jest zawsze pisany dużą literą, ponieważ został utworzony od nazwiska
angielskiego matematyka George’a Boole’a.
Wykorzystanie pól wyboru 313

Następnie tworzę samo pole wyboru:


Checkbutton(self,
text = "komedia",
variable = self.likes_comedy,
command = self.update_text
).grid(row = 2, column = 0, sticky = W)

Powyższy kod tworzy nowe pole wyboru z tekstem komedia. Poprzez przekazanie
atrybutu self.likes_comedy do parametru variable kojarzę stan pola wyboru (zaznaczone
albo niezaznaczone) z atrybutem likes_comedy. Poprzez przekazanie metody
self.update_text() do parametru command wiążę uaktywnienie pola wyboru z metodą
update_text(). To oznacza, że ilekroć użytkownik zaznacza lub kasuje zaznaczenie pola
wyboru, tylekroć zostaje wywołana metoda update_text(). W końcu umieszczam pole
wyboru w następnym wierszu, całkiem z lewej strony.
Zwróć uwagę, że nie przypisuję nowo utworzonego obiektu Checkbutton do zmiennej.
Wszystko jest dobrze, ponieważ tak naprawdę interesuje mnie tylko stan pola, do którego
mam dostęp poprzez atrybut likes_comedy.
Kolejne dwa pola wyboru tworzę w taki sam sposób:
# utwórz pole wyboru dramatu filmowego
self.likes_drama = BooleanVar()
Checkbutton(self,
text = "dramat",
variable = self.likes_drama,
command = self.update_text
).grid(row = 3, column = 0, sticky = W)

# utwórz pole wyboru romansu


self.likes_romance = BooleanVar()
Checkbutton(self,
text = "romans",
variable = self.likes_romance,
command = self.update_text
).grid(row = 4, column = 0, sticky = W)

Więc za każdym razem, gdy użytkownik zaznacza pola wyboru dramatu i romansu
lub gdy anuluje ich zaznaczenie, wywoływana jest metoda update_text(). I jeśli nawet
nie przypisuję powstałych obiektów klasy Checkbutton do żadnych zmiennych, zawsze
mogę sprawdzić stan pola wyboru dramatu poprzez atrybut likes_drama oraz zawsze
mogę sprawdzić stan pola wyboru romansu poprzez atrybut likes_romance.
W końcu tworzę pole tekstowe, które wykorzystuję do pokazania wyników wyborów
użytkownika:
# utwórz pole tekstowe do wyświetlenia wyników
self.results_txt = Text(self, width = 40, height = 5, wrap = WORD)
self.results_txt.grid(row = 5, column = 0, columnspan = 3)
314 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Odczytywanie stanu pola wyboru


Następnie piszę metodę update_text(), która aktualizuje zawartość pola tekstowego
w taki sposób, aby odzwierciedlała zaznaczone przez użytkownika pola wyboru:
def update_text(self):
""" Zaktualizuj pole tekstowe i wyświetl ulubione gatunki filmów użytkownika."""
likes = ""

if self.likes_comedy.get():
likes += "Lubisz filmy komediowe.\n"

if self.likes_drama.get():
likes += "Lubisz dramaty filmowe.\n"

if self.likes_romance.get():
likes += "Lubisz filmy romantyczne."

self.results_txt.delete(0.0, END)
self.results_txt.insert(0.0, likes)
Nie można uzyskać bezpośredniego dostępu do wartości obiektu BooleanVar. Dlatego
musisz wywołać metodę get() tego obiektu. W powyższym kodzie wykorzystuję metodę
get() obiektu BooleanVar, do którego odwołuje się atrybut likes_comedy, aby uzyskać
wartość obiektu. Jeśli tą wartością jest prawda, co oznacza, że pole wyboru komedii
zostało zaznaczone, dodaję łańcuch "Lubisz filmy komediowe.\n" do łańcucha
konstruowanego w celu wyświetlenia go w polu tekstowym. Wykonuję podobne operacje
na podstawie stanu pól wyboru dramatu i romansu. Na koniec usuwam cały tekst
znajdujący się w polu tekstowym, a następnie wstawiam nowy łańcuch, likes,
który właśnie zbudowałem.

Dokończenie programu
Kończę program znajomą główną częścią. Tworzę okno główne i nowy obiekt klasy
Application z oknem głównym jako obiektem nadrzędnym. Następnie uruchamiam
pętlę zdarzeń okna.
# część główna
root = Tk()
root.title("Wybór filmów")
app = Application(root)
root.mainloop()

Wykorzystanie przycisków opcji


Przyciski opcji są bardzo podobne do pól wyboru z tym wyjątkiem, że umożliwiają
jednoczesny wybór tylko jednego przycisku z grupy. Jest to znakomite w sytuacji, gdy
chcesz, aby użytkownik dokonał wyboru jednej opcji z grupy możliwych do wybrania.
Wykorzystanie przycisków opcji 315

Ponieważ przyciski opcji mają tak dużo wspólnego z polami wyboru, nauczenie się ich
stosowania jest dosyć proste.

Prezentacja programu Wybór filmów 2


Program Wybór filmów 2 jest podobny do programu Wybór filmów. Użytkownikowi
zostają przedstawione trzy różne gatunki filmów, spośród których powinien dokonać
wyboru. Różnica polega na tym, że program Wybór filmów 2 wykorzystuje przyciski
opcji zamiast pól wyboru, więc użytkownik może wybrać tylko jeden gatunek filmu.
Jest to doskonała okoliczność, ponieważ program prosi użytkownika o wybranie
ulubionego gatunku filmu. Program został zademonstrowany na rysunku 10.15.

Rysunek 10.15. Użytkownik może wybrać tylko jeden gatunek filmu

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 10.; nazwa pliku
to wybor_filmow2.py.

Rozpoczęcie programu
Rozpoczynam program od importu zawartości modułu tkinter:
# Wybór filmów 2
# Demonstruje przyciski opcji

from tkinter import *

Następnie piszę kod klasy Application. Definiuję jej konstruktor, który inicjalizuje
nowy obiekt klasy Application:
class Application(Frame):
""" Aplikacja z GUI służąca do wyboru ulubionego gatunku filmowego. """
def __init__(self, master):
""" Inicjalizuj ramkę. """
316 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

super(Application, self).__init__(master)
self.grid()
self.create_widgets()

Następnie tworzę etykiety zawierające instrukcje dla użytkowników:


def create_widgets(self):
""" Utwórz widżety umożliwiające wybór gatunku filmowego. """
# utwórz etykietę z opisem
Label(self,
text = "Wybierz swój ulubiony gatunek filmu"
).grid(row = 0, column = 0, sticky = W)

# utwórz etykietę z instrukcją


Label(self,
text = "Wybierz jeden gatunek:"
).grid(row = 1, column = 0, sticky = W)

Utworzenie przycisków opcji


Ponieważ tylko jeden przycisk opcji w grupie może jednocześnie zostać wybrany,
nie istnieje potrzeba, aby każdy przycisk opcji miał własną zmienną stanu, jak to było
wymagane w przypadku pól wyboru. Natomiast grupa przycisków opcji współdzieli
jeden specjalny obiekt odzwierciedlający to, który z przycisków opcji został wybrany.
Ten obiekt może być instancją klasy StringVar z modułu tkinter, która umożliwia
przechowywanie i pobieranie łańcucha znaków. Więc przed utworzeniem samych
przycisków opcji tworzę pojedynczy obiekt klasy StringVar, który ma być
współużytkowany przez przyciski opcji, przypisuję go do atrybutu favorite i ustawiam
jego wartość początkową na None przy użyciu metody set() obiektu:
# utwórz zmienną, która ma reprezentować pojedynczy, ulubiony gatunek filmu
self.favorite = StringVar()
self.favorite.set(None)

Następnie tworzę przycisk opcji komedia:


# utwórz przycisk opcji do wyboru komedii
Radiobutton(self,
text = "komedia",
variable = self.favorite,
value = "komedia.",
command = self.update_text
).grid(row = 2, column = 0, sticky = W)

Opcja variable przycisku definiuje specjalną zmienną związaną z tym przyciskiem,


podczas gdy opcja value definiuje wartość, która ma być przechowywana przez zmienną
specjalną, gdy przycisk jest wybrany. Więc poprzez ustawienie opcji variable tego
przycisku na self.favorite, a jego opcji value na "komedia." deklaruję, że wtedy,
gdy przycisk jest zaznaczony, obiekt klasy StringVar, do którego odwołuje się atrybut
self.favorite, powinien przechowywać łańcuch "komedia.".
Wykorzystanie przycisków opcji 317

Następnie tworzę dwa pozostałe przyciski opcji:


# utwórz przycisk opcji do wyboru dramatu
Radiobutton(self,
text = "dramat",
variable = self.favorite,
value = "dramat.",
command = self.update_text
).grid(row = 3, column = 0, sticky = W)

# utwórz przycisk opcji do wyboru romansu


Radiobutton(self,
text = "romans",
variable = self.favorite,
value = "romans.",
command = self.update_text
).grid(row = 4, column = 0, sticky = W)

Poprzez ustawienie opcji variable przycisku dramat na self.favorite, a jego opcji


value na "dramat." deklaruję, że wtedy, gdy ten przycisk jest wybrany, obiekt klasy
StringVar, do którego odwołuje się atrybut self.favorite, powinien przechowywać
łańcuch "dramat.".
A poprzez ustawienie opcji variable przycisku romans na self.favorite, a jego opcji
value na "romans." deklaruję, że wtedy, gdy ten przycisk jest wybrany, obiekt klasy
StringVar, do którego odwołuje się atrybut self.favorite, powinien przechowywać
łańcuch "romans.".
Następnie tworzę pole tekstowe służące do wyświetlania wyników wyboru
użytkownika:
# utwórz pole tekstowe do wyświetlania wyników
self.results_txt = Text(self, width = 40, height = 5, wrap = WORD)
self.results_txt.grid(row = 5, column = 0, columnspan = 3)

Pobranie wartości z grupy przycisków opcji


Pobranie wartości z grupy przycisków opcji jest proste i sprowadza się do wywołania
metody get() obiektu StringVar, który jest wykorzystywany wspólnie przez wszystkie
przyciski:
def update_text(self):
""" Zaktualizuj pole tekstowe i wyświetl ulubiony gatunek filmu użytkownika. """
message = "Twoim ulubionym gatunkiem filmu jest "
message += self.favorite.get()

Kiedy jest zaznaczony przycisk opcji komedia, metoda self.favorite.get()


zwraca łańcuch "komedia."; gdy jest wybrany przycisk opcji dramat, metoda
self.favorite.get() zwraca łańcuch "dramat."; a kiedy jest zaznaczony przycisk opcji
romans, metoda self.favorite.get() zwraca łańcuch "romans.".
318 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Następnie usuwam wszelki tekst, jaki może znajdować się w polu tekstowym,
i wstawiam świeżo utworzony łańcuch znaków, który oznajmia, jaki jest ulubiony
gatunek filmu użytkownika:
self.results_txt.delete(0.0, END)
self.results_txt.insert(0.0, message)

Dokończenie programu
Finalizuję program poprzez utworzenie okna głównego i nowego obiektu klasy
Application. Następnie inicjuję pętlę zdarzeń okna głównego w celu uruchomienia
interfejsu GUI.
# część główna
root = Tk()
root.title("Wybór filmów 2")
app = Application(root)
root.mainloop()

Powrót do programu Mad Lib


Teraz, gdy już widziałeś dość dużo różnych widżetów wykorzystywanych osobno, nadszedł
czas, aby połączyć je w jeden większy interfejs GUI. Nie wprowadzam żadnych nowych
koncepcji w programie Mad Lib, więc nie komentuję zanadto kodu. Kod tego programu
możesz znaleźć na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze rozdziału 10.; nazwa pliku to mad_lib.py.

Import modułu tkinter


Jak już do tej pory zapewne wiesz, musisz zaimportować zawartość modułu tkinter
przed jej wykorzystaniem:
# Mad Lib
# Tworzy opowiadanie oparte na szczegółach wprowadzonych przez użytkownika

from tkinter import *

Konstruktor klasy Application


Podobnie jak wszystkie poprzednie konstruktory klasy Application także i ten inicjalizuje
nowo utworzony obiekt klasy Application i wywołuje metodę create_widgets():
class Application(Frame):
"""
Aplikacja z GUI, która tworzy opowiadanie oparte na danych
wprowadzonych przez użytkownika.
"""
Powrót do programu Mad Lib 319

def __init__(self, master):


""" Inicjalizuj ramkę. """
super(Application, self).__init__(master)
self.grid()
self.create_widgets()

Metoda create_widgets() klasy Application


Ta klasa tworzy wszystkie widżety interfejsu GUI. Jedyną nową rzeczą, jaką robię, jest
utworzenie wszystkich trzech przycisków opcji w pętli poprzez przejście przez listę łańcuchów
znaków, które zostają przypisane do opcji text i value każdego kolejnego przycisku:
def create_widgets(self):
"""
Utwórz widżety potrzebne do pobrania informacji podanych przez
użytkownika i wyświetlenia opowiadania.
"""
# utwórz etykietę z instrukcją
Label(self,
text = "Wprowadź informacje do nowego opowiadania"
).grid(row = 0, column = 0, columnspan = 2, sticky = W)

# utwórz etykietę i pole znakowe służące do wpisania imienia osoby


Label(self,
text = "Osoba: "
).grid(row = 1, column = 0, sticky = W)
self.person_ent = Entry(self)
self.person_ent.grid(row = 1, column = 1, sticky = W)

# utwórz etykietę i pole znakowe do wpisania rzeczownika w liczbie mnogiej


Label(self,
text = "Rzeczownik w liczbie mnogiej:"
).grid(row = 2, column = 0, sticky = W)
self.noun_ent = Entry(self)
self.noun_ent.grid(row = 2, column = 1, sticky = W)

# utwórz etykietę i pole znakowe służące do wpisania czasownika


Label(self,
text = "Czasownik:"
).grid(row = 3, column = 0, sticky = W)
self.verb_ent = Entry(self)
self.verb_ent.grid(row = 3, column = 1, sticky = W)

# utwórz etykietę do pól wyboru przymiotników


Label(self,
text = "Przymiotnik(i):"
).grid(row = 4, column = 0, sticky = W)

# utwórz pole wyboru przymiotnika 'naglące'


self.is_itchy = BooleanVar()
Checkbutton(self,
text = "naglące",
320 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

variable = self.is_itchy
).grid(row = 4, column = 1, sticky = W)

# utwórz pole wyboru przymiotnika 'radosne'


self.is_joyous = BooleanVar()
Checkbutton(self,
text = "radosne",
variable = self.is_joyous
).grid(row = 4, column = 2, sticky = W)

# utwórz pole wyboru przymiotnika 'elektryzujące'


self.is_electric = BooleanVar()
Checkbutton(self,
text = "elektryzujące",
variable = self.is_electric
).grid(row = 4, column = 3, sticky = W)

# utwórz etykietę do przycisków opcji odnoszących się do części ciała


Label(self,
text = "Część ciała:"
).grid(row = 5, column = 0, sticky = W)

# utwórz zmienną na pojedynczą część ciała


self.body_part = StringVar()
self.body_part.set(None)

# utwórz przyciski opcji odnoszące się do części ciała


body_parts = ["pępek", "duży palec u nogi", "rdzeń przedłużony"]
column = 1
for part in body_parts:
Radiobutton(self,
text = part,
variable = self.body_part,
value = part
).grid(row = 5, column = column, sticky = W)
column += 1

# utwórz przycisk akceptacji danych


Button(self,
text = "Kliknij, aby wyświetlić opowiadanie",
command = self.tell_story
).grid(row = 6, column = 0, sticky = W)

self.story_txt = Text(self, width = 75, height = 10, wrap = WORD)


self.story_txt.grid(row = 7, column = 0, columnspan = 4)

Metoda tell_story() klasy Application


W tej metodzie pobieram wartości wprowadzone przez użytkownika i wykorzystuję je
do utworzenia jednego, długiego łańcucha reprezentującego opowiadanie. Następnie
usuwam wszelki tekst znajdujący się w polu tekstowym oraz wstawiam ten nowy łańcuch
znaków, aby pokazać użytkownikowi opowiadanie, które utworzył.
Powrót do programu Mad Lib 321

def tell_story(self):
""" Wpisz w pole tekstowe nowe opowiadanie oparte na danych użytkownika. """
# pobierz wartości z interfejsu GUI
person = self.person_ent.get()
noun = self.noun_ent.get()
verb = self.verb_ent.get()
adjectives = ""
if self.is_itchy.get():
adjectives += "naglące, "
if self.is_joyous.get():
adjectives += "radosne, "
if self.is_electric.get():
adjectives += "elektryzujące, "
body_part = self.body_part.get()

# create the story


story = "Sławny badacz i odkrywca "
story += person
story += " o mało co nie zrezygnował z życiowej misji poszukiwania "
story += "zaginionego miasta, które zamieszkiwały "
story += noun
story += ", gdy pewnego dnia "
story += noun
story += " znalazły "
story += person + "a. "
story += "Silne, "
story += adjectives
story += "osobliwe uczucie owładnęło badaczem. "
story += "Po tak długim czasie poszukiwanie wreszcie się zakończyło. W oku "
story += person + "a pojawiła się łza, która spadła na jego "
story += body_part + ". "
story += "A wtedy "
story += noun
story += " szybko pożarły "
story += person + "a. "
story += "Jaki morał płynie z tego opowiadania? Pomyśl, zanim zaczniesz coś "
story += verb
story += "."

# wyświetl opowiadanie
self.story_txt.delete(0.0, END)
self.story_txt.insert(0.0, story)

Główna część programu


Widziałeś już przedtem ten tekst niemało razy. Tworzę okno główne oraz instancję klasy
Application. Potem uruchamiam cały interfejs GUI poprzez wywołanie metody
mainloop() obiektu root.
# część główna
root = Tk()
root.title("Mad Lib")
app = Application(root)
root.mainloop()
322 Rozdział 10. Tworzenie interfejsów GUI. Gra Mad Lib

Podsumowanie
W tym rozdziale poznałeś tworzenie interfejsów GUI. Najpierw dowiedziałeś się
o programowaniu sterowanym zdarzeniami, nowym sposobie spojrzenia na pisanie kodu.
Potem poznałeś szereg widżetów GUI, a wśród nich ramki, przyciski, pola wejściowe,
pola tekstowe, pola wyboru oraz przyciski opcji. Zobaczyłeś, jak przystosowywać widżety
do swoich potrzeb. Zobaczyłeś także, jak organizować je w ramce przy użyciu menedżera
układu Grid. Dowiedziałeś się, jak wiązać zdarzenia z procedurami ich obsługi, aby
widżety coś robiły po ich uaktywnieniu. Na koniec zobaczyłeś, jak łączyć poszczególne
elementy w dość skomplikowany interfejs GUI, tworząc zabawny program Mad Lib.

Sprawdź swoje umiejętności


1. Napisz swoją własną wersję programu Mad Lib, wykorzystując inny układ
widżetów.
2. Napisz wersję programu Jaka to liczba?, stanowiącego projekt rozdziału 3.,
która wykorzystuje interfejs GUI.
3. Utwórz program z interfejsem GUI o nazwie Złóż zamówienie!, który
przedstawia użytkownikowi proste restauracyjne menu, które wymienia
pozycje i ich ceny. Pozwól użytkownikowi na wybranie różnych pozycji,
a potem pokaż mu końcowy rachunek.
11
Grafika.
Gra Pizza Panic

W iększość programów, z którymi dotychczas się spotkałeś, skupiała się na prezentacji


tekstu, lecz dzisiaj ludzie oczekują od programów bogatej, wizualnej treści,
niezależnie od ich zastosowania. W tym rozdziale dowiesz się, jak używać grafiki za
pomocą kilku multimedialnych modułów zaprojektowanych do pisania gier w języku
Python. W szczególności nauczysz się:
 tworzyć okno graficzne,
 tworzyć duszki i manipulować nimi,
 wyświetlać tekst w oknie graficznym,
 sprawdzać, czy nie dochodzi do kolizji między duszkami,
 obsługiwać dane wejściowe z myszy,
 sterować komputerowym przeciwnikiem.

Wprowadzenie do gry Pizza Panic


Główny projekt tego rozdziału, gra Pizza Panic, obejmuje szalonego szefa kuchni,
głęboką patelnię i znaczną liczbę latających pizz. Oto scenariusz gry: wyprowadzony
z równowagi przez o jednego za dużo wybrednych gości szef kuchni w lokalnej pizzerii
wyszedł na dach i z wściekłością ciska pizze, skazując je na zmarnowanie. Oczywiście
pizze muszą zostać uratowane. Przy użyciu myszy gracz steruje patelnią, manewrując nią
w taki sposób, aby złapać spadające pizze. Zdobycz punktowa gracza zwiększy się wraz
z każdą złapaną pizzą, ale kiedy choć tylko jedna spadnie na ziemię, gra się kończy.
Na rysunkach 11.1 i 11.2 pokazałem tę grę w akcji.
324 Rozdział 11. Grafika. Gra Pizza Panic

Rysunek 11.1. Gracz musi łapać spadające pizze

Rysunek 11.2. Kiedy graczowi nie udaje się złapać jakiejś pizzy, gra się kończy
Wprowadzenie do pakietów pygame i livewires 325

Wprowadzenie do pakietów pygame i livewires


Pakiety (ang. packages) pygame i livewires to zestawy modułów, które dają programistom
Pythona dostęp do szerokiego wachlarza klas multimediów. Za pomocą tych klas możesz
tworzyć programy z grafiką, efektami dźwiękowymi, muzyką i animacją. Te pakiety
umożliwiają także obsługę danych wejściowych z rozmaitych urządzeń, w tym z myszy
i klawiatury. Dzięki nim nie będziesz musiał się troszczyć o niskopoziomowe szczegóły
dotyczące sprzętu, takie jak rodzaj karty graficznej posiadanej przez gracza lub to,
czy w ogóle ma jakąś kartę graficzną. Będziesz za to mógł się skoncentrować na logice
programu i zabrać się szybko do pisania gier.
Pakiet pygame stanowi tajną broń w Twoim arsenale obsługi mediów. Napisany
przez Pete’a Shinnersa pakiet umożliwia pisanie w Pythonie imponujących programów
multimedialnych. Ponieważ pakiet ma tak wielkie możliwości, może nieco przytłaczać
niedoświadczonego programistę.
Pakiet livewires napisany przez grupę edukatorów w Zjednoczonym Królestwie
został zaprojektowany w celu wykorzystania potencjału pakietu pygame przy jednoczesnej
redukcji jego złożoności dla programisty. Pakiet livewires udostępnia prostszy sposób
na rozpoczęcie programowania gier z grafiką i dźwiękiem. I jeśli nawet nie będziesz
bezpośrednio korzystać z dostępu do pakietu pygame, będzie on nadal obecny, pracując
ciężko na zapleczu.
Zanim będziesz mógł uruchomić programy prezentowane w tym rozdziale, musisz
zainstalować zarówno pygame, jak i livewires. Na szczęście wersje obydwu pakietów
znajdują się na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm),
w folderze Software. Postępuj zgodnie z instrukcjami instalacji, które towarzyszą tym
pakietom.

Pułapka
Chociaż zachęcam Cię do odwiedzenia witryny organizacji LiveWires o adresie
http://www.livewires.org.uk, musisz mieć na uwadze, że pakiet livewires
dostępny na stronie internetowej tej książki (http://www.helion.pl/ksiazki/
pytdk3.htm) jest zmodyfikowaną wersją pakietu utworzonego przez LiveWires.
Zaktualizowałem ten pakiet, aby uczynić go jeszcze prostszym w użyciu
dla programistów. I nie martw się — zmodyfikowaną wersję dokumentacji
zamieściłem w dodatku B.

Jeśli chcesz się dowiedzieć więcej o pakiecie pygame, odwiedź stronę tego pakietu
o adresie http://www.pygame.org.

Tworzenie okna graficznego


Zanim zaczniesz wyświetlać jakąkolwiek grafikę, musisz utworzyć okno graficzne —
puste tło, na którym można wyświetlać tekst i obrazy.
326 Rozdział 11. Grafika. Gra Pizza Panic

Prezentacja programu Nowe okno graficzne


Utworzenie okna graficznego za pomocą pakietu livewires to pestka. Program Nowe okno
graficzne tworzy puste okno graficzne w zaledwie paru wierszach kodu. Na rysunku 11.3
został pokazany wynik uruchomienia programu.

Rysunek 11.3. Moje pierwsze okno graficzne. Niby nic wielkiego, ale jest moje

Pułapka
Tak jak w przypadku programu używającego zestawu narzędzi Tkinter, tworząc
nowe okno, nie powinieneś uruchamiać programu wykorzystującego livewires
w środowisku IDLE. Jeśli używasz systemu Windows, utwórz plik wsadowy,
który uruchamia Twój program w języku Python, a następnie wstrzymuje swoje
działanie. Aby sprawdzić, co zawiera taki plik wsadowy, zajrzyj do punktu
„Prezentacja programu Prosty interfejs GUI” w rozdziale 10.

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to nowe_okno_graficzne.py.
Tworzenie okna graficznego 327

Import modułu games


Pakiet livewires składa się z kilku ważnych modułów, w tym modułu games, który
zawiera grupę obiektów i klas służących do programowania gier. Możesz zaimportować
konkretny moduł pakietu przy użyciu instrukcji from. W tym celu użyj słowa kluczowego
from, następnie podaj nazwę pakietu, słowo kluczowe import oraz nazwę modułu
(lub listę nazw modułów oddzielonych przecinkami).
Pierwszą moją czynnością w programie jest import modułu games pakietu livewires:
# Nowe okno graficzne
# Demonstruje tworzenie okna graficznego

from livewires import games

W rezultacie mogę używać modułu games tak jak każdego innego modułu, który
zaimportowałem. Aby poznać moduł games w ogólnym zarysie, zajrzyj do tabeli 11.1,
która zawiera listę użytecznych obiektów modułu, oraz do tabeli 11.2, zawierającej wykaz
przydatnych klas.

Tabela 11.1. Przydatne obiekty modułu games


Obiekt Opis
screen Umożliwia dostęp do ekranu graficznego — obszaru, w którym mogą
istnieć obiekty graficzne
mouse Umożliwia dostęp do myszy.
keyboard Umożliwia dostęp do klawiatury.

Tabela 11.2. Przydatne klasy modułu games


Klasa Opis
Sprite Służy do tworzenia obiektów graficznych, które mogą zostać wyświetlone
na ekranie graficznym.
Text Podklasa klasy Sprite. Służy do tworzenia obiektów tekstowych
wyświetlanych na ekranie graficznym
Message Podklasa klasy Text. Służy do tworzenia obiektów tekstowych
wyświetlanych na ekranie graficznym, które znikają po określonym czasie.

Inicjalizacja ekranu graficznego


Następnie inicjalizuję ekran graficzny:
games.init(screen_width = 640, screen_height = 480, fps = 50)

Kiedy wywołujesz funkcję games.init(), tworzysz nowy ekran graficzny. Szerokość


ekranu jest określona przez wartość parametru screen_width, podczas gdy (tu proszę
o werble) wysokość ekranu jest określona przez wartość parametru screen_height.
Rozmiary ekranu są mierzone w pikselach — pojedynczych punktach obszaru grafiki.
328 Rozdział 11. Grafika. Gra Pizza Panic

Wartość parametru fps (ang. frames per second — liczba ramek na sekundę) decyduje, ile
razy w ciągu każdej sekundy ekran będzie aktualizowany.

Uruchomienie głównej pętli


Ostatni wiersz programu to:
games.screen.mainloop()

Słowo screen oznacza obiekt z modułu games, który reprezentuje ekran graficzny.
Metoda mainloop() jest wołem roboczym obiektu screen i aktualizuje okno graficzne,
wyświetlając wszystko na nowo fps razy na sekundę. Tak więc ostatni wiersz programu
utrzymuje otwarte okno graficzne i aktualizuje ekran 50 razy na sekundę. Przyjrzyj się
kilku właściwościom obiektu screen wymienionym w tabeli 11.3. Listę przydatnych
metod obiektu screen znajdziesz w tabeli 11.4.

Tabela 11.3. Przydatne właściwości obiektu screen


Właściwość Opis
width Szerokość ekranu.
height Wysokość ekranu.
fps Częstotliwość aktualizacji ekranu wyrażona w ramkach na sekundę.
background Obraz stanowiący tło ekranu.
all objects Lista wszystkich duszków na ekranie.
event_grab Właściwość typu Boolean, której wartość decyduje, czy sygnały
wejściowe są przechwytywane przez ekran. Wartość True oznacza
przechwytywanie sygnałów wejściowych przez ekran. Wartość False
oznacza brak przechwytywania.

Tabela 11.4. Przydatne metody obiektu screen

Metoda Opis
add(sprite) Dodaje sprite, obiekt klasy Sprite (lub obiekt klasy pochodnej klasy
Sprite) do ekranu graficznego.
clear() Usuwa wszystkie duszki z ekranu graficznego.
mainloop() Uruchamia główną pętlę ekranu graficznego.
quit() Zamyka okno graficzne.

Ustawienie obrazu tła


Pusty ekran pasuje całkiem dobrze, jeśli Twoim celem jest utworzenie najnudniejszego
programu na świecie. Na szczęście obiekt screen ma właściwość służącą do ustawienia
obrazu tła.
Ustawienie obrazu tła 329

Prezentacja programu Obraz tła


Program Obraz tła jest tylko modyfikacją programu Nowe okno graficzne. Do ekranu
graficznego dodałem obraz tła pokazany na rysunku 11.4.

Rysunek 11.4. Dzięki wykorzystaniu właściwości background obiektu screen


dodaję do okna graficznego obraz tła

Aby utworzyć program Obraz tła, do programu Nowe okno graficzne dodaję
dwa wiersze, umieszczając je bezpośrednio przed wywołaniem metody mainloop().
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to obraz_tla.py.
# Obraz tła
# Demonstruje ustawienie obrazu tła ekranu graficznego

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

wall_image = games.load_image("sciana.jpg", transparent = False)


games.screen.background = wall_image

games.screen.mainloop()
330 Rozdział 11. Grafika. Gra Pizza Panic

Załadowanie obrazu
Zanim będziesz mógł zrobić cokolwiek z obrazem, ustawiając go na przykład jako tło
ekranu graficznego, musisz załadować ten obraz do pamięci, aby utworzyć obiekt obrazu.
Ładuję obraz poprzez dodanie bezpośrednio po inicjalizacji okna graficznego
następującego wiersza kodu:
wall_image = games.load_image("sciana.jpg", transparent = False)

Wywołuję w nim funkcję load_image() z modułu games, która ładuje obraz


przechowywany w pliku sciana.jpg do pamięci operacyjnej i przypisuje utworzony
w ten sposób obiekt obrazu do zmiennej wall_image.

Pułapka
Upewnij się, że każdemu plikowi, do którego Twój program w języku Python
ma mieć dostęp, towarzyszyła prawidłowa informacja o ścieżce, o czym
dowiedziałeś się w rozdziale 7., w punkcie „Otwarcie i zamknięcie pliku”.
Najprostszym rozwiązaniem w zarządzaniu plikami, które stosuję w tym
przypadku, jest umieszczenie plików z obrazem w tym samym folderze, w którym
się znajduje ładujący je program. Jeśli będziesz naśladować ten sposób, nie
będziesz w ogóle musiał się martwić o informacje dotyczące ścieżek dostępu.

Funkcja load_image() przyjmuje dwa argumenty: łańcuch reprezentujący nazwę


pliku z obrazem oraz wartość True albo False poprzez parametr transparent. Dokładnie
wyjaśnię, co oznacza transparent, nieco później w tym rozdziale. Jak na razie wystarczy,
że zapamiętasz tę regułę: zawsze ładuj obraz tła, stosując przypisanie transparent = False.
Zapewne zauważysz, że jako tło ładuję w tym programie obraz typu JPEG. Nie jesteś
jednak ograniczony do obrazów JPEG, gdy korzystasz z funkcji load_image(). Działa ona
tak samo dobrze z wieloma innymi typami plików zawierających obraz, w tym BMP, GIF,
PNG, PCX oraz TGA.

Ustawienie tła
Aby ustawić obiekt obrazu jako tło ekranu, musisz jedynie użyć właściwości background
obiektu screen. Bezpośrednio po załadowaniu obrazu dodaję następujący wiersz kodu:
games.screen.background = wall_image

W ten sposób ustawiam jako tło ekranu obiekt obrazu wall_image.


Kiedy program napotyka wywołanie metody mainloop(), utrzymuje otwarte okno
graficzne z jego nowym obrazem tła, aby każdy mógł go zobaczyć.
Układ współrzędnych ekranu graficznego 331

Układ współrzędnych ekranu graficznego


Do tej pory utworzyłem już kilka ekranów graficznych, które za każdym razem miały
szerokość 640 i wysokość 480, ale poza tym nie powiedziałem o nich zbyt wiele. Teraz
przyjrzymy się dokładniej ekranowi i jego układowi współrzędnych.
Można sobie wyobrazić ekran graficzny jako siatkę 640 kolumn i 480 wierszy. Każde
przecięcie kolumny i wiersza wyznacza miejsce na ekranie, pojedynczy punkt, czyli piksel.
Gdy masz na myśli określony punkt ekranu, podajesz jego dwie współrzędne: x, która
reprezentuje kolumnę, oraz y, która reprezentuje wiersz. Początkiem układu współrzędnych
jest lewy górny róg ekranu. Zarówno współrzędna x tego punktu, jak i współrzędna y
mają wartość 0, co się zapisuje w postaci pary (0,0). W miarę przesuwania się w prawo
wzrastają wartości współrzędnej x. Gdy przesuwasz się w dół ekranu, wzrastają wartości
współrzędnej y. W ten sposób punkt w prawym dolnym rogu ekranu otrzymuje
współrzędne (639, 479). Układ współrzędnych ekranu graficznego został przedstawiony
wizualnie na rysunku 11.5.

Rysunek 11.5. Położenie punktów na ekranie graficznym określasz


za pomocą pary współrzędnych x i y

Obiekty graficzne, takie jak obrazek pizzy lub czerwonego koloru tekst „Koniec gry”,
możesz umieszczać na ekranie, wykorzystując układ współrzędnych. Środek obiektu
graficznego zostaje umieszczony w punkcie o podanych współrzędnych. Zobaczysz
dokładnie, jak to działa, na przykładzie następnego programu.
332 Rozdział 11. Grafika. Gra Pizza Panic

Wyświetlanie duszka
Obrazy tła mogą przyozdobić gładki ekran graficzny, ale nawet oszałamiające tło
pozostaje nadal tylko obrazem statycznym. Ekran graficzny z samym tylko obrazem tła
przypomina pustą scenę. Potrzebni są jacyś aktorzy. Niech pojawi się na nim duszek.
Duszek (ang. sprite) to obiekt graficzny z obrazem, który rzeczywiście potrafi ożywić
programy. Duszki są wykorzystywane w grach, oprogramowaniu służącemu rozrywce,
prezentacjach oraz w całej sieci WWW. Właściwie już widziałeś przykłady duszków
w grze Pizza Panic. Szalony kucharz, patelnia, pizze — to wszystko duszki.

W świecie rzeczywistym
Duszki mają zastosowanie nie tylko w grach. Istnieje wiele przypadków dotyczących
oprogramowania, które nie służy rozrywce, gdzie są one używane… lub nadużywane.
Zapewne znacie najsławniejszego duszka w historii oprogramowania aplikacji,
asystenta pakietu Microsoft Office o nazwie Clippy — animowany spinacz, który
miał z założenia udzielać użytecznych wskazówek użytkownikom. Wielu ludzi
jednak uważało, że Clippy jest natrętny i denerwujący. Jeden z większych portali
internetowych zamieścił nawet artykuł zatytułowany Kill Clippy! (zabijcie Clippy’ego).
W końcu Microsoft przejrzał na oczy. Począwszy od pakietu Office 2007, Clippy
jest już niedostępny. Więc chociaż grafika może sprawić, że programy staną się
bardziej interesujące, pamiętaj o tym, aby wykorzystywać moce duszków tylko
w dobrym celu, a nigdy w złym.

Chociaż byłoby zabawne i ciekawe widzieć chmarę duszków, które fruwają dokoła
i wpadają na siebie, rozpocznę od postawienia pierwszego kroku — wyświetlenia
pojedynczego, nieruchomego duszka.

Prezentacja programu Duszek pizzy


W programie Duszek pizzy tak jak poprzednio tworzę okno graficzne i ustawiam obraz
tła. Jednak po wykonaniu tego kroku, tworzę duszka pizzy w samym środku ekranu.
Wynik uruchomienia tego programu został pokazany na rysunku 11.6.
Program Duszek pizzy jest tylko modyfikacją programu Obraz tła. Aby mógł pojawić się
na ekranie nowy duszek, dodaję tylko trzy wiersze kodu bezpośrednio przed wywołaniem
metody mainloop(). Kod tego programu możesz znaleźć na stronie internetowej tej
książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa
pliku to duszek_pizzy.py.
# Duszek pizzy
# Demonstruje tworzenie duszka

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

wall_image = games.load_image("sciana.jpg", transparent = False)


games.screen.background = wall_image
Wyświetlanie duszka 333

Rysunek 11.6. Obrazek pizzy nie jest częścią tła, lecz niezależnym obiektem klasy Sprite

pizza_image = games.load_image("pizza.bmp")
pizza = games.Sprite(image = pizza_image, x = 320, y = 240)
games.screen.add(pizza)

games.screen.mainloop()

Załadowanie obrazu potrzebnego


do utworzenia obiektu klasy Sprite
Najpierw ładuję obrazek pizzy do pamięci operacyjnej, aby utworzyć obiekt obrazu:
pizza_image = games.load_image("pizza.bmp")

Zwróć uwagę na jedną małą różnicę w stosunku do sposobu, w jaki załadowałem


obraz tła. Tym razem w wywołaniu funkcji nie uwzględniłem nadania wartości
parametrowi transparent. Jego wartością domyślną jest True, więc obraz został
załadowany z włączoną przezroczystością.
Jeśli obraz został załadowany z włączoną przezroczystością, jest wyświetlany na
ekranie graficznym w taki sposób, że przez jego przezroczyste części widać obraz tła.
Jest to wspaniała rzecz w przypadku duszków o nieregularnym kształcie, które nie są
idealnymi prostokątami, oraz w przypadku duszków z „dziurami” w środku, takich jak,
powiedzmy, duszek sera szwajcarskiego.
334 Rozdział 11. Grafika. Gra Pizza Panic

Te części obrazu, które są przezroczyste, są zdefiniowane przez swój kolor. Jeśli obraz
zostaje załadowany z włączoną przezroczystością, kolorem przezroczystym staje się kolor
punktu położonego w lewym górnym rogu obrazu. Przez wszystkie części obrazu, które
mają ten przezroczysty kolor, będzie przezierać tło ekranu. Na rysunku 11.7 pokazuję
duszka sera szwajcarskiego na jednolitym białym tle umożliwiającym wykorzystanie
przezroczystości.

Rysunek 11.7. Serowy duszek przedstawiony na tle jednolitego koloru umożliwiający


wykorzystanie przezroczystości

Jeśli załaduję ten obraz sera szwajcarskiego z włączoną przezroczystością, każdy jego
fragment, który ma czysto biały kolor (kolor wzięty z piksela znajdującego się w lewym
górnym rogu ekranu), stanie się przezroczysty, kiedy duszek zostanie wyświetlony na ekranie
graficznym. Przez te przezroczyste części duszka będzie widoczny obraz tła. Na rysunku
11.8 pokazuję, jak obraz wygląda po załadowaniu z włączoną i wyłączoną przezroczystością.

Rysunek 11.8. Obrazek z lewej strony został załadowany z włączoną przezroczystością. Po


prawej stronie znajduje się ten sam obrazek, lecz załadowany z wyłączoną przezroczystością
Wyświetlanie duszka 335

Przyjmij jako ogólną regułę: musisz utworzyć swój obraz duszka na tle jednolitego
koloru, który nie jest używany w żadnej innej części obrazu.

Pułapka
Upewnij się, że Twój obraz duszka też nie zawiera koloru, którego używasz do
uzyskania przezroczystości. W przeciwnym razie te części duszka staną się również
przezroczyste, co sprawi, że Twój duszek będzie wyglądał tak, jakby zawierał
małe dziurki lub pęknięcia, ponieważ będzie prześwitywał przez nie obraz tła.

Utworzenie duszka
Następnie tworzę duszka pizzy:
pizza = games.Sprite(image = pizza_image, x = 320, y = 240)

Zostaje utworzony nowy obiekt klasy Sprite z obrazem pizzy i współrzędnymi x i y


o wartościach (320, 240), które ustawiają obraz w samym środku ekranu. Następnie
nowy obraz zostaje przypisany do zmiennej pizza. Gdy tworzysz obiekt klasy Sprite,
powinieneś przekazać do jej konstruktora przynajmniej obraz, współrzędną x oraz
współrzędną y.

Dodanie obiektu klasy Sprite do ekranu


Po utworzeniu duszka musisz dodać go do ekranu, aby mógł być widoczny
i aktualizowany, i dokładnie to robię w kolejnym wierszu kodu:
games.screen.add(pizza)

Metoda add() po prostu dodaje duszka do ekranu graficznego.

Sztuczka
Aby tworzyć grafikę do swoich gier, nie musisz być artystą. Jak możesz zauważyć
w tym rozdziale, nadrabiam mój całkowity brak artystycznych zdolności elementem
nowoczesnej techniki — moim aparatem cyfrowym. Jeśli masz dostęp do aparatu
cyfrowego, możesz tworzyć wspaniałe obrazy do swoich projektów. Oto jak
w rzeczywistości tworzyłem grafikę do gry Pizza Panic. Ceglany mur to tył domu
mojego przyjaciela. Jeśli chodzi o pizzę, to pewnego wieczoru zamówiłem jej
dostawę. A szefem kuchni jest mój bardzo dzielny kolega, Dave.
Chociaż jest to wspaniała technika, ważną rzeczą do zapamiętania jest fakt,
że kiedy zrobisz zdjęcie osoby lub obiektu, niekoniecznie zostajesz właścicielem
obrazu — oczywiście, pewne rzeczy są chronione znakiem firmowym lub prawem
autorskim. Wykorzystanie aparatu cyfrowego jest jednak wspaniałym sposobem
uzyskiwania obrazów o ogólnej treści i nadawania programom wyjątkowego,
fotorealistycznego stylu.

Tabela 11.5 zawiera listę przydatnych właściwości klasy Sprite, a tabela 11.6 — listę
użytecznych metod tej klasy.
336 Rozdział 11. Grafika. Gra Pizza Panic

Tabela 11.5. Przydatne właściwości klasy Sprite


Właściwość Opis
angle Orientacja w stopniach.
x Współrzędna x.
y Współrzędna y.
dx Prędkość zmiany współrzędnej x.
dy Prędkość zmiany współrzędnej y.
left Współrzędna x lewej krawędzi duszka.
right Współrzędna x prawej krawędzi duszka.
top Współrzędna y górnej krawędzi duszka.
bottom Współrzędna y dolnej krawędzi duszka.
image Obiekt obrazu duszka.
overlapping_sprites Lista innych obiektów, które zachodzą na duszka.
is_collideable Ustala, czy duszek podlega kolizjom, czy nie. Wartość True
oznacza, że duszek wystąpi w kolizjach. Wartość False oznacza,
że duszek nie pojawi się w kolizjach.

Tabela 11.6. Przydatne metody klasy Sprite

Metoda Opis
update() Aktualizuje duszka. Wywoływana automatycznie w każdym
cyklu pętli mainloop().
destroy() Usuwa duszka z ekranu.

Wyświetlanie tekstu
Czy chcesz pokazać liczby przy prezentacji wyników sprzedaży, czy też liczbę unicestwionych
kosmitów, są sytuacje, w których chciałbyś wyświetlić tekst na ekranie graficznym.
Moduł games zawiera klasę, która to właśnie umożliwia, o stosownej nazwie Text.

Prezentacja programu Wysoki wynik


Wyświetlenie tekstu na ekranie graficznym sprowadza się do utworzenia obiektu klasy
Text. Program Wysoki wynik jest modyfikacją programu Obraz tła. Wyświetlam wynik
gry w prawym górnym rogu ekranu, tak jak w wielu klasycznych grach arkadowych.
Efekt uruchomienia programu został pokazany na rysunku 11.9.
Wyświetlanie tekstu 337

Rysunek 11.9. Imponująco wysoki wynik został wyświetlony


po konkretyzacji obiektu klasy Text

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to wysoki_wynik.py.
# Wysoki wynik
# Demonstruje wyświetlanie tekstu na ekranie graficznym

from livewires import games, color

games.init(screen_width = 640, screen_height = 480, fps = 50)

wall_image = games.load_image("sciana.jpg", transparent = False)


games.screen.background = wall_image

score = games.Text(value = 1756521,


size = 60,
color = color.black,
x = 550,
y = 30)
games.screen.add(score)

games.screen.mainloop()
338 Rozdział 11. Grafika. Gra Pizza Panic

Import modułu color


Pakiet livewires zawiera jeszcze jeden moduł, color, w którym zdefiniowano zbiór
stałych reprezentujących różne kolory. Te kolory mogą zostać użyte w pewnych obiektach
graficznych, w tym w dowolnych obiektach klasy Text i Message. Kompletną listę
predefiniowanych kolorów znajdziesz w dokumentacji pakietu livewires, w dodatku B.
Aby móc dokonać wyboru z grupy dostępnych kolorów, importuję moduł color,
zastępując instrukcję import znajdującą się na początku programu wierszem kodu:
from livewires import games, color

Obydwa moduły, color i game, są ładowane z pakietu livewires.

Utworzenie obiektu klasy Text


Obiekt klasy Text reprezentuje tekst na ekranie graficznym. Tuż przed wywołaniem
metody mainloop() tworzę obiekt klasy Text i przypisuję go do zmiennej score:
score = games.Text(value = 1756521,
size = 60,
color = color.black,
x = 550,
y = 30)

Do konstruktora obiektu klasy Text powinno się przekazać co najmniej wartość,


która ma zostać wyświetlona w postaci tekstu, rozmiar czcionki, kolor, współrzędną x
oraz współrzędną y.
Jako wartość parametru value przekazuję liczbę całkowitą 1756521, co ma spowodować
wyświetlenie ciągu tworzących ją znaków. (Obiekt klasy Text zostanie wyświetlony jako
łańcuch znaków reprezentujący dowolną wartość, jaka zostanie przekazana do value).
Parametrowi size, który reprezentuje wysokość tekstu w pikselach, nadaję wartość 60,
aby czcionka była ładna i duża — pasująca do wyniku. Parametrowi color nadaję wartość
stałej color.black z modułu color, aby sprawić, że tekst będzie — zgadłeś — czarny.
Nadaję parametrowi x wartość 550, a parametrowi y wartość 30, umieszczając w ten
sposób środek obiektu w punkcie o współrzędnych (550, 30). Oznacza to umiejscowienie
tekstu w prawym górnym rogu okna graficznego.

Dodanie obiektu klasy Text do ekranu


W kolejnym wierszu kodu dodaję nowy obiekt do ekranu, aby został wyświetlony:
games.screen.add(score)

Natychmiast po wywołaniu metody mainloop() okno graficzne zostanie wyświetlone


wraz z obiektem score.
Ponieważ klasa Text jest podklasą klasy Sprite, dziedziczy wszystkie jej właściwości,
atrybuty i metody. Tabela 11.7 zawiera dwie dodatkowe właściwości klasy Text w niej
zdefiniowane.
Wyświetlanie komunikatu 339

Tabela 11.7. Dodatkowe właściwości klasy Text


Właściwość Opis
value Wartość, która ma być wyświetlona w postaci tekstu.
color Kolor tekstu.

Wyświetlanie komunikatu
Mógłbyś chcieć wyświetlać jakiś tekst na ekranie jedynie przez krótki czas. Mógłbyś
zechcieć pokazać komunikat o treści „Wszystkie rekordy zostały zaktualizowane”
lub „Siódma fala ataku została zakończona!”. Klasa Message modułu games nadaje się
doskonale do tworzenia tego rodzaju tymczasowych komunikatów.

Prezentacja programu Wygrałeś


Program Wygrałeś to zmodyfikowana wersja programu Obraz tła. Konkretyzuję obiekt
klasy Message tuż przed wywołaniem metody mainloop(), aby wyświetlić tekst „Wygrałeś!”
dużymi, czerwonymi literami. Komunikat jest wyświetlany przez mniej więcej pięć
sekund, a następnie program kończy działanie. Program ten został zilustrowany
na rysunku 11.10.

Rysunek 11.10. Och, ten smak zwycięstwa


340 Rozdział 11. Grafika. Gra Pizza Panic

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to wygrales.py.
# Wygrałeś
# Demonstruje wyświetlanie komunikatu

from livewires import games, color

games.init(screen_width = 640, screen_height = 480, fps = 50)

wall_image = games.load_image("sciana.jpg", transparent = False)


games.screen.background = wall_image

won_message = games.Message(value = "Wygrałeś!",


size = 100,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 250,
after_death = games.screen.quit)
games.screen.add(won_message)

games.screen.mainloop()

Import modułu color


Obiekty klasy Message podobnie jak obiekty klasy Text mają właściwość color. Aby móc
dokonać wyboru z grupy dostępnych kolorów, importuję moduł color, zastępując
instrukcję import znajdującą się na początku programu wierszem kodu:
from livewires import games, color

Utworzenie obiektu klasy Message


Komunikaty są tworzone na podstawie klasy Message modułu games. Komunikat jest
specjalnym rodzajem obiektu klasy Text, który dokonuje samozniszczenia po ustalonym
czasie. Komunikat może też określać metodę lub funkcję, która ma zostać wykonana po
samolikwidacji obiektu.
Konstruktor klasy Message przyjmuje wszystkie wartości, z którymi się spotkałeś przy
tworzeniu obiektów klasy Text, oraz dwie dodatkowe: lifetime i after_death. Parametr
lifetime przyjmuje wartość w postaci liczby całkowitej, która określa czas wyświetlania
komunikatu mierzony w cyklach pętli mainloop(). Poprzez parametr after_death można
przekazać funkcję lub metodę, która ma zostać wykonana po samolikwidacji obiektu
klasy Message. Wartością domyślną parametru after_death jest None, więc przekazanie
wartości parametru nie jest wymagane.
Wyświetlanie komunikatu 341

Tworzę ten obiekt klasy Message tuż przed wywołaniem metody mainloop():
won_message = games.Message(value = "Wygrałeś!",
size = 100,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 250,
after_death = games.screen.quit)

Zostaje w ten sposób utworzony komunikat „Wygrałeś!” wyświetlany dużymi,


czerwonymi literami w środku ekranu przez mniej więcej pięć sekund, po czym program
kończy działanie.
Powyższy kod konkretyzuje nowy obiekt klasy Message z atrybutem lifetime
o wartości 250. To oznacza, że obiekt będzie istniał przez jakieś pięć sekund, gdyż pętla
mainloop() wykonuje się z prędkością 50 ramek na sekundę. Po pięciu sekundach zostaje
wywołana metoda games.screen.quit(), ponieważ właśnie jej nazwę przekazałem
poprzez parametr after_death. W tym momencie ekran i wszystkie związane z nim
obiekty są niszczone i program kończy pracę.

Wskazówka
Pamiętaj, aby jako parametr after_death przekazywać samą nazwę funkcji
lub metody, która ma zostać wywołana po zniknięciu obiektu klasy Message.
Nie dołączaj do nazwy nawiasów.

Wykorzystanie danych
o szerokości i wysokości ekranu
Obiekt screen ma właściwość width, która reprezentuje szerokość ekranu graficznego,
oraz właściwość height reprezentującą jego wysokość. Czasem klarowniejsze jest użycie
tych właściwości zamiast liczb całkowitych w formie literału w celu określenia miejsca
na ekranie.
Wykorzystuję te właściwości, kiedy przekazuję wartości dotyczące położenia
nowego obiektu klasy Message za pomocą przypisań x = games.screen.width/2 i y =
games.screen.height/2. Ustawiając wartość współrzędnej x jako połowę szerokości
ekranu, a wartość współrzędnej y jako połowę jego wysokości, umieszczam obiekt
dokładnie w środku ekranu. Można użyć tej techniki do umieszczenia obiektu w środku
ekranu graficznego niezależnie od jego faktycznej szerokości i wysokości.

Dodanie obiektu klasy Message do ekranu


W kolejnym wierszu kodu dodaję nowy obiekt do ekranu, aby mógł zostać wyświetlony:
games.screen.add(won_message)
342 Rozdział 11. Grafika. Gra Pizza Panic

Klasa Message jest podklasą klasy Text. Oznacza to, że klasa Message dziedziczy
wszystkie właściwości, atrybuty i metody klasy Text. W tabeli 11.8 zostały wymienione
dwa dodatkowe atrybuty klasy Message.

Tabela 11.8. Dodatkowe atrybuty klasy Message

Atrybuty Opis
lifetime Liczba cykli pętli mainloop(), zanim obiekt ulegnie samolikwidacji.
Wartość 0 oznacza, że obiekt ma nie ulec samozniszczeniu.
Jest to wartość domyślna.
after_death Funkcja lub metoda, jaka ma zostać uruchomiona po samolikwidacji
obiektu. Wartością domyślną jest None.

Przemieszczanie duszków
Ruch obrazów stanowi istotę większości gier — dotyczy to zresztą większości form
rozrywki. W przypadku duszków przejście od statyczności do ruchu jest proste. Obiekty klasy
Sprite mają właściwości, które umożliwiają im poruszanie się po ekranie graficznym.

Prezentacja programu Pizza w ruchu


Ten nowy program jest modyfikacją programu Duszek pizzy. W tym programie pizza
porusza się w dół i w prawo. Muszę tylko zmienić kilka wierszy kodu, aby sprawić, by pizza się
poruszała. W tym tkwi potęga duszków. Program został przedstawiony na rysunku 11.11.

Rysunek 11.11. Pizza porusza się w prawo, w dół, w kierunku wskazywanym przez strzałkę
Przemieszczanie duszków 343

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to pizza_w_ruchu.py.
# Pizza w ruchu
# Demonstruje określanie parametrów prędkości ruchu duszka

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

wall_image = games.load_image("sciana.jpg", transparent = False)


games.screen.background = wall_image

pizza_image = games.load_image("pizza.bmp")
the_pizza = games.Sprite(image = pizza_image,
x = games.screen.width/2,
y = games.screen.height/2,
dx = 1,
dy = 1)
games.screen.add(the_pizza)

games.screen.mainloop()

Ustawienie wartości prędkości ruchu pizzy


Wystarczy, że zmodyfikuję kod, który tworzy nowego duszka, przez dostarczenie
dodatkowych wartości parametrów dx i dy do konstruktora:
the_pizza = games.Sprite(image = pizza_image,
x = games.screen.width/2,
y = games.screen.height/2,
dx = 1,
dy = 1)

Każdy obiekt oparty na klasie Sprite ma właściwości dx i dy określające prędkość


obiektu odpowiednio w kierunku osi x i osi y. (Można tu dodać, że litera „d” reprezentuję
symbol „delta” oznaczający zmianę). Tak więc dx to zmiana współrzędnej x obiektu, a dy
to zmiana współrzędnej y obiektu po każdej kolejnej aktualizacji ekranu (obiektu screen)
przez mainloop(). Dodatnia wartość dx powoduje przesunięcie duszka w prawo, a wartość
ujemna przesuwa go w lewo. Dodatnia wartość dy powoduje przesunięcie duszka w dół,
a wartość ujemna — w górę
Kiedy wrócimy do programu Duszek pizzy, okaże się, że nie przekazałem żadnych
wartości do parametrów dx i dy. Mimo że duszek w tamtym programie zawierał
właściwości dx i dy, obie miały wartość domyślną równą 0.
Ponieważ przekazuję 1 do parametru dx oraz 1 do parametru dy, za każdym razem,
gdy okno jest aktualizowane przez mainloop(), zarówno współrzędna x pizzy, jak i jej
współrzędna y są zwiększane o 1, co powoduje przesunięcie pizzy w prawo w dół.
344 Rozdział 11. Grafika. Gra Pizza Panic

Radzenie sobie z granicami ekranu


Obserwując działanie programu Pizza w ruchu odpowiednio długo, możesz zauważyć,
że kiedy pizza dociera do granicy ekranu, nie zaprzestaje swojego ruchu. Tak naprawdę
znika z pola widzenia.
Zawsze, gdy wprowadzasz duszka w ruch, powinieneś stworzyć mechanizm
obsługujący granice okna graficznego. Masz kilka możliwości wyboru. Ruch duszka
mógłby być po prostu zatrzymany po osiągnięciu brzegu ekranu. Duszek mógłby stracić
życie, powiedzmy, na skutek gwałtownej eksplozji. Mógłby odbić się od brzegu jak
olbrzymia gumowa piłka. Mógłby nawet przewinąć się wokół ekranu, znikając na jednym
jego brzegu i pojawiając się ponownie po przeciwnej stronie. Co wydaje się najsensowniejsze
w przypadku pizzy? Oczywiście odbicie się.

Program Sprężysta pizza


Mówiąc, że duszek „odbija się” od brzegów okna graficznego, mam na myśli to, że kiedy
dotrze do granicy ekranu, składowa jego prędkości, która powodowała ruch w kierunku
tej granicy, powinna zmienić swoją wartość na przeciwną. Więc jeśli sprężysta pizza
dotrze do górnego lub dolnego brzegu ekranu, powinna zmienić wartość swojej właściwości
dy na przeciwną. Kiedy dotrze do jednego z boków ekranu, na przeciwną powinna
zmienić się wartość właściwości dx obiektu. Działanie programu Sprężysta pizza zostało
zilustrowane na rysunku 11.12.

Rysunek 11.12. Chociaż nie możesz tego stwierdzić na podstawie samego zrzutu ekranu,
pizza odbija się od brzegów ekranu i porusza się
wzdłuż ścieżki wskazanej przez linię ze strzałką
Radzenie sobie z granicami ekranu 345

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to sprezysta_pizza.py.

Rozpoczęcie programu
Zaczynam tak jak w przypadku dowolnego innego programu graficznego:
# Sprężysta pizza
# Demonstruje postępowanie po osiągnięciu granic ekranu

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

Jak poprzednio, w powyższych wierszach kodu uzyskuję dostęp do modułu games


oraz tworzę ekran graficzny.

Utworzenie nowej klasy pochodnej


na bazie klasy Sprite
Po raz pierwszy chcę, aby duszek robił coś, do czego nie został zaprogramowany —
odbijał się od brzegów ekranu. Więc muszę na bazie klasy Sprite utworzyć nową klasę.
Ponieważ potrzebuję odbijającej się pizzy, nadaję nowej klasie nazwę Pizza:
class Pizza(games.Sprite):
""" Sprężysta pizza. """

Przesłonięcie metody update()


Aby przekształcić ruchomą pizzę w taką, która odbija się od brzegów ekranu, muszę
dodać tylko jedną metodę. Za każdym razem, gdy okno graficzne jest aktualizowane
przez mainloop(), zachodzą następujące dwa zdarzenia:
 położenie każdego duszka zostaje zaktualizowane na podstawie jego
właściwości dx i dy,
 wywoływana jest metoda update() każdego duszka.

Każdy obiekt klasy Sprite ma metodę update(); domyślnie nie robi ona niczego.
Więc poprzez przesłonięcie tej metody w klasie Pizza uzyskuję doskonałe miejsce
do wstawienia kodu, który obsłuży sprawdzanie granic ekranu.
def update(self):
""" Po osiągnięciu brzegu ekranu zmień wartość składowej prędkości
na przeciwną. """
if self.right > games.screen.width or self.left < 0:
self.dx = -self.dx

if self.bottom > games.screen.height or self.top < 0:


self.dy = -self.dy
346 Rozdział 11. Grafika. Gra Pizza Panic

W metodzie update() sprawdzam, czy duszek nie wychodzi w żadnym kierunku poza
granice ekranu. Jeżeli tak się dzieje, zmieniam wartość odpowiedzialnej za to składowej
prędkości na przeciwną.
Jeżeli właściwość right obiektu, która reprezentuje współrzędną x jego prawego brzegu,
jest większa niż games.screen.width, pizza ma właśnie przekroczyć prawą krawędź ekranu
i zapaść się w nicość. Jeśli zaś właściwość left obiektu, która reprezentuje współrzędną x
jego lewego brzegu, jest mniejsza niż 0, pizza opuszcza ekran z jego lewej strony.
W każdym przypadku po prostu zmieniam na przeciwną wartość dx poziomej prędkości
pizzy, aby spowodować jej „odbicie się” od granicy ekranu.
Jeśli właściwość bottom obiektu, która reprezentuje współrzędną y jego dolnego
brzegu, jest większa niż games.screen.height, pizza znajduje się na granicy przekroczenia
dolnej krawędzi ekranu i zniknięcia. Jeśli zaś właściwość top obiektu, która reprezentuje
współrzędną y jego lewego górnego brzegu, jest mniejsza niż 0, pizza opuszcza ekran
po przekroczeniu jego górnej krawędzi. W każdym przypadku po prostu zmieniam na
przeciwną wartość dy pionowej składowej prędkości pizzy, aby spowodować jej „odbicie się”
od granicy ekranu.

Dokończenie programu
Ponieważ definiuję w programie klasę, pomyślałem, że zorganizuję pozostałą część kodu
w funkcję:
def main():
wall_image = games.load_image("sciana.jpg", transparent = False)
games.screen.background = wall_image

pizza_image = games.load_image("pizza.bmp")
the_pizza = Pizza(image = pizza_image,
x = games.screen.width/2,
y = games.screen.height/2,
dx = 1,
dy = 1)
games.screen.add(the_pizza)

games.screen.mainloop()

# wystartuj!
main()

Gros tego kodu już przedtem widziałeś. Jedyna ważna różnica polega na tym,
że utworzyłem obiekt na bazie mojej nowej klasy Pizza, zamiast posłużyć się klasą
Sprite. Dzięki temu metoda update() obiektu sprawdza przekroczenie granic ekranu
i odwraca kierunek prędkości, aby w razie potrzeby zapewnić odbicie się pizzy, która
ma być sprężysta!
Obsługa danych wejściowych z myszy 347

Obsługa danych wejściowych z myszy


Chociaż widziałeś dużo z tego, co ma do zaoferowania pakiet livewires, nie poznałeś
jeszcze głównego składnika interaktywności — obsługi danych wejściowych użytkownika.
Jednym z najczęściej występujących sposobów uzyskiwania danych wejściowych
od użytkownika jest wykorzystanie w tym celu myszy. Pakiet livewires oferuje obiekt,
który pozwala to zrealizować.

Prezentacja programu Patelnia w ruchu


Obiekt mouse z modułu games daje Ci dostęp do myszy. Obiekt ma właściwości,
które sprawiają, że odczytywanie położenia myszy na ekranie graficznym staje się bułką
z masłem. Wykorzystując te właściwości, tworzę program Patelnia w ruchu, umożliwiający
użytkownikowi przeciąganie duszka patelni w poprzek ekranu poprzez wykonywanie
ruchów myszą. Efekt działania tego programu został pokazany na rysunku 11.13.

Rysunek 11.13. Duszek patelni wędruje po całym ekranie graficznym,


podążając za ruchem myszy

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to patelnia_w_ruchu.py.
348 Rozdział 11. Grafika. Gra Pizza Panic

Rozpoczęcie programu
Poniższy kod powinien wyglądać wybitnie znajomo:
# Patelnia w ruchu
# Demonstruje obsługę danych wejściowych z myszy

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

Tak jak przedtem, importuję moduł games i inicjalizuję ekran graficzny. Funkcja
init() tworzy również obiekt mouse, którego użyję do odczytywania pozycji myszy.

Odczytywanie współrzędnych x i y myszy


Następnie tworzę klasę Pan reprezentującą duszka patelni:
class Pan(games.Sprite):
"""" Patelnia sterowana myszą. """
def update(self):
""" Przejdź do współrzędnych myszy. """
self.x = games.mouse.x
self.y = games.mouse.y

Podobnie jak obiekt klasy Sprite, obiekt mouse posiada właściwość x, reprezentującą
jego współrzędną x, oraz właściwość y, reprezentującą jego współrzędną y. Za ich pomocą
mogę odczytać aktualne położenie myszy na ekranie graficznym.
W metodzie update() przypisuję właściwości x obiektu klasy Pan wartość
właściwości x obiektu mouse. To powoduje przesunięcie patelni do aktualnego
położenia wskaźnika myszy.
Następnie piszę funkcję main() zawierającą typ kodu, z jakim już wcześniej się
spotkałeś, która ustawia obraz tła i tworzy obiekty duszków:
def main():
wall_image = games.load_image("sciana.jpg", transparent = False)
games.screen.background = wall_image

pan_image = games.load_image("patelnia.bmp")
the_pan = Pan(image = pan_image,
x = games.mouse.x,
y = games.mouse.y)
games.screen.add(the_pan)

Dzięki przekazaniu wartości games.mouse.x do parametru x oraz wartości


games.mouse.y do parametru y początkowe położenie obiektu klasy Pan odpowiada
współrzędnym myszy.
Obsługa danych wejściowych z myszy 349

Ustawienie widoczności wskaźnika myszy


Następnie wykorzystuję w funkcji main()właściwość is_visible obiektu mouse
do ustawienia widoczności wskaźnika myszy.
games.mouse.is_visible = False

Ustawienie tej właściwości na True oznacza, że wskaźnik myszy będzie widoczny,


natomiast ustawienie jej na False oznacza, że wskaźnik myszy będzie niewidoczny.
Ponieważ nie chcę widzieć wskaźnika na wierzchu obrazka patelni, nadaję tej właściwości
wartość False.

Przechwytywanie sygnałów wejściowych


przez okno graficzne
Następnie w funkcji main() wykorzystuję właściwość event_grab obiektu screen,
aby wszystkie sygnały wejściowe były przechwytywane przez ekran graficzny:
games.screen.even_grab = True

Ustawienie tej właściwości na True oznacza, że obsługa wszystkich sygnałów wejściowych


zostanie skoncentrowana w ekranie graficznym. Korzyść z tego polega na tym, że mysz
wraz z wszystkimi zdarzeniami nie opuści okna graficznego. Ustawienie tej właściwości
na False oznacza, że kierowanie wszystkich sygnałów wejściowych do ekranu graficznego
nie ma zastosowania i wskaźnik myszy może opuścić okno graficzne.

Wskazówka
Jeśli przechwytujesz wszystkie sygnały wejściowe, kierując je do ekranu
graficznego, nie będziesz mógł zamknąć okna graficznego za pomocą myszy.
Zawsze jednak możesz zamknąć okno przez naciśnięcie klawisza Esc.

Dokończenie programu
Wreszcie kończę funkcję main() tak jak przedtem i wywołuję metodę mainloop(),
aby zapewnić aktualizowanie całej zawartości ekranu.
games.screen.mainloop()

# wystartuj!
main()

Podsumowanie kilku użytecznych właściwości obiektu mouse znajdziesz


w tabeli 11.9.
350 Rozdział 11. Grafika. Gra Pizza Panic

Tabela 11.9. Przydatne właściwości obiektu mouse


Właściwość Opis
x Współrzędna x wskaźnika myszy.
y Współrzędna y wskaźnika myszy.
is_visible Wartość typu Boolean służąca do ustawienia widoczności wskaźnika
myszy. True oznacza widoczność wskaźnika, False — brak widoczności.
Wartością domyślną jest True.

Wykrywanie kolizji
W większości gier, gdy zderzają się dwie rzeczy, efekt tego jest wyraźny. Może to być
tak prosty przypadek jak zderzenie się postaci dwuwymiarowej z granicą, której nie może
przekroczyć, lub tak spektakularny jak rozgrywająca się w trzech wymiarach scena,
w której asteroida przebija kadłub ogromnego statku bazy. Tak czy owak istnieje
potrzeba wykrycia momentu kolizji obiektów.

Prezentacja programu Nieuchwytna pizza


Program Nieuchwytna pizza stanowi rozszerzenie programu Patelnia w ruchu.
W programie Nieuchwytna pizza użytkownik steruje patelnią za pomocą myszy, tak
jak w programie Patelnia w ruchu. Tym razem jednak na ekranie pojawia się duszek
pizzy. Użytkownik może przesuwać patelnię w kierunku pizzy, ale jeśli tylko uda mu
się jej dosięgnąć, wyślizgująca się pizza przenosi się w nowe, przypadkowo wybrane
miejsce ekranu. Na rysunkach 11.14 i 11.15 przedstawiłem program w działaniu.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 11.; nazwa pliku
to nieuchwytna_pizza.py.

Rozpoczęcie programu
Początkowy fragment kodu został wzięty z programu Patelnia w ruchu z jednym małym
dodatkiem:
# Nieuchwytna pizza
# Demonstruje wykrywanie kolizji duszków

from livewires import games


import random

games.init(screen_width = 640, screen_height = 480, fps = 50)

Jedyną nową czynnością, jaką wykonuję, jest import naszego starego znajomego —
modułu random. Pozwoli mi to na wygenerowanie nowego, przypadkowego położenia
duszka pizzy po kolizji.
Wykrywanie kolizji 351

Rysunek 11.14. Gracz prawie dosięga pizzy

Rysunek 11.15. Nieuchwytnej pizzy znowu udaje się wymknąć


352 Rozdział 11. Grafika. Gra Pizza Panic

Wykrywanie kolizji
Tworzę nową klasę Pan poprzez dodanie niewielkiej ilości kodu służącego do wykrywania
kolizji:
class Pan(games.Sprite):
"""" Patelnia sterowana za pomocą myszy. """
def update(self):
""" Przesuń do pozycji myszy. """
self.x = games.mouse.x
self.y = games.mouse.y
self.check_collide()

def check_collide(self):
""" Sprawdź, czy nie doszło do kolizji z pizzą. """
for pizza in self.overlapping_sprites:
pizza.handle_collide()

W ostatnim wierszu metody update() wywołuję metodę check_collide() klasy Pan.


Metoda check_collide() przetwarza w pętli listę wszystkich obiektów zachodzących
na obiekt klasy Pan, dostępną poprzez właściwość overlapping_sprites tego ostatniego
obiektu. W każdym obiekcie, którego obraz zachodzi na obraz patelni, wywoływana
jest jego własna metoda handle_collide(). Zasadniczo obiekt patelni zleca każdemu
z obiektów, które na niego zachodzą, obsługę wynikającej stąd kolizji.

Obsługa kolizji
Następnie tworzę nową klasę o nazwie Pizza:
class Pizza(games.Sprite):
"""" Nieuchwytna pizza. """
def handle_collide(self):
""" Przemieść się w przypadkowe miejsce ekranu. """
self.x = random.randrange(games.screen.width)
self.y = random.randrange(games.screen.height)

Piszę tylko jedną metodę, handle_collide(), która generuje losowe współrzędne


punktu na ekranie, i przenosi obiekt klasy Pizza w to nowe miejsce.

Dokończenie programu
A oto funkcja main():
def main():
wall_image = games.load_image("sciana.jpg", transparent = False)
games.screen.background = wall_image

pizza_image = games.load_image("pizza.bmp")
pizza_x = random.randrange(games.screen.width)
pizza_y = random.randrange(games.screen.height)
the_pizza = Pizza(image = pizza_image, x = pizza_x, y = pizza_y)
Powrót do gry Pizza Panic 353

games.screen.add(the_pizza)

pan_image = games.load_image("patelnia.bmp")
the_pan = Pan(image = pan_image,
x = games.mouse.x,
y = games.mouse.y)
games.screen.add(the_pan)

games.mouse.is_visible = False

games.screen.event_grab = True

games.screen.mainloop()

# wystartuj!
main()

Jak zawsze ustawiam obraz tła. Następnie tworzę dwa obiekty: obiekt klasy Pizza
oraz obiekt klasy Pan. Generuję losową parę współrzędnych ekranowych dla pizzy
i umieszczam patelnię w miejscu wyznaczonym przez współrzędne myszy. Ustawiam
wskaźnik myszy tak, aby był niewidoczny, i konfiguruję przechwytywanie wszystkich
sygnałów wejściowych przez okno gry. Potem wywołuję metodę mainloop(). W końcu
wszystko uruchamiam poprzez wywołanie funkcji main().

Powrót do gry Pizza Panic


Teraz, kiedy masz już wyobrażenie o tym, czego może dokonać multimedialny pakiet
livewires, nadszedł czas na stworzenie gry Pizza Panic przedstawionej na początku rozdziału.
Duża część kodu gry może być bezpośrednio przeniesiona z programów przykładowych.
Wprowadzę również jednak kilka nowych koncepcji. Kod tego programu możesz znaleźć
na stronie internetowej tej książki (http://www.helion.pl/ksiazki/pytdk3.htm), w folderze
rozdziału 11.; nazwa pliku to pizza_panic.py.

Rozpoczęcie programu
Tak jak we wszystkich programach w tym rozdziale, zaczynam od importu modułów
oraz inicjalizacji ekranu graficznego:
# Pizza Panic
# Gracz musi złapać lecące w dół pizze, zanim spadną na ziemię

from livewires import games, color


import random

games.init(screen_width = 640, screen_height = 480, fps = 50)

Aby móc tworzyć jakąś grafikę, muszę zaimportować moduł games, podczas gdy
moduł color daje mi dostęp do palety predefiniowanych kolorów. Importuję moduł
354 Rozdział 11. Grafika. Gra Pizza Panic

random, aby oszalały kucharz bardziej przypominał w swoim dokonywaniu wyborów


żywą osobę. W końcu wywołuję funkcję init() modułu games w celu inicjalizacji ekranu
graficznego i uzyskania dostępu do obiektu mouse modułu games.

Klasa Pan
Klasa Pan to projekt duszka patelni, którym gracz steruje za pomocą myszy. Patelnia
może się jednak poruszać tylko w prawo lub w lewo. Omówię całą klasę po kawałku.

Załadowanie obrazu patelni


Na początku tworzenia tej klasy robię coś trochę odmiennego — ładuję obraz duszka
i przypisuję go do zmiennej klasy o nazwie image. Robię tak, ponieważ gra Pizza Panic
zawiera kilka klas, a załadowanie obrazu w definicji odpowiadającej mu klasy jest
bardziej przejrzyste niż załadowanie wszystkich obrazów w funkcji main() programu.
class Pan(games.Sprite):
"""
Patelnia sterowana przez gracza służąca do łapania spadających pizz.
"""
image = games.load_image("patelnia.bmp")

Metoda __init__()
Następnie piszę kod konstruktora służącego do inicjalizacji nowego obiektu klasy Pan:
def __init__(self):
""" Initialize Pan object and create Text object for score. """
super(Pan, self).__init__(image = Pan.image,
x = games.mouse.x,
bottom = games.screen.height)

self.score = games.Text(value = 0, size = 25, color = color.black,


top = 5, right = games.screen.width - 10)
games.screen.add(self.score)

Używam funkcji super(), aby zapewnić sobie wywołanie metody __init__() klasy
Sprite. Następnie definiuję atrybut score — obiekt klasy Text reprezentujący wynik
gracza, którego wartością początkową jest 0. Oczywiście pamiętam, aby dodać nowy
obiekt klasy Text do ekranu, by mógł być wyświetlony.

Metoda update()
Ta metoda obsługuje ruch patelni gracza:
def update(self):
""" Zmień pozycję na wyznaczoną przez współrzędną x myszy. """
self.x = games.mouse.x

if self.left < 0:
self.left = 0
Powrót do gry Pizza Panic 355

if self.right > games.screen.width:


self.right = games.screen.width

self.check_catch()

Metoda przypisuje współrzędną x myszy do współrzędnej x obiektu klasy Pan,


umożliwiając graczowi przesuwanie patelni w lewo i w prawo za pomocą myszy.
Następnie wykorzystuję właściwość left obiektu, aby sprawdzić, czy współrzędna x
jego lewego brzegu jest mniejsza od 0, co oznaczałoby, że część patelni znajduje się poza
lewą krawędzią okna graficznego. Jeśli tak jest w istocie, ustawiam współrzędną x lewego
brzegu (właściwość left obiektu) na 0, aby patelnia została wyświetlona przy lewej
krawędzi okna.
Potem wykorzystuję właściwość right obiektu, aby sprawdzić, czy współrzędna x jego
prawego brzegu jest większa od wartości games.screen.width, co oznaczałoby, że część
patelni znajduje się poza prawą krawędzią okna graficznego. Jeśli tak się dzieje, przypisuję
współrzędnej x prawego brzegu (właściwości right obiektu) wartość games.screen.width,
aby patelnia została wyświetlona przy prawej krawędzi okna.
Na koniec wywołuję metodę check_catch() obiektu.

Metoda check_catch()
Metoda ta sprawdza, czy gracz złapał jakieś spadające pizze:
def check_catch(self):
""" Sprawdź, czy nie zostały złapane jakieś pizze. """
for pizza in self.overlapping_sprites:
self.score.value += 10
self.score.right = games.screen.width - 10
pizza.handle_caught()

Dla każdego obiektu, który zachodzi na patelnię, metoda zwiększa liczbę punktów
uzyskanych przez gracza o 10. Potem zapewnia, że prawy brzeg obiektu klasy Text
reprezentującego wynik jest zawsze oddalony o 10 pikseli od prawej krawędzi ekranu,
bez względu na to, ile cyfr ten wynik zawiera. Na koniec omawiana metoda wywołuje
metodę handle_caught() zachodzącego na patelnię duszka.

Klasa Pizza
Ta klasa reprezentuje spadające pizze, które gracz musi łapać:
class Pizza(games.Sprite):
"""
Pizza, która spada na ziemię.
"""
image = games.load_image("pizza.bmp")
speed = 1

Definiuję dwie zmienne klasy: image, reprezentującą obraz pizzy, oraz speed,
reprezentującą szybkość spadania pizzy. Ustawiam wartość zmiennej speed na 1,
356 Rozdział 11. Grafika. Gra Pizza Panic

aby pizze spadały w dość wolnym tempie. Obydwu zmiennych klasy używam
w konstruktorze klasy Pizza, o czym wkrótce się przekonasz.

Metoda __init__()
Ta metoda inicjalizuje nowy obiekt klasy Pizza:
def __init__(self, x, y = 90):
""" Inicjalizuj obiekt klasy Pizza. """
super(Pizza, self).__init__(image = Pizza.image,
x = x, y = y,
dy = Pizza.speed)

Wszystko, co robię w tej metodzie, to wywołanie konstruktora klasy nadrzędnej klasy


Pizza. Zwróć uwagę, że ustawiam wartość domyślną parametru y na 90, co lokuje każdą
nową pizzę dokładnie na wysokości klatki piersiowej kucharza.

Metoda update()
Ta metoda obsługuje sprawdzanie, czy obiekt nie dotarł do granicy ekranu:
def update(self):
""" Sprawdź, czy dolny brzeg pizzy dosięgnął dołu ekranu. """
if self.bottom > games.screen.height:
self.end_game()
self.destroy()

Cała praca tej metody polega na sprawdzeniu, czy pizza dotarła do dolnej krawędzi
ekranu. Jeśli okazuje się, że tak jest w istocie, omawiana metoda wywołuje metodę
end_game obiektu, a następnie obiekt sam się usuwa z ekranu.

Metoda handle_caught()
Warto przypomnieć, że ta metoda jest wywoływana przez obiekt klasy Pan wtedy,
gdy zderza się z nim obiekt klasy Pizza:
def handle_caught(self):
""" Destroy self if caught. """
self.destroy()

Kiedy pizza zderza się z patelnią, uważa się, że pizza została „złapana” i po prostu
przestaje istnieć. Więc obiekt klasy Pizza wywołuje swoją własną metodę destroy()
i pizza dosłownie znika.

Metoda end_game()
Ta metoda kończy grę. Jest wywoływana, kiedy pizza dociera do dolnej krawędzi ekranu.
def end_game(self):
""" Zakończ grę. """
end_message = games.Message(value = "Koniec gry",
size = 90,
color = color.red,
x = games.screen.width/2,
Powrót do gry Pizza Panic 357

y = games.screen.height/2,
lifetime = 5 * games.screen.fps,
after_death = games.screen.quit)
games.screen.add(end_message)

Powyższy kod tworzy obiekt klasy Message, który oznajmia koniec gry. Po mniej
więcej pięciu sekundach komunikat znika i okno graficzne się zamyka, kończąc grę.

Pułapka
Metoda end_game() jest wywoływana zawsze, gdy pizza dociera do dolnej krawędzi
ekranu. Ponieważ jednak komunikat „Koniec gry” jest wyświetlany przez mniej
więcej pięć sekund, istnieje możliwość, że inna pizza dotrze do dolnej krawędzi
ekranu, zanim okno graficzne zdąży się zamknąć, powodując w ten sposób
zwielokrotnienie komunikatów „Koniec gry”.
W rozdziale 12. zobaczysz, jak utworzyć obiekt reprezentujący samą grę, który
mógłby śledzić, czy gra się skończyła, czy też nie, i zapobiegać czemuś takiemu
jak wielokrotne tworzenie komunikatów „Koniec gry”.

Klasa Chef
Klasa Chef jest wykorzystywana do utworzenia szalonego szefa kuchni, który zrzuca pizze
z dachu restauracji.
class Chef(games.Sprite):
"""
Szef kuchni, który porusza się w lewo i w prawo, zrzucając pizze.
"""
image = games.load_image("kucharz.bmp")

Definiuję atrybut klasy, image, reprezentujący obraz kucharza.

Metoda __init__()
Oto kod konstruktora:
def __init__(self, y = 55, speed = 2, odds_change = 200):
""" Initialize the Chef object. """
super(Chef, self).__init__(image = Chef.image,
x = games.screen.width / 2,
y = y,
dx = speed)

self.odds_change = odds_change
self.time_til_drop = 0

Najpierw wywołuję konstruktora klasy nadrzędnej klasy Chef. Jako parametr image
przekazuję atrybut klasy Chef.image. Jako x przekazuję wartość, która ustawia kucharza
w samym środku ekranu. W przypadku parametru y wartość domyślna 55 sytuuje szefa
kuchni dokładnie na szczycie ceglanej ściany. Jako parametr dx zostaje przekazana wartość
speed, która ustala poziomą prędkość kucharza, gdy ten się przemieszcza wzdłuż dachu.
Wartość domyślna wynosi 2.
358 Rozdział 11. Grafika. Gra Pizza Panic

Metoda tworzy również dwa atrybuty obiektu: odds_change i time_til_drop. Atrybut


odds_change jest liczbą całkowitą, która reprezentuje prawdopodobieństwo, że kucharz
zmieni kierunek swojego ruchu. Jeśli na przykład wartość odds_change wynosi 200,
istnieje przy każdym ruchu kucharza 1 szansa na 200, że zmieni się jego kierunek.
Zobaczysz, jak to działa w metodzie update() tej klasy.
Atrybut time_til_drop jest liczbą całkowitą, która reprezentuje ilość czasu wyrażoną
w cyklach pętli mainloop(), jaka pozostaje do zrzucenia przez kucharza następnej pizzy.
Nadaję mu na początku wartość 0, co oznacza, że gdy tylko obiekt kucharza zostanie
powołany do życia, powinien od razu zrzucić pizzę. Jak funkcjonuje atrybut
time_til_drop, zobaczysz w metodzie check_drop().

Metoda update()
Ta metoda definiuje reguły, które określają, w jaki sposób kucharz decyduje
o przesuwaniu się w jedną lub w drugą stronę wzdłuż krawędzi dachu:
def update(self):
""" Ustal, czy kierunek ruchu musi zostać zmieniony na przeciwny. """
if self.left < 0 or self.right > games.screen.width:
self.dx = -self.dx
elif random.randrange(self.odds_change) == 0:
self.dx = -self.dx

self.check_drop()

Kucharz przesuwa się wzdłuż krawędzi dachu w jednym kierunku, dopóki albo
nie dotrze do brzegu ekranu, albo nie „zadecyduje” w sposób losowy o zmianie kierunku.
Na początku tej metody sprawdzam, czy szef kuchni nie przesunął się poza lewą lub prawą
krawędź okna graficznego. Jeśli tak się stało, kierunek ruchu kucharza zostaje odwrócony
za pomocą kodu self.dx = -self.dx. W przeciwnym wypadku kucharz ma jedną szansę
zmiany kierunku ruchu na taką liczbę możliwości, jaką ustala atrybut odd_change.
Niezależnie od tego, czy kucharz zmienia kierunek ruchu, czy też nie, ostatnią
czynnością wykonywaną przez omawianą metodę jest wywołanie metody check_drop()
obiektu klasy Chef.

Metoda check_drop()
Ta metoda jest wywoływana w każdym cyklu pętli mainloop(), ale to nie oznacza,
że 50 razy na sekundę jest zrzucana nowa pizza!
def check_drop(self):
""" Zmniejsz licznik odliczający czas lub zrzuć pizzę i zresetuj odliczanie. """
if self.time_til_drop > 0:
self.time_til_drop -= 1
else:
new_pizza = Pizza(x = self.x)
games.screen.add(new_pizza)
Powrót do gry Pizza Panic 359

# ustaw margines na mniej więcej 30% wysokości pizzy, niezależnie


od prędkości pizzy
self.time_til_drop = int(new_pizza.height * 1.3 / Pizza.speed) + 1

Atrybut time_til_drop reprezentuje mechanizm odliczania wykorzystywany przez


naszego kucharza. Jeśli wartość time_til_drop jest większa od 0, zostaje od niej odjęta
liczba 1. W przeciwnym wypadku tworzony jest nowy obiekt klasy Pizza, a wartość
atrybutu time_til_drop zostaje przywrócona do stanu początkowego.
Tworząc nową pizzę, przekazuje do konstruktora klasy Pizza współrzędną x obiektu
klasy Chef, żeby nowa pizza pojawiała się na ekranie w tym samym miejscu co duszek
kucharza.
Nowa wartość atrybutu time_til_drop jest obliczana w taki sposób, aby nowa pizza
była zrzucana wtedy, gdy jej odległość od poprzedniej wyniesie około 30% wysokości
pizzy, niezależnie od tego, z jaką szybkością pizze spadają.

Funkcja main()
Funkcja main() tworzy obiekty i uruchamia grę:
def main():
""" Uruchom grę. """
wall_image = games.load_image("sciana.jpg", transparent = False)
games.screen.background = wall_image

the_chef = Chef()
games.screen.add(the_chef)

the_pan = Pan()
games.screen.add(the_pan)

games.mouse.is_visible = False

games.screen.event_grab = True
games.screen.mainloop()

# wystartuj!
main()

Najpierw ustawiam ceglaną ścianę jako tło. Tworzę kucharza i patelnię. Następnie
ustawiam niewidoczność wskaźnika myszy i przechwytywanie wszystkich sygnałów
wejściowych, które sprawia, że wskaźnik myszy nie może opuścić okna graficznego.
Wywołuję metodę mainloop() w celu rozpoczęcia gry. W końcu wywołuję funkcję
main(), aby to wszystko uruchomić!
360 Rozdział 11. Grafika. Gra Pizza Panic

Podsumowanie
W tym rozdziale zobaczyłeś, jak można wykorzystać multimedialny pakiet livewires,
aby do programów dodać grafikę. Dowiedziałeś się, jak utworzyć nowe okno graficzne
i jak ustawić dla tego okna obraz tła. Zobaczyłeś, jak można wyświetlać tekst w oknie
graficznym. Poznałeś duszka, specjalny obiekt graficzny z obrazem. W szczególności
zobaczyłeś, jak można umiejscawiać i zmieniać położenie duszka na ekranie graficznym.
Zobaczyłeś również, jak można sprawdzać, czy między duszkami występują kolizje.
Dowiedziałeś się, jak pobierać dane wejściowe z myszy. Na koniec zobaczyłeś, jak to
wszystko można poskładać w jedną całość w postaci szybkiej gry wideo z udziałem
sterowanego przez komputer przeciwnika.

Sprawdź swoje umiejętności


1. Ulepsz grę Pizza Panic poprzez zwiększanie jej trudności w miarę postępu gry.
Pomyśl o różnych sposobach osiągnięcia tego celu. Mógłbyś zwiększać
prędkość pizz oraz prędkość ruchu kucharza. Mógłbyś podnieść poziom,
na którym porusza się patelnia. Mógłbyś nawet zwiększyć liczbę szalonych
kucharzy ciskających pizze.
2. Napisz grę, w której gracz steruje postacią, która musi unikać spadających
szczątków. Gracz steruje tą postacią za pomocą myszy, a obiekty spadają z nieba.
3. Utwórz prostą grę Pong dla jednego gracza, w której gracz steruje paletką,
a piłeczka odbija się od trzech ścian. Jeśli piłeczka przejdzie obok paletki gracza,
następuje koniec gry.
12
Dźwięk, animacja
i rozwijanie programu.
Gra Astrocrash

W tym rozdziale poszerzysz swoje umiejętności tworzenia programów multimedialnych


o obsługę dźwięku i ruchomych obrazów. Zobaczysz również, jak można pisać
duży program etapami. W szczególności nauczysz się:
 odczytywać dane z klawiatury,
 odtwarzać pliki dźwiękowe,
 odtwarzać pliki muzyczne,
 tworzyć animacje,
 rozwijać program poprzez pisanie coraz bardziej kompletnych jego wersji.

Wprowadzenie do gry Astrocrash


Projekt przedstawiony w tym rozdziale, gra Astrocrash, to moja wersja klasycznej gry
arkadowej Asteroids. W grze Astrocrash gracz steruje statkiem kosmicznym w ruchomym
polu śmiertelnie groźnych asteroidów. Statek może się obracać i wykonywać ruch do
przodu oraz, co najważniejsze, może wystrzeliwać pociski w kierunku asteroidów, aby je
zniszczyć. Gracz ma jednak trochę roboty do wykonania, ponieważ asteroidy dużego
i średniego rozmiaru rozpadają się na dwie mniejsze asteroidy, gdy zostaną zniszczone.
I w tej samej chwili, gdy graczowi uda się unicestwić wszystkie asteroidy, pojawia się ich
nowa, większa fala. Liczba punktów uzyskanych przez gracza zwiększa się wraz z każdą
zniszczoną asteroidą, lecz jeśli tylko statek gracza zderzy się z unoszącą się w przestrzeni
kosmiczną skałą, gra się kończy. Rysunki 12.1 i 12.2 pokazują tę grę w toku.
362 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Rysunek 12.1. Gracz steruje statkiem kosmicznym i niszczy asteroidy w celu zwiększenia
swojego wyniku punktowego. (Obraz mgławicy należy do domeny publicznej.
Dzięki uprzejmości: NASA, The Hubble Heritage Team–AURA/STScl)

Rysunek 12.2. Jeśli asteroida uderzy w statek gracza, gra się kończy
Odczyt klawiatury 363

Odczyt klawiatury
Już wiesz, jak pobierać łańcuchy znaków od użytkownika przy użyciu funkcji input(),
ale odczyt klawiatury w celu identyfikacji pojedynczych naciśnięć klawiszy to inna
kwestia. Na szczęście istnieje nowy obiekt z modułu games, który to właśnie umożliwia.

Prezentacja programu Odczytaj klawisz


Program Odczytaj klawisz wyświetla statek kosmiczny na tle mgławicy. Użytkownik
może przemieszczać statek po całym ekranie za pomocą naciśnięć różnych klawiszy.
Kiedy użytkownik naciska klawisz W, statek porusza się do góry. Gdy użytkownik
naciska klawisz S, statek porusza się w dół. Kiedy użytkownik naciska klawisz A, statek
porusza się w lewo. Kiedy użytkownik naciska klawisz D, statek porusza się w prawo.
Użytkownik może również nacisnąć wiele klawiszy jednocześnie dla uzyskania łącznego
efektu. Na przykład, gdy użytkownik naciska jednocześnie klawisze W i D, statek porusza
się po przekątnej — w prawo, do góry. Program został zilustrowany na rysunku 12.3.

Rysunek 12.3. Statek porusza się po ekranie dzięki naciśnięciom klawiszy

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to odczytaj_klawisz.py.
364 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Rozpoczęcie programu
Tak jak w przypadku wszystkich programów wykorzystujących pakiet livewires
rozpoczynam od importu potrzebnych modułów i wywołania funkcji inicjalizującej
ekran graficzny:
# Odczytaj klawisz
# Demonstruje odczytywanie klawiatury

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

Testowanie stanu klawiszy


Następnie piszę kod klasy reprezentującej statek kosmiczny. W metodzie update()
sprawdzam, czy zostały naciśnięte określone klawisze, i zgodnie z wynikiem tych testów
zmieniam pozycję statku.
class Ship(games.Sprite):
""" Poruszający się statek kosmiczny. """
def update(self):
""" Kieruj ruchem statku na podstawie wciśniętych klawiszy. """
if games.keyboard.is_pressed(games.K_w):
self.y -= 1
if games.keyboard.is_pressed(games.K_s):
self.y += 1
if games.keyboard.is_pressed(games.K_a):
self.x -= 1
if games.keyboard.is_pressed(games.K_d):
self.x += 1

Wykorzystuję nowy obiekt z modułu games o nazwie keyboard. Możesz użyć tego
obiektu do sprawdzenia, czy określone klawisze zostały naciśnięte. Wywołuję metodę
is_pressed() obiektu, która zwraca wartość True, jeśli testowany klawisz jest naciśnięty,
i wartość False w przeciwnym wypadku.
Używam metody is_pressed w ciągu instrukcji if, aby sprawdzić, czy którykolwiek
z czterech klawiszy — W, S, A lub D — nie jest naciśnięty. Jeśli jest naciśnięty klawisz W,
zmniejszam o 1 wartość właściwości y obiektu klasy Ship, przesuwając duszka w górę
ekranu o jeden piksel. Jeśli jest naciśnięty klawisz S, zwiększam wartość właściwości y
obiektu o 1, przesuwając duszka w dół ekranu. Jeśli jest naciśnięty klawisz A, zmniejszam
o 1 wartość właściwości x obiektu, przesuwając duszka w lewo. Jeśli jest naciśnięty
klawisz S, zwiększam wartość właściwości x obiektu o 1, przesuwając duszka w prawo.
Ponieważ wielokrotne wywołania metody is_pressed() mogą odczytywać
jednoczesne naciśnięcia klawiszy, użytkownik może przyciskać wiele klawiszy naraz dla
uzyskania łącznego efektu. Jeśli na przykład użytkownik przytrzymuje w tym samym czasie
wciśnięte klawisze D i S, statek porusza się w dół i w prawo, ponieważ za każdym razem,
gdy wykonywana jest metoda update(), wartość 1 zostaje dodana zarówno do
współrzędnej x, jak i współrzędnej y obiektu klasy Ship.
Obracanie duszka 365

Moduł games zawiera zbiór stałych reprezentujących klawisze, których możesz


używać w roli argumentu w wywołaniu metody is_pressed(). W tym programie
wykorzystuję stałą games.K_w reprezentującą klawisz W, stałą games.K_s odpowiadającą
klawiszowi S, stałą games.K_a oznaczającą klawisz A oraz stałą games.K_d identyfikującą
klawisz D. Schemat nadawania nazw tym stałym jest dość intuicyjny. Oto szybki sposób
na wydedukowanie nazwy większości stałych reprezentujących klawisze:
 wszystkie stałe klawiatury zaczynają się od games.K_;
 w przypadku klawiszy alfabetycznych dodaj literę z klawisza, po zamianie
na małą, na końcu nazwy stałej; na przykład stała reprezentująca klawisz A
to games.K_a;
 w przypadku klawiszy numerycznych dodaj cyfrę z klawisza na końcu nazwy
stałej; na przykład stała reprezentująca klawisz 1 to games.K_1;
 w przypadku pozostałych klawiszy często możesz na końcu nazwy stałej dodać
ich nazwę pisaną samymi dużymi literami; na przykład stała reprezentująca
klawisz spacji to games.K_SPACE.
Kompletną listę stałych klawiatury znajdziesz w dokumentacji pakietu livewires,
w dodatku B.

Dokończenie programu
Na koniec piszę znajomą funkcję main(). Ładuję obraz tła przedstawiający mgławicę,
tworzę statek, umieszczając go w środku ekranu, oraz wszystko uruchamiam poprzez
wywołanie metody mainloop().
def main():
nebula_image = games.load_image("mglawica.jpg", transparent = False)
games.screen.background = nebula_image

ship_image = games.load_image("statek.bmp")
the_ship = Ship(image = ship_image,
x = games.screen.width/2,
y = games.screen.height/2)
games.screen.add(the_ship)

games.screen.mainloop()

main()

Obracanie duszka
W rozdziale 11. dowiedziałeś się, jak przemieszczać duszki po ekranie, ale pakiet
livewires umożliwia również ich obracanie. Duszka można obracać, wykorzystując
jedną z jego właściwości.
366 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Prezentacja programu Obróć duszka


W programie Obróć duszka, użytkownik może obracać statek kosmiczny przy użyciu
klawiatury. Kiedy użytkownik naciska klawisz strzałki w górę, statek obraca się w kierunku
zgodnym z ruchem wskazówki zegara. Jeśli użytkownik naciśnie klawisz strzałki w lewo,
statek obróci się w kierunku przeciwnym do ruchu wskazówki zegara. Jeśli naciśnie
klawisz 1, kąt położenia statku zmieni się skokowo na 0 stopni. Jeśli użytkownik naciśnie
klawisz 2, statek zmieni swoją orientację na 90 stopni. Jeśli naciśnie klawisz 3, statek
przyjmie położenie kątowe 180 stopni. Jeśli użytkownik naciśnie klawisz 4, kąt położenia
zmieni się skokowo na 270 stopni. Program został przedstawiony na rysunku 12.4.

Rysunek 12.4. Statek kosmiczny może się obracać zgodnie z ruchem wskazówki zegara
lub w kierunku przeciwnym do ruchu wskazówki zegara.
Może też przeskoczyć do położenia pod z góry ustalonym kątem

Pułapka
Program Obróć duszka sprawdza, czy są wciśnięte klawisze cyfr znajdujące się
u góry klawiatury, powyżej klawiszy z literami, lecz nie sprawdza stanu klawiszy
klawiatury numerycznej.

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to obroc_duszka.py.
Obracanie duszka 367

# Obróć duszka
# Demonstruje obracanie duszka
from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

class Ship(games.Sprite):
""" Obracający się statek kosmiczny. """
def update(self):
""" Obróć w zależności od naciśniętych klawiszy. """
if games.keyboard.is_pressed(games.K_RIGHT):
self.angle += 1
if games.keyboard.is_pressed(games.K_LEFT):
self.angle -= 1

if games.keyboard.is_pressed(games.K_1):
self.angle = 0
if games.keyboard.is_pressed(games.K_2):
self.angle = 90
if games.keyboard.is_pressed(games.K_3):
self.angle = 180
if games.keyboard.is_pressed(games.K_4):
self.angle = 270

def main():
nebula_image = games.load_image("mglawica.jpg", transparent = False)
games.screen.background = nebula_image

ship_image = games.load_image("statek.bmp")
the_ship = Ship(image = ship_image,
x = games.screen.width/2,
y = games.screen.height/2)
games.screen.add(the_ship)

games.screen.mainloop()

main()

Wykorzystanie właściwości angle obiektu klasy Sprite


Nowym elementem w programie jest właściwość angle, która reprezentuje orientację
kątową duszka wyrażoną w stopniach. Możesz zwiększać lub zmniejszać wartość tej
właściwości przez dodawanie lub odejmowanie przyrostów, lecz możesz też po prostu
przypisać jej nową wartość w celu zmiany kąta położenia duszka.
W metodzie update() najpierw sprawdzam, czy wciśnięty jest klawisz strzałki w prawo.
Jeśli tak jest, dodaję jedynkę do wartości właściwości angle obiektu, co powoduje obrócenie
duszka o jeden stopień w kierunku ruchu wskazówki zegara. Następnie sprawdzam, czy
wciśnięty jest klawisz strzałki w lewo. Jeśli jest tak w istocie, odejmuję jedynkę od wartości
tej właściwości, powodując obrót duszka o jeden stopień w kierunku przeciwnym do
ruchu wskazówki zegara.
368 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Kolejny zestaw wierszy kodu obraca statek bezpośrednio do określonego kąta


położenia poprzez przypisanie nowej wartości do właściwości angle. Kiedy użytkownik
naciska klawisz 1, kod przypisuje 0 do właściwości angle i duszek przeskakuje do
położenia pod kątem 0 stopni (jest to jego początkowa orientacja). Kiedy użytkownik
naciska klawisz 2, kod przypisuje do właściwości angle wartość 90 i duszek przeskakuje
do położenia pod kątem 90 stopni. Gdy użytkownik naciska klawisz 3, kod przypisuje do
właściwości angle wartość 180 i duszek przeskakuje do położenia pod kątem 180 stopni.
Wreszcie, gdy użytkownik naciska klawisz 4, kod przypisuje do właściwości angle
wartość 270 i duszek przeskakuje do położenia pod kątem 270 stopni.

Tworzenie animacji
Przemieszczanie duszków i ich obracanie sprawia, że gra staje się bardziej ekscytująca,
lecz dopiero animacja wnosi w nią prawdziwe życie. Na szczęście moduł games zawiera
klasę do obsługi animacji, stosownie nazwaną Animation.

Prezentacja programu Eksplozja


Program Eksplozja tworzy animację wybuchu w środku ekranu graficznego. Animacja
jest odtwarzana w sposób ciągły, żebyś mógł się jej dobrze przyjrzeć. Kiedy skończysz już
podziwiać ten bombowy efekt, możesz zakończyć program przez zamknięcie okna
graficznego. Na rysunku 12.5 prezentuję migawkę programu w akcji.

Rysunek 12.5. Chociaż trudno to stwierdzić na podstawie nieruchomego obrazu,


w centrum okna graficznego wykonywana jest animacja wybuchu
Przegląd obrazów eksplozji 369

Przegląd obrazów eksplozji


Animacja to sekwencja obrazów (zwanych także ramkami — ang. frames) wyświetlanych
jeden po drugim. Utworzyłem sekwencję dziewięciu obrazów, które, kiedy są wyświetlane
jeden po drugim, tworzą wrażenie ognistej eksplozji. Wszystkie dziewięć obrazków
pokazałem na rysunku 12.6.

Rysunek 12.6. Gdy te dziewięć ramek zostanie wyświetlonych w szybkim tempie


jedna po drugiej, otrzymamy wrażenie eksplozji

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to eksplozja.py.

Rozpoczęcie programu
Jak zawsze, na początku programu importuję potrzebne moduły i wywołuję funkcję
inicjalizującą ekran graficzny:
# Eksplozja
# Demonstruje tworzenie animacji

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)


370 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Potem ustawiam tło ekranu graficznego:


nebula_image = games.load_image("mglawica.jpg", transparent = 0)
games.screen.background = nebula_image

Utworzenie listy plików z obrazami


Konstruktor klasy Animation pobiera listę nazw plików zawierających obrazy lub listę
obiektów obrazu reprezentującą sekwencję obrazów do wyświetlenia. Więc w następnej
kolejności tworzę listę nazw plików z obrazami, które zostały pokazane na rysunku 12.6:
explosion_files = ["eksplozja1.bmp",
"eksplozja2.bmp",
"eksplozja3.bmp",
"eksplozja4.bmp",
"eksplozja5.bmp",
"eksplozja6.bmp",
"eksplozja7.bmp",
"eksplozja8.bmp",
"eksplozja9.bmp"]

Utworzenie obiektu klasy Animation


W końcu tworzę obiekt klasy Animation i dodaję go do ekranu:
explosion = games.Animation(images = explosion_files,
x = games.screen.width/2,
y = games.screen.height/2,
n_repeats = 0,
repeat_interval = 5)
games.screen.add(explosion)

Animation jest klasą pochodną klasy Sprite, więc dziedziczy wszystkie jej atrybuty,
właściwości i metody. Podobnie jak w przypadku wszystkich duszków, możesz podać
współrzędne x i y, aby zdefiniować umiejscowienie animacji. W powyższym kodzie
przekazuję współrzędne do konstruktora klasy, tak aby animacja była utworzona
w środku ekranu.
Animacja tym się różni od duszka, że występuje w niej lista obrazów, która jest
przetwarzana cyklicznie. Więc musisz dostarczyć listę nazw plików graficznych w postaci
łańcuchów znaków albo listę obiektów obrazu reprezentujących obrazy, które mają być
wyświetlane. Ja dostarczam listę explosion_files z łańcuchami reprezentującymi nazwy
plików graficznych poprzez parametr images.
Atrybut n_repeats obiektu określa, ile razy animacja (jako sekwencja jej wszystkich
obrazów) zostanie wyświetlona. Wartość domyślna atrybutu n_repeats wynosi 0.
Ponieważ przekazuję 0 do n_repeats, cykl animacji eksplozji będzie powtarzany
bez końca (lub przynajmniej do momentu zamknięcia okna graficznego).
Atrybut repeat_interval obiektu reprezentuje opóźnienie między następującymi po
sobie obrazami. Większa liczba oznacza większe opóźnienie między ramkami, skutkujące
Wykorzystywanie dźwięku i muzyki 371

wolniejszą animacją. Mniejsza liczba reprezentuje mniejszą zwłokę, generując szybszą


animację. Ja przekazuję do atrybutu repeat_interval wartość 5, aby uzyskać prędkość,
jaką uważam za właściwą do wygenerowania przekonującej eksplozji.
Ostatnią, lecz równie ważną czynnością jest uruchomienie programu poprzez
wywołanie metody mainloop() obiektu screen:
games.screen.mainloop()

Wykorzystywanie dźwięku i muzyki


Dźwięk i muzyka dodają nowy, oddziałujący na zmysły wymiar do programów. Ładowanie,
odtwarzanie, powtarzanie w pętli i zatrzymywanie dźwięku i muzyki są łatwe do wykonania
za pomocą modułu games. Chociaż ludzie mogliby się spierać na temat różnicy między
dźwiękiem a muzyką, na gruncie pakietu livewires nie ma żadnej takiej dyskusji —
istnieje w nim wyraźne rozróżnienie między tymi dwoma elementami.

Prezentacja programu Dźwięk i muzyka


Program Dźwięk i muzyka umożliwia użytkownikowi odtwarzanie, powtarzanie w pętli
i zatrzymywanie efektu dźwiękowego wystrzelonego pocisku oraz tematu muzycznego
z gry Astrocrash. Użytkownik może nawet odtwarzać obydwa te elementy jednocześnie.
Rysunek 12.7 pokazuje uruchomiony program (lecz niestety nie jest w stanie
wygenerować dźwięku).

Rysunek 12.7. Program umożliwia użytkownikowi odtworzenie dźwięku i fragmentu muzyki


372 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Wskazówka
Kiedy uruchomisz ten program, będzie Ci potrzebna interakcja z oknem konsoli.
Powinieneś umieścić okno konsoli w takiej pozycji, aby nie było ono zakryte
przez okno graficzne. Możesz zignorować lub nawet zminimalizować utworzone
przez program okno graficzne.

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to dzwiek_i_muzyka.py.

Praca z dźwiękami
Możesz utworzyć obiekt dźwiękowy do użytku programu poprzez załadowanie pliku
typu WAV. Format WAV znakomicie się nadaje do zapisu efektów dźwiękowych,
ponieważ może zostać użyty do zakodowania wszystkiego, co zarejestrujesz za pomocą
mikrofonu.

Załadowanie dźwięku
Program rozpoczynam jak zawsze:
# Dźwięk i muzyka
# Demonstruje odtwarzanie plików dźwiękowych i muzycznych

from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

Potem ładuję plik WAV, wykorzystując funkcję load_sound() modułu games.


# załaduj plik dźwiękowy
missile_sound = games.load_sound("pocisk.wav")

Funkcja przyjmuje łańcuch znaków reprezentujący nazwę pliku dźwiękowego, który


ma zostać załadowany. Ładuję plik pocisk.wav i przypisuję nowo utworzony obiekt
dźwiękowy do zmiennej missile_sound.

Pułapka
Za pomocą funkcji load_sound() można ładować tylko pliki WAV.

Następnie ładuję plik muzyczny:


# załaduj plik muzyczny
games.music.load("temat.mid")

Omówienie sposobu obsługi muzyki odłożę do momentu, gdy skończę


demonstrowanie dźwięków.
Wykorzystywanie dźwięku i muzyki 373

Odtworzenie dźwięku
Następnie tworzę menu, z czym po raz pierwszy spotkałeś się w rozdziale 5.:
choice = None
while choice != "0":

print(
"""
Dźwięk i muzyka

0 - zakończ
1 - odtwórz dźwięk pocisku
2 - odtwarzaj cyklicznie dźwięk pocisku
3 - zatrzymaj odtwarzanie dźwięku pocisku
4 - odtwórz temat muzyczny
5 - odtwarzaj cyklicznie temat muzyczny
6 - zatrzymaj odtwarzanie tematu muzycznego
"""
)

choice = input("Wybieram: ")


print()

# wyjdź
if choice == "0":
print("Żegnaj!")

Jeśli użytkownik wprowadzi 0, program pożegna użytkownika i zakończy działanie.


Poniższy kod obsługuje przypadek, w którym użytkownik wprowadza 1:
# odtwórz dźwięk pocisku
elif choice == "1":
missile_sound.play()
print("Odtworzenie dźwięku pocisku.")

Aby odtworzyć dźwięk jeden raz, wywołuję metodę play() obiektu dźwiękowego.
Kiedy dźwięk jest odtwarzany, zajmuje jeden z ośmiu dostępnych kanałów dźwiękowych.
Aby odtworzyć dźwięk, potrzeba przynajmniej jednego otwartego kanału dźwiękowego.
Kiedy zajętych jest wszystkich osiem kanałów, wywołanie metody play() obiektu
dźwiękowego nie przyniesie żadnego efektu.
Jeśli wywołasz metodę play() obiektu dźwiękowego, który jest już odtwarzany,
rozpocznie się odtwarzanie tego dźwięku na innym kanale, jeśli taki jest dostępny.

Cykliczne odtwarzanie dźwięku


Możesz odtwarzać dźwięk cyklicznie poprzez przekazanie liczby dodatkowych
odtworzeń, jakie mają mieć miejsce, do metody play() obiektu. Jeśli na przykład
przekażesz do play() liczbę 3, odpowiedni dźwięk zostanie odtworzony cztery razy
(początkowe odtworzenie plus trzy powtórzenia). Możesz cyklicznie odtwarzać dźwięk
bez końca po przekazaniu wartości -1 do metody play().
Poniższy kod obsługuje przypadek, gdy użytkownik wprowadza 2:
374 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

# odtwarzaj cyklicznie dźwięk pocisku


elif choice == "2":
loop = int(input("Ile razy powtórzyć odtwarzanie? (-1 = bez końca): "))
missile_sound.play(loop)
print("Cykliczne odtwarzanie dźwięku pocisku.")

W tym fragmencie kodu pobieram liczbę wskazującą, ile dodatkowo razy użytkownik
chce usłyszeć odgłos pocisku, a następnie przekazuję tę wartość do metody play()
obiektu dźwiękowego.

Zatrzymanie odtwarzania dźwięku


Odtwarzanie dźwięku przez obiekt dźwiękowy zatrzymuje się poprzez wywołanie metody
stop(). Zatrzymuje ona ten konkretny dźwięk na wszystkich kanałach, na których jest
odtwarzany. Jeśli wywołasz metodę stop() obiektu dźwiękowego, który w danej chwili
nic nie odtwarza, przekonasz się, że pakiet livewires jest tolerancyjny i nie poskarży się
poprzez zgłoszenie błędu.
Jeśli użytkownik wprowadzi 3, poniższy kod zatrzyma odtwarzanie odgłosu pocisku
(jeśli takowy jest właśnie odtwarzany):
# zatrzymaj odtwarzanie dźwięku pocisku
elif choice == "3":
missile_sound.stop()
print("Zatrzymanie odtwarzania dźwięku pocisku.")

Praca z muzyką
W pakiecie livewires muzyka jest obsługiwana nieco inaczej niż dźwięk. Istnieje tylko
jeden kanał muzyczny, więc w danym momencie jako bieżący plik muzyczny może
zostać wyznaczony tylko jeden plik. Kanał muzyczny jest jednak bardziej elastyczny
niż kanały dźwiękowe. Akceptuje on wiele różnych typów plików dźwiękowych, takich
jak WAV, MP3, OGG i MIDI. Wreszcie, ponieważ istnieje tylko jeden kanał muzyczny,
nie tworzy się nowego obiektu dla każdego pliku muzycznego. Zamiast tego masz dostęp
do zestawu funkcji służących do ładowania, odtwarzania i zatrzymywania muzyki.

Załadowanie muzyki
Widziałeś kod odpowiedzialny za załadowanie pliku muzycznego w podpunkcie
„Załadowanie dźwięku” punktu „Praca z dźwiękami”. Kod skorzystał z dostępu do
obiektu music modułu games. To dzięki obiektowi music możesz załadować, odtworzyć
i zatrzymać pojedynczą ścieżkę muzyczną.
Kod, którego użyłem do załadowania ścieżki muzycznej, games.music.load("temat.mid"),
ustawia bieżącą muzykę na plik temat.mid typu MIDI. Muzykę ładuje się poprzez wywołanie
metody games.music.load() i przekazanie do niej nazwy pliku muzycznego w postaci
łańcucha znaków.
Masz dostępną tylko jedną ścieżkę muzyczną. Więc jeśli załadujesz nowy plik
muzyczny, zastąpi on muzykę bieżącą.
Wykorzystywanie dźwięku i muzyki 375

Odtworzenie muzyki
Poniższy kod obsługuje przypadek, gdy użytkownik wprowadzi 4:
# odtwórz temat muzyczny
elif choice == "4":
games.music.play()
print("Odtworzenie tematu muzycznego.")

W rezultacie komputer odtwarza plik muzyczny, który załadowałem, temat.mid.


Jeśli nie przekażesz żadnych wartości do metody games.music.play(), muzyka jest
odtwarzana tylko raz.

Cykliczne odtwarzanie muzyki


Możesz odtwarzać muzykę cyklicznie, tyle razy, ile chcesz, po przekazaniu do metody
play() liczby dodatkowych odtworzeń. Jeśli na przykład przekażesz wartość 3 do metody
games.music.play(), muzyka zostanie odtworzona cztery razy (odtworzenie początkowe
plus trzy powtórzenia). Możesz cyklicznie odtwarzać muzykę bez końca po przekazaniu
wartości -1 do metody.
Poniższy kod obsługuje przypadek, w którym użytkownik wprowadza 5:
# odtwarzaj cyklicznie temat muzyczny
elif choice == "5":
loop = int(input("Ile razy powtórzyć odtwarzanie? (-1 = bez końca): "))
games.music.play(loop)
print("Cykliczne odtwarzanie tematu muzycznego.")

W tym fragmencie kodu wczytuję liczbę dodatkowych odtworzeń tematu muzycznego,


jakiej użytkownik chce posłuchać, a następnie przekazuję tę wartość do metody play().

Zatrzymanie odtwarzania muzyki


Jeśli użytkownik wprowadzi opcję 6, poniższy kod zatrzyma odtwarzanie muzyki (jeśli
faktycznie jest wykonywane):
# zatrzymaj odtwarzanie tematu muzycznego
elif choice == "6":
games.music.stop()
print("Zatrzymanie odtwarzania tematu muzycznego.")

Możesz spowodować zatrzymanie odtwarzania bieżącej muzyki poprzez wywołanie


metody games.music.stop(), co właśnie w tym miejscu kodu robię. Jeśli wywołasz tę
metodę, kiedy nie jest odtwarzana żadna muzyka, moduł livewires okazuje się
wyrozumiały i nie generuje błędu.

Dokończenie programu
Wreszcie kończę program obsługą nieprawidłowego wyboru i oczekiwaniem na decyzję
użytkownika:
376 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

# nieprzewidziany wybór
else:
print("\nNiestety,", choice, "nie jest prawidłowym wyborem.")

input("\n\nAby zakończyć program, naciśnij klawisz Enter.")

Planowanie gry Astrocrash


Pora na powrót do projektu rozdziału — gry Astrocrash. Zamierzam pisać stopniowo
coraz kompletniejsze wersję gry, aż osiągnę postać końcową, lecz nadal odczuwam
potrzebę wymienienia kilku szczegółów programu, w tym głównych elementów gry,
kilku niezbędnych klas i wymaganych zasobów multimedialnych.

Elementy gry
Chociaż moja gra jest oparta na klasycznej grze wideo, którą dobrze znam (a poznawałem ją
etapami, drogą prób i błędów), wypisanie listy jej elementów jest nadal dobrym pomysłem:
 statek kosmiczny powinien obracać się i inicjować (lub przyśpieszać) ruch
do przodu w reakcji na klawisze naciśnięte przez gracza;
 statek powinien wystrzeliwać pociski po naciśnięciu przez gracza
odpowiedniego klawisza;
 asteroidy powinny przelatywać przez ekran z różnymi prędkościami; mniejsze
asteroidy powinny mieć generalnie wyższe prędkości niż większe;
 statek, wszystkie pociski i asteroidy powinny „przewijać się” przez brzegi ekranu —
jeśli wyjdą poza granicę ekranu, powinny ukazać się po przeciwnej stronie;
 jeśli pocisk uderzy w dowolny inny obiekt na ekranie, powinien zniszczyć ten
obiekt i sam siebie w efektownej, ognistej eksplozji;
 jeśli statek uderzy w dowolny inny obiekt na ekranie, powinien zniszczyć ten
obiekt i sam siebie w efektownej, ognistej eksplozji;
 jeśli statek zostaje zniszczony, gra się kończy;
 jeśli zostaje zniszczona duża asteroida, powinny utworzyć się dwie asteroidy
średniej wielkości; jeśli zostaje zniszczona asteroida średniego rozmiaru,
powinny powstać dwie małe asteroidy; jeśli zostaje zniszczona mała asteroida,
nie powstają już żadne nowe;
 za każdym razem, gdy gracz zniszczy asteroidę, jego dorobek punktowy powinien
się zwiększyć; mniejsze asteroidy powinny być warte więcej punktów niż większe;
 liczba punktów uzyskanych przez gracza powinna być wyświetlana w prawym
górnym rogu ekranu;
 kiedy tylko wszystkie asteroidy zostaną zniszczone, powinna zostać utworzona
nowa, większa fala asteroidów.
Pomijam kilka elementów oryginału, aby zachować prostotę gry.
Utworzenie asteroidów 377

Klasy potrzebne w grze


Następnie sporządzam listę klas, które, jak sądzę, będą mi potrzebne:
 Ship,
 Missile,
 Asteroid,
 Explosion.

Już trochę wiem o tych klasach. Ship, Missile i Asteroid powinny być klasami
pochodnymi klasy games.Sprite, podczas gdy Explosion powinna być klasą pochodną
klasy games.Animation. Wiem też, że ta lista może ulec zmianie, kiedy teorię będę
zamieniał w praktykę i gdy będę pisał kod gry.

Zasoby gry
Ponieważ gra zawiera dźwięk, muzykę, duszki i animację, wiem, że muszę utworzyć
pewną liczbę plików multimedialnych. Oto lista, jaką udało mi się stworzyć:
 plik graficzny reprezentujący statek kosmiczny,
 plik graficzny reprezentujący pociski,
 trzy pliki graficzne, po jednym dla każdego rozmiaru asteroidy,
 seria plików graficznych do animacji eksplozji,
 plik dźwiękowy imitujący rozpędzanie statku,
 plik dźwiękowy z odgłosem wystrzeliwania pocisku,
 plik dźwiękowy imitujący eksplozję obiektu,
 plik z tematem muzycznym.

Utworzenie asteroidów
Ponieważ w grze mają występować śmiercionośne asteroidy, pomyślałem, że zacznę od nich.
Choć wydaje się, że jest to dla mnie najlepszy wybór pierwszego kroku, w przypadku
innego programisty może być inaczej — i jest to w porządku. Mógłbyś oczywiście wybrać
inny pierwszy krok, taki jak umieszczenie na ekranie statku kosmicznego gracza. Nie
istnieje jeden właściwy pierwszy krok. Najważniejszą rzeczą jest zdefiniowanie i wykonanie
programów „na jeden kęs”, które, bazując jeden na drugim, wypracowują ścieżkę do
kompletnego projektu.

Program Astrocrash01
Program Astrocrash01 tworzy okno graficzne, ustawia tło w postaci mgławicy i tworzy
osiem asteroid w losowo wybranych miejscach. Prędkość każdej asteroidy jest również
378 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

obliczana z uwzględnieniem losowości, lecz mniejsze asteroidy mogą się poruszać


szybciej niż większe. Na rysunku 12.8 pokazuję program w akcji.

Rysunek 12.8. Pole poruszających się asteroid stanowi podstawę gry

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash01.py.

Rozpoczęcie programu
Program rozpoczyna się jak większość pozostałych:
# Astrocrash01
# Tworzy poruszające się po ekranie asteroidy

import random
from livewires import games

games.init(screen_width = 640, screen_height = 480, fps = 50)

Importuję moduł random, aby wygenerować współrzędne x i y dla asteroid.


Utworzenie asteroidów 379

Klasa Asteroid
Klasa Asteroid jest wykorzystywana do tworzenia poruszających się asteroid:
class Asteroid(games.Sprite):
""" Asteroida przelatująca przez ekran. """
SMALL = 1
MEDIUM = 2
LARGE = 3
images = {SMALL : games.load_image("asteroida_mala.bmp"),
MEDIUM : games.load_image("asteroida_sred.bmp"),
LARGE : games.load_image("asteroida_duza.bmp") }

SPEED = 2

Pierwszą moją czynnością jest zdefiniowanie stałych klasowych reprezentujących


trzy różne wielkości asteroid: SMALL (mała), MEDIUM (średniego rozmiaru) i LARGE (duża).
Następnie tworzę słownik z rozmiarami i odpowiadającymi im obiektami obrazów
asteroid. W ten sposób mogę wykorzystać stałą reprezentującą rozmiar do znalezienia
odpowiedniego obiektu obrazu. Na koniec tworzę stałą klasową o nazwie SPEED, której
użyję jako podstawy do obliczenia ulosowionej prędkości każdej asteroidy.

Metoda __init__()
W następnej kolejności zajmuję się zdefiniowaniem konstruktora:
def __init__(self, x, y, size):
""" Inicjalizuj duszka asteroidy. """
super(Asteroid, self).__init__(
image = Asteroid.images[size],
x = x, y = y,
dx = random.choice([1, -1]) * Asteroid.SPEED * random.random()/size,
dy = random.choice([1, -1]) * Asteroid.SPEED * random.random()/size)

self.size = size

Wartość przekazana poprzez parametr size reprezentuje wielkość asteroidy


i powinna być równa jednej ze stałych rozmiaru: Asteroid.SMALL, Asteroid.MEDIUM
lub Asteroid.LARGE. Na podstawie wartości size pobierany jest odpowiedni obraz nowej
asteroidy, który następnie zostaje przekazany do konstruktora klasy Sprite (ponieważ
Sprite jest klasą nadrzędną klasy Asteroid). Do konstruktora klasy Sprite zostają również
przekazane wartości x i y reprezentujące położenie kosmicznej skały, przekazane
wcześniej do konstruktora klasy Asteroid.
Konstruktor klasy Asteroid generuje losowe wartości składowych prędkości nowego
obiektu i przekazuje je do konstruktora klasy Sprite. Składowe prędkości mają wartości
losowe, ale mniejsze asteroidy mogą się potencjalnie poruszać szybciej niż większe.
W końcu konstruktor klasy Asteroid tworzy i inicjalizuje atrybut size obiektu.
380 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Metoda update()
Metoda update() utrzymuje asteroidę w grze poprzez przeniesienie jej na przeciwległy
brzeg ekranu:
def update(self):
""" Przenieś asteroidę na przeciwległy brzeg ekranu. """
if self.top > games.screen.height:
self.bottom = 0

if self.bottom < 0:
self.top = games.screen.height

if self.left > games.screen.width:


self.right = 0

if self.right < 0:
self.left = games.screen.width

Funkcja main()
Na koniec funkcja main() ustawia tło w postaci mgławicy oraz tworzy osiem asteroid
w przypadkowych miejscach ekranu:
def main():
# ustaw tło
nebula_image = games.load_image("mglawica.jpg")
games.screen.background = nebula_image

# utwórz 8 asteroid
for i in range(8):
x = random.randrange(games.screen.width)
y = random.randrange(games.screen.height)
size = random.choice([Asteroid.SMALL, Asteroid.MEDIUM, Asteroid.LARGE])
new_asteroid = Asteroid(x = x, y = y, size = size)
games.screen.add(new_asteroid)

games.screen.mainloop()

# wystartuj!
main()

Obracanie statku
Aby wykonać swoje następne zadanie, wprowadzam statek kosmiczny gracza. Moim
skromnym celem jest umożliwienie użytkownikowi obracania statku za pomocą klawiszy
strzałek. Do pozostałych funkcji statku zamierzam zabrać się później.
Obracanie statku 381

Program Astrocrash02
Program Astrocrash02 stanowi rozszerzenie programu Astrocrash01. W nowej wersji
tworzę w środku ekranu statek, który gracz może obracać. Jeśli gracz naciska klawisz
strzałki w prawo, statek obraca się zgodnie z ruchem wskazówek zegara. Jeśli zaś gracz
naciska klawisz strzałki w lewo, statek obraca się w kierunku przeciwnym do ruchu
wskazówek zegara. Na rysunku 12.9 pokazuję ten program w działaniu.

Rysunek 11.9. Statek kosmiczny gracz stanowi teraz część akcji

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash02.py.

Klasa Ship
Moim głównym zadaniem jest napisanie kodu klasy Ship reprezentującej statek
kosmiczny gracza:
class Ship(games.Sprite):
""" Statek kosmiczny gracza. """
image = games.load_image("statek.bmp")
ROTATION_STEP = 3
382 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

def update(self):
""" Obróć statek zgodnie z naciśniętym klawiszem. """
if games.keyboard.is_pressed(games.K_LEFT):
self.angle -= Ship.ROTATION_STEP
if games.keyboard.is_pressed(games.K_RIGHT):
self.angle += Ship.ROTATION_STEP

Ta klasa jest podobna do klasy występującej w programie Obróć duszka z wcześniejszej


części tego rozdziału, lecz istnieje kilka różnic. Po pierwsze, ładuję obraz statku i przypisuję
uzyskany w ten sposób obiekt obrazu do zmiennej klasowej o nazwie image. Po drugie,
wykorzystuję stałą klasową, ROTATION_STEP, do reprezentowania liczby stopni, o jaką
statek się obraca.

Konkretyzacja obiektu klasy Ship


Moją ostatnią czynnością w tej nowej wersji gry jest konkretyzacja obiektu klasy Ship
oraz dodanie go do ekranu. Tworzę nowy statek w funkcji main():
# utwórz statek
the_ship = Ship(image = Ship.image,
x = games.screen.width/2,
y = games.screen.height/2)
games.screen.add(the_ship)

Poruszanie statku
W następnej wersji programu wprawiam statek w ruch. Gracz może nacisnąć strzałkę
w górę, aby włączyć silnik statku. Dzięki temu na statek oddziałuje siła ciągu, pchając go
w kierunku, jaki wskazuje przód statku. Ponieważ brak jest tarcia, statek kontynuuje
poruszanie się, nie tracąc prędkości nadanej mu na początku przez gracza.

Program Astrocrash03
Kiedy gracz włącza silnik statku, program Astrocrash03 zmienia prędkość statku
w sposób zależny od położenia kątowego statku (czemu towarzyszy odpowiedni
efekt dźwiękowy). Program został zilustrowany na rysunku 12.10.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash03.py.

Import modułu math


Moją pierwszą czynnością jest umieszczenie na początku programu instrukcji
importującej nowy moduł:
import math, random
Poruszanie statku 383

Rysunek 12.10. Teraz statek może się poruszać po ekranie

Moduł math zawiera znaczną liczbę funkcji i stałych matematycznych, ale niech Cię to
nie przeraża. W tym programie użyję tylko kilku z nich.

Dodanie do klasy Ship zmiennej i stałej klasowej


Tworzę stałą klasową, VELOCITY_STEP, którą wykorzystam do zmiany prędkości statku:
VELOCITY_STEP = .03

Użycie większej liczby spowodowałoby szybsze przyśpieszanie statku,


podczas gdy mniejsza liczba sprawiłaby, że statek przyśpieszałby wolniej.
Dodaję również nową zmienną klasową, sound, mającą reprezentować dźwięk
towarzyszący przyśpieszaniu statku:
sound = games.load_sound("przyspieszenie.wav")

Modyfikacja metody update() klasy Ship


Następnie dodaję nowy kod na końcu metody update() klasy Ship, aby sprawić, by statek
się poruszał. Sprawdzam, czy gracz naciska klawisz strzałki w górę. Jeśli ma to miejsce,
odtwarzam dźwięk przyśpieszającego statku:
384 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

# zastosuj siłę ciągu przy naciśniętym klawiszu strzałki w górę


if games.keyboard.is_pressed(games.K_UP):
Ship.sound.play()

Poza tym, gdy gracz naciska klawisz strzałki w górę, muszę zmieniać składowe
prędkości statku (właściwości dx i dy obiektu klasy Ship). Więc jak, mając dany kąt
położenia statku, mogę obliczyć wartość, o jaką powinienem zmienić każdą ze składowych
prędkości? Odpowiedź daje trygonometria. Poczekaj, nie zamykaj z trzaskiem tej książki
i nie uciekaj, gdzie Cię nogi poniosą, wykrzykując coś bez ładu i składu. Jak obiecałem,
do tych obliczeń wykorzystam tylko dwie funkcję matematyczne w paru wierszach kodu.
Aby rozpocząć ten proces, obliczam kąt położenia statku po zamianie stopni
na radiany:
# zmień składowe prędkości w zależności od kąta położenia statku
angle = self.angle * math.pi / 180 # zamień na radiany

Radian to tylko miara obrotu, podobnie jak stopień. Moduł math w języku Python
wymaga, aby miary kątów były wyrażone w radianach (podczas gdy pakiet livewires
używa stopni), więc z tego powodu muszę dokonać konwersji. W obliczeniu
wykorzystuję stałą pi modułu math, która reprezentuje liczbę π.
Kiedy już mam kąt położenia statku wyrażony w radianach, mogę obliczyć, o jaką
wartość powinienem zmienić każdą ze składowych prędkości, wykorzystując funkcje
sin() i cos() obliczające sinus i cosinus kąta. W poniższych wierszach zostają obliczone
nowe wartości właściwości dx i dy obiektu:
self.dx += Ship.VELOCITY_STEP * math.sin(angle)
self.dy += Ship.VELOCITY_STEP * -math.cos(angle)

Zasadniczo math.sin(angle) reprezentuje procent siły ciągu powodujący zmianę


prędkości statku w kierunku osi x, podczas gdy -math.cos(angle) reprezentuje procent
siły ciągu zmieniający prędkość statku w kierunku osi y.
Pozostaje tylko zająć się granicami ekranu. Korzystam z tej samej strategii, której
używałem w przypadku asteroid: statek wychodzący poza krawędź ekranu powinien
wrócić po przeciwnej stronie. Prawdę mówiąc, kopiuję kod z metody update() klasy
Asteroid i wklejam go na końcu metody update() w klasie Ship:
# przenieś statek na przeciwległy brzeg ekranu
if self.top > games.screen.height:
self.bottom = 0

if self.bottom < 0:
self.top = games.screen.height

if self.left > games.screen.width:


self.right = 0

if self.right < 0:
self.left = games.screen.width
Wystrzeliwanie pocisków 385

Chociaż jest to skuteczne, kopiowanie i wklejanie dużych fragmentów kodu to zwykle


oznaka słabości projektu. Wrócę do tego kodu później, aby znaleźć bardziej eleganckie
rozwiązanie.

Pułapka
Powtarzające się, duże porcje kodu powodują rozdęcie programów i sprawiają,
że stają się one trudniejsze do konserwacji. Kiedy widzisz powtarzający się kod,
to często pora na wprowadzenie nowej funkcji lub klasy. Pomyśl, jak mógłbyś
skonsolidować kod w jednym miejscu i wywoływać go z innych części programu,
w których powtarzający się kod aktualnie występuje.

Wystrzeliwanie pocisków
Następnie umożliwię statkowi wystrzeliwanie pocisków. Kiedy gracz naciska klawisz
spacji, wystrzeliwany jest pocisk z działa statku, który leci w kierunku wskazywanym
przez przód statku. Pocisk powinien niszczyć wszystko, w co uderza, ale aby nie
komplikować spraw, odkładam frajdę niszczenia do jednej z późniejszych wersji
programu.

Program Astrocrash04
Program Astrocrash04 pozwala graczowi na wystrzeliwanie pocisków poprzez naciśnięcie
klawisza spacji, lecz jest z tym pewien problem. Jeśli gracz przytrzymuje naciśnięty
klawisz spacji, ze statku wylatuje strumień pocisków w tempie około 50 na sekundę.
Muszę ograniczyć tempo wystrzeliwania pocisków, lecz zostawiam ten problem do
następnej wersji gry. Na rysunku 12.11 przedstawiłem program Astrocrash04 z pełnym
realizmem.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash04.py.

Modyfikacja metody update() klasy Ship


Modyfikuję metodę update() klasy Ship poprzez dodanie kodu, dzięki któremu statek
może wystrzeliwać pociski. Jeśli gracz naciśnie klawisz spacji, tworzony jest nowy pocisk:
# wystrzel pocisk, jeśli jest naciśnięty klawisz spacji
if games.keyboard.is_pressed(games.K_SPACE) or True:
new_missile = Missile(self.x, self.y, self.angle)
games.screen.add(new_missile)

Oczywiście, aby skonkretyzować nowy obiekt przy użyciu wyrażenia Missile(self.x,


self.y, self.angle), muszę napisać taką drobną rzecz… jak klasa Missile.
386 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Rysunek 12.11. Tempo wystrzeliwania pocisków jest zbyt duże

Klasa Missile
Piszę kod klasy Missile mającej reprezentować pociski wystrzeliwane przez statek.
Zaczynam od utworzenia zmiennych i stałych klasowych:
class Missile(games.Sprite):
""" Pocisk wystrzelony przez statek gracza. """
image = games.load_image("pocisk.bmp")
sound = games.load_sound("pocisk.wav")
BUFFER = 40
VELOCITY_FACTOR = 7
LIFETIME = 40

Zmienna image ma reprezentować pocisk — pełne, czerwone kółko. Zmienna sound


reprezentuje efekt dźwiękowy wystrzeliwania pocisku. Stała BUFFER definiuje odległość
miejsca utworzenia nowego pocisku od statku (żeby pocisk nie został utworzony
na wierzchu statku). Stała VELOCITY_FACTOR wpływa na szybkość lotu pocisku.
Wreszcie stała LIFETIME określa długość czasu istnienia pocisku przed jego zniknięciem
(żeby pocisk nie latał bez końca po ekranie).

Metoda __init__()
Rozpoczynam kod konstruktora klasy od następujących wierszy:
def __init__(self, ship_x, ship_y, ship_angle):
""" Inicjalizuj duszka pocisku. """
Wystrzeliwanie pocisków 387

Możesz być zaskoczony tym, że konstruktor obiektu pocisku wymaga podania


wartości współrzędnych x i y statku oraz jego kąta położenia, które są przyjmowane przez
parametry ship_x, ship_y oraz ship_angle. Metoda potrzebuje tych wartości, aby ustalić
dwie rzeczy: dokładne miejsce pierwszego pojawienia się pocisku oraz składowe jego
prędkości. To, gdzie tworzony jest pocisk, zależy od miejsca statku, a to, jak pocisk leci,
zależy od jego położenia kątowego.
Następnie odtwarzam efekt dźwiękowy wystrzeliwania pocisku:
Missile.sound.play()

Potem wykonuję trochę obliczeń, aby określić początkowe położenie nowego pocisku:
# zamień na radiany
angle = ship_angle * math.pi / 180

# oblicz pozycję początkową pocisku


buffer_x = Missile.BUFFER * math.sin(angle)
buffer_y = Missile.BUFFER * -math.cos(angle)
x = ship_x + buffer_x
y = ship_y + buffer_y

Uzyskuję miarę kąta położenia statku wyrażoną w radianach. Następnie obliczam


współrzędne początkowe pocisku, x i y, na podstawie kąta położenia statku i wartości
stałej Missile.BUFFER. Uzyskane wartości x i y umieszczają pocisk dokładnie przed
działem statku.
Następnie obliczam składowe prędkości pocisku. Stosuję ten sam typ obliczeń,
którego użyłem w klasie Ship:
# oblicz składowe prędkości pocisku
dx = Missile.VELOCITY_FACTOR * math.sin(angle)
dy = Missile.VELOCITY_FACTOR * -math.cos(angle)

Wywołuję konstruktor klasy Sprite na rzecz bieżącego obiektu:


# utwórz pocisk
super(Missile, self).__init__(image = Missile.image,
x = x, y = y,
dx = dx, dy = dy)

W końcu dodaję do obiektu klasy Missile atrybut lifetime, żeby obiekt


nie pojawiał się na ekranie bez końca.
self.lifetime = Missile.LIFETIME

Metoda update()
Następnie piszę kod metody update(). Oto jego pierwsza część:
def update(self):
""" obsługuj ruch pocisku. """
# jeśli wyczerpał się czas życia pocisku, zniszcz go
self.lifetime -= 1
if self.lifetime == 0:
self.destroy()
388 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Powyższy kod odlicza czas życia pocisku. Zmniejszana jest wartość atrybutu
lifetime. Kiedy osiągnie 0, obiekt klasy Missile dokonuje samozniszczenia.
W drugiej części metody update() zawarłem znajomy kod przenoszący pocisk
na przeciwległy brzeg ekranu:
# przenieś pocisk na przeciwległy brzeg ekranu
if self.top > games.screen.height:
self.bottom = 0

if self.bottom < 0:
self.top = games.screen.height

if self.left > games.screen.width:


self.right = 0

if self.right < 0:
self.left = games.screen.width

Widzę, że powyższy kod został już w moim programie trzy razy powtórzony.
Zdecydowanie będę go później konsolidował.

Regulowanie tempa wystrzeliwania pocisków


Jak widziałeś w poprzednim programie, statek może wystrzelić około 50 pocisków
w ciągu sekundy. Nawet dla gracza, który chce wygrać, jest to trochę za dużo.
Więc w tej kolejnej wersji gry nakładam ograniczenie na tempo wystrzeliwania pocisków.

Program Astrocrash05
Program Astrocrash05 ogranicza tempo wystrzeliwania pocisków poprzez utworzenie
mechanizmu odliczania, który wymusza zwłokę pomiędzy wystrzałami. Kiedy
odliczanie się kończy, gracz może wystrzelić kolejny pocisk. Program został
zilustrowany na rysunku 12.12.
Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash05.py.

Dodanie stałej klasowej do klasy Ship


Moim pierwszym krokiem w wymuszeniu zwłoki między wystrzeliwaniem pocisków
jest dodanie do klasy Ship stałej klasowej:
MISSILE_DELAY = 25

Stała MISSILE_DELAY reprezentuje czas zwłoki, jaki gracz musi odczekiwać między
wystrzeliwaniem pocisków. Wykorzystuję ją do ponownego ustawiania odliczania,
które zmusza gracza do czekania.
Regulowanie tempa wystrzeliwania pocisków 389

Rysunek 12.12. Teraz statek wystrzeliwuje pociski w rozsądniejszym tempie

Tworzenie konstruktora klasy Ship


Następnie tworzę konstruktor dla klasy:
def __init__(self, x, y):
""" Inicjalizuj duszka statku. """
super(Ship, self).__init__(image = Ship.image, x = x, y = y)
self.missile_wait = 0

Metoda przyjmuje wartości reprezentujące współrzędne x i y nowego statku


i przekazuje je dalej do klasy nadrzędnej klasy Ship, games.Sprite. W kolejnym wierszu
dodaję do nowego obiektu atrybut o nazwie missile_wait. Wykorzystuję go
do odliczania czasu zwłoki, zanim gracz będzie mógł wystrzelić kolejny pocisk.

Modyfikacja metody update() klasy Ship


Dodaję do metody update() klasy Ship kod, który zmniejsza wartość atrybutu
missile_wait obiektu w ramach odliczania do 0.
# jeśli czekasz, aż statek będzie mógł wystrzelić następny pocisk,
# zmniejsz czas oczekiwania
if self.missile_wait > 0:
self.missile_wait -= 1

Następnie zmieniam kod z poprzedniej wersji gry obsługujący wystrzeliwanie


pocisków na poniższe wiersze:
390 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

# wystrzel pocisk, jeśli klawisz spacji jest naciśnięty i skończył się


# czas oczekiwania
if games.keyboard.is_pressed(games.K_SPACE) and self.missile_wait == 0:
new_missile = Missile(self.x, self.y, self.angle)
games.screen.add(new_missile)
self.missile_wait = Ship.MISSILE_DELAY

Teraz, kiedy gracz naciśnie klawisz spacji, zanim statek wystrzeli nowy pocisk,
musi się zakończyć odliczanie (wartość missile_wait musi być równa 0). Natychmiast
po wystrzeleniu pocisku ustawiam atrybut missile_wait z powrotem na wartość
MISSILE_DELAY, aby rozpocząć na nowo odliczanie.

Obsługa kolizji
Jak dotąd gracz może przemieszczać statek po polu asteroid, a nawet wystrzeliwać
pociski, ale żaden z obiektów nie wchodzi w interakcję z innymi. Zmieniam ten stan
rzeczy w kolejnej wersji gry. Kiedy pocisk zderza się z dowolnym innym obiektem,
niszczy zarówno ten obiekt, jak i samego siebie. Ta sama zasada obowiązuje w przypadku
kolizji statku kosmicznego z innym obiektem. Asteroidy będą w tym układzie pasywne,
ponieważ nie chcę, aby zachodzące na siebie asteroidy niszczyły się wzajemnie.

Program Astrocrash06
Program Astrocrash06 realizuje całe niezbędne wykrywanie kolizji dzięki wykorzystaniu
właściwości overlapping_sprites klasy Sprite. Muszę też obsługiwać niszczenie asteroid
w specjalny sposób, ponieważ kiedy są niszczone asteroidy dużej i średniej wielkości,
tworzone są w miejsce każdej z nich dwie nowe, lecz mniejsze.

Pułapka
Ponieważ asteroidy są początkowo generowane w losowo wybranych miejscach,
istnieje możliwość, że któraś z nich zostanie utworzona na wierzchu statku
kosmicznego gracza, niszcząc statek już w momencie rozpoczęcia programu.
Muszę tymczasowo pogodzić się z tą niedogodnością, ale będę musiał
rozwiązać ten problem w ostatecznej wersji gry.

Program w działaniu został pokazany na rysunku 12.13.


Kod tego programu możesz znaleźć na stronie internetowej tej książki
(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash06.py.

Modyfikacja metody update() klasy Missile


Na końcu metody update() klasy Missile dodaję następujący kod:
# sprawdź, czy pocisk zachodzi na jakiś inny obiekt
if self.overlapping_sprites:
Obsługa kolizji 391

Rysunek 12.13. Teraz pociski statku kosmicznego niszczą asteroidy,


ale uważaj — asteroidy mogą zniszczyć statek

for sprite in self.overlapping_sprites:


sprite.die()
self.die()

Jeśli pocisk zachodzi na jakieś inne obiekty, zarówno w kontekście tych innych
obiektów, jak i pocisku, jest wywoływana metoda die(). Jest to nowa metoda, którą
dodam do klas Asteroid, Ship i Missile.

Dodanie metody die() do klasy Missile


W klasie Missile, podobnie jak w każdej innej klasie w tej wersji gry, jest potrzebna
metoda die(). Metoda jest prawie tak prosta, jak tylko to możliwe:
def die(self):
""" Zniszcz pocisk. """
self.destroy()

Kiedy zostaje wywołana metoda die() obiektu klasy Missile, obiekt sam się niszczy.
392 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Modyfikacja metody update() klasy Ship


Na końcu metody update() klasy Ship dodaję następujący kod:
# sprawdź, czy statek nie zachodzi na jakiś inny obiekt
if self.overlapping_sprites:
for sprite in self.overlapping_sprites:
sprite.die()
self.die()

Jeśli statek zachodzi na jakieś inne obiekty, zarówno w kontekście tych innych
obiektów, jak i statku jest wywoływana metoda die(). Zwróć uwagę, że dokładnie taki
sam kod pojawia się również w metodzie update() klasy Missile. Jak już wcześniej
wspomniałem, kiedy widzisz zdublowany kod, powinieneś pomyśleć o tym, jak go
skonsolidować. W następnej wersji gry pozbędę się zarówno tego, jak i innych
fragmentów redundantnego kodu.

Dodanie metody die() do klasy Ship


Ta metoda jest identyczna jak metoda die() w klasie Missile:
def die(self):
""" Zniszcz statek. """
self.destroy()

Gdy zostaje wywołana metoda die() obiektu klasy Ship, obiekt sam się niszczy.

Dodanie stałej klasowej do klasy Asteroid


Do klasy Asteroid dodaję jedną stałą klasową:
SPAWN = 2

Stała SPAWN określa liczbę nowych asteroid, jakie powstają po zniszczeniu asteroidy
macierzystej.

Dodanie metody die() do klasy Asteroid


def die(self):
""" Zniszcz asteroidę. """
# jeśli nie jest to mała asteroida, zastąp ją dwoma mniejszymi
if self.size != Asteroid.SMALL:
for i in range(Asteroid.SPAWN):
new_asteroid = Asteroid(x = self.x,
y = self.y,
size = self.size - 1)
games.screen.add(new_asteroid)
self.destroy()
Dodanie efektów eksplozji 393

Utrudnienie, jakie dodaję w tym miejscu, polega na tym, że metoda die() klasy
Asteroid zawiera potencjał tworzenia nowych obiektów tej klasy. Metoda sprawdza,
czy niszczona asteroida nie należy do kategorii małych asteroid. Jeśli do niej nie należy,
zostają utworzone dwie nowe asteroidy, o jeden rozmiar mniejsze, w miejscu aktualnego
położenia asteroidy macierzystej. Czy nowe asteroidy zostały utworzone, czy też nie,
wcześniej istniejąca asteroida sama się niszczy i metoda się kończy.

Dodanie efektów eksplozji


W poprzedniej wersji gry gracz mógł niszczyć asteroidy, strzelając w nie pociskami,
lecz ich destrukcji brakowało nieco wyrazu. Więc w następnym kroku dodaję do gry
eksplozje.

Program Astrocrash07
W programie Astrocrash07 piszę nową klasę, opartą na klasie games.Animation,
obsługującą animowane eksplozje. Wykonuję też pewną pracę niewidoczną dla użytkownika,
konsolidując redundantny kod. Nawet jeśli gracz nie doceni tych dodatkowych zmian,
to i tak są one ważne. Na rysunku 12.14 pokazuję nowy program w akcji.

Rysunek 12.14. Teraz wszystkim przypadkom destrukcji w grze towarzyszą


potężne eksplozje
394 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash07.py.

Klasa Wrapper
Rozpoczynam od pracy na zapleczu. Tworzę nową klasę, Wrapper, opartą na klasie
games.Sprite.

Metoda update()
Klasa Wrapper zawiera metodę update(), która po przekroczeniu przez obiekt krawędzi
ekranu automatycznie przenosi go na krawędź przeciwległą, tak że jego tor ruchu jakby
„owija” ekran:
class Wrapper(games.Sprite):
""" Duszek, którego tor lotu owija się wokół ekranu. """
def update(self):
""" Przenieś duszka na przeciwległy brzeg ekranu. """
if self.top > games.screen.height:
self.bottom = 0

if self.bottom < 0:
self.top = games.screen.height

if self.left > games.screen.width:


self.right = 0

if self.right < 0:
self.left = games.screen.width

Powyższy kod widziałeś już kilkakrotnie. Powoduje owinięcie toru lotu duszka
wokół ekranu. Kiedy teraz oprę pozostałe klasy występujące w tej grze na klasie Wrapper,
jej metoda update() może utrzymywać instancje tych pozostałych klas w obrębie ekranu
— a kod może istnieć tylko w jednym miejscu!

Metoda die()
Kod klasy kończę metodą die(), która niszczy obiekt:
def die(self):
""" Zniszcz się. """
self.destroy()

Klasa Collider
Następnie biorę się za inny redundantny kod. Zauważyłem, że zarówno klasa Ship, jak
i Missile dzielą takie same instrukcje obsługujące kolizje, więc postanowiłem utworzyć
Dodanie efektów eksplozji 395

nową klasę, Collider (opartą na klasie Wrapper), reprezentującą obiekty, których tor lotu
owija się wokół ekranu i które mogą się zderzać z innymi obiektami.

Metoda update()
Oto metoda update() obsługująca kolizje:
def update(self):
""" Sprawdź, czy duszki nie zachodzą na siebie. """
super(Collider, self).update()

if self.overlapping_sprites:
for sprite in self.overlapping_sprites:
sprite.die()
self.die()

Moją pierwszą czynnością w metodzie update() klasy Collider jest wywołanie


metody update() jej nadklasy (czyli metody update() klasy Wrapper) w celu utrzymania
obiektu w obrębie ekranu. Potem sprawdzam, czy nie występują kolizje. Jeśli nasz obiekt
zachodzi na jakiekolwiek inne obiekty, wywołuje metodę die() tych innych obiektów,
a potem jego własną metodę die().

Metoda die()
Następnie tworzę metodę dla opisywanej klasy, ponieważ wszystkie obiekty klasy Collider
robią to samo, kiedy kończą swoje istnienie — tworzą eksplozję i niszczą się same.
def die(self):
""" Zniszcz się i pozostaw po sobie eksplozję. """
new_explosion = Explosion(x = self.x, y = self.y)
games.screen.add(new_explosion)
self.destroy()

W tej metodzie tworzę obiekt klasy Explosion. To nowa klasa, której obiektami są
animacje eksplozji. Wkrótce ujrzysz tę klasę w pełni jej blasku.

Modyfikacja klasy Asteroid


Modyfikuję klasę Asteroid w taki sposób, aby była oparta na klasie Wrapper:
class Asteroid(Wrapper):

Teraz klasa Asteroid dziedziczy metodę update() po klasie Wrapper i dlatego


wycinam jej własną metodę update(). Redundantny kod zaczyna znikać!
Jedyną rzeczą, jaką jeszcze robię w tej klasie, jest zmiana ostatniego wiersza jej
metody die(). Wywołanie self.die() zastępuję wierszem:
super(Asteroid, self).die()

Odtąd zawsze, kiedy zmienię metodę die() klasy Wrapper, klasa Asteroid
automatycznie zbierze wynikające z tego korzyści.
396 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Modyfikacja klasy Ship


Modyfikuję klasę Ship tak, aby była oparta na klasie Collider:
class Ship(Collider):

W metodzie update() klasy Ship dodaję wiersz:


super(Ship, self).update()

Mogę teraz wyciąć kilka dalszych fragmentów redundantnego kodu. Ponieważ kolizje
obsługuje metoda update() klasy Collider, wycinam kod wykrywania kolizji z metody
update() klasy Ship. A ponieważ metoda update() klasy Collider wywołuje metodę
update() klasy Wrapper, z metody update() klasy Ship wycinam także kod obsługujący
powracanie obiektu na ekran. Wycinam również z klasy Ship metodę die(), ponieważ
dziedziczy ją ona po klasie Collider.

Modyfikacja klasy Missile


W trakcie modyfikacji klasy Missile, zmieniam nagłówek klasy w taki sposób,
aby klasa była oparta na klasie Collider:
class Missile(Collider):

W metodzie update() klasy Missile dodaję wiersz


super(Missile, self).update()

Podobnie jak w przypadku klasy Ship, mogę teraz wyciąć z klasy Missile redundantny
kod. Ponieważ kolizje obsługuje metoda update() klasy Collider, wycinam kod
wykrywania kolizji z metody update() klasy Missile. A ponieważ metoda update() klasy
Collider wywołuje metodę update() klasy Wrapper, z metody update() klasy Missile
wycinam także kod obsługujący powracanie obiektu na ekran. Wycinam również z klasy
Missile metodę die(), ponieważ dziedziczy ją ona po klasie Collider.

Wskazówka
Aby pomóc sobie w zrozumieniu tych wszystkich zmian, jakie opisuję, zajrzyj do
kompletnego kodu wszystkich wersji programu Astrocrash zamieszczonego na
stronie internetowej tej książki www.courseptr.com/downloads.

Klasa Explosion
Ponieważ chcę tworzyć animowane eksplozje, napisałem klasę Explosion opartą na klasie
games.Animation.
class Explosion(games.Animation):
""" Animacja eksplozji. """
sound = games.load_sound("eksplozja.wav")
images = ["eksplozja1.bmp",
"eksplozja2.bmp",
Dodanie poziomów gry, rejestracji wyników oraz tematu muzycznego 397

"eksplozja3.bmp",
"eksplozja4.bmp",
"eksplozja5.bmp",
"eksplozja6.bmp",
"eksplozja7.bmp",
"eksplozja8.bmp",
"eksplozja9.bmp"]

Definiuję zmienną klasową sound reprezentującą efekt dźwiękowy towarzyszący


eksplozji. Definiuję także zmienną klasową images, przypisując do niej listę nazw plików
zawierających dziewięć ramek służących do animacji eksplozji.
Następnie piszę konstruktor klasy Explosion.
def __init__(self, x, y):
super(Explosion, self).__init__(images = Explosion.images,
x = x, y = y,
repeat_interval = 4, n_repeats = 1,
is_collideable = False)
Explosion.sound.play()
Konstruktor klasy Explosion przyjmuje wartości reprezentujące współrzędne ekranowe
eksplozji poprzez parametry x i y. Kiedy wywołuję konstruktor nadklasy (games.Animation),
przekazuję te wartości znów jako parametry x i y, aby animacja została odtworzona
dokładnie tam, gdzie chcę. Do konstruktora klasy nadrzędnej przekazuję również poprzez
parametr images listę nazw plików graficznych, Explosion.images. Parametrowi
n_repeats nadaję wartość 1, aby animacja była odtwarzana tylko raz. A parametrowi
repat_interval nadaję wartość 4, aby szybkość animacji była taka jak należy. Parametrowi
is_collideable nadaję wartość False, aby mogące się przydarzyć zachodzenie innych
duszków na animację eksplozji nie liczyło się jako kolizja.
Na koniec odtwarzam efekt dźwiękowy eksplozji za pomocą wywołania metody
Explosion.sound.play().

Sztuczka
Pamiętaj, że do konstruktora klasy games.Animation możesz przekazać albo listę
nazw plików, albo listę obiektów obrazu reprezentującą ramki animacji.

Dodanie poziomów gry, rejestracji wyników


oraz tematu muzycznego
Do gry trzeba dodać jeszcze kilka elementów, aby mogła być postrzegana jako
kompletna. Moim ostatnim posunięciem jest dodanie poziomów gry — co oznacza,
że wtedy, gdy gracz zniszczy wszystkie asteroidy znajdujące się na ekranie, pojawia się
nowa, liczniejsza grupa tych obiektów. Dodaję również funkcjonalność rejestracji
dorobku punktowego gracza oraz wzmagający napięcie temat muzyczny, aby było można
w pełni przeżywać grę.
398 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Program Astrocrash08
Oprócz poziomów gry, rejestracji zdobytych punktów i tematu muzycznego dodaję
trochę kodu, którego efekty mogą być mniej oczywiste dla gracza, lecz który ma pomimo
to istotne znaczenie dla kompletności programu. Na rysunku 12.15 pokazuję moją
ostateczną wersję tej gry.

Rysunek 12.15. Ostateczny szlif umożliwia kontynuowanie gry tak długo,


jak na to pozwalają umiejętności gracza

Kod tego programu możesz znaleźć na stronie internetowej tej książki


(http://www.helion.pl/ksiazki/pytdk3.htm), w folderze rozdziału 12.; nazwa pliku
to astrocrash08.py.

Import modułu color


Pierwszy dodatek jest dosyć prosty. Z pakietu livewires oprócz modułu games importuję
moduł color:
from livewires import games, color

Potrzebuję modułu color, aby komunikat „Koniec gry” mógł zostać wyświetlony
w ładnym, jaskrawoczerwonym kolorze.
Dodanie poziomów gry, rejestracji wyników oraz tematu muzycznego 399

Klasa Game
Pod koniec programu dodaję klasę Game — nową klasę, służącą do utworzenia obiektu
reprezentującego samą grę. Tworzenie obiektu mającego reprezentować grę może się
z początku wydawać nieco dziwnym pomysłem, ale nabierze ono sensu, gdy się nad tym
dłużej zastanowisz. Sama gra mogłaby z pewnością stanowić obiekt z takimi metodami
jak play(), służąca do rozpoczęcia gry, advance(), umożliwiająca podniesienie gry na
kolejny poziom, oraz end(), pozwalająca zakończyć grę.
Decyzja projektowa o reprezentowaniu gry przez obiekt ułatwia innym obiektom
przesyłanie do gry komunikatów. Na przykład w sytuacji, gdy na aktualnym poziomie
gry zostaje zniszczona ostatnia asteroida, mogłaby przesłać do gry komunikat z żądaniem
przejścia do następnego poziomu. Albo wtedy, gdy zostaje zniszczony statek, mógłby
przesłać do gry komunikat, że powinna się zakończyć.
Kiedy będę omawiał klasę Game, zapewne zauważysz, że duża część kodu zawartego
w funkcji main() została włączona do tej klasy.

Metoda __init__()
Pierwszą rzeczą, jaką robię w klasie Game, jest zdefiniowanie konstruktora:
class Game(object):
""" Sama gra. """
def __init__(self):
""" Inicjalizuj obiekt klasy Game. """
# ustaw poziom
self.level = 0

# załaduj dźwięk na podniesienie poziomu


self.sound = games.load_sound("poziom.wav")

# utwórz wynik punktowy


self.score = games.Text(value = 0,
size = 30,
color = color.white,
top = 5,
right = games.screen.width - 10,
is_collideable = False)
games.screen.add(self.score)

# utwórz statek kosmiczny gracza


self.ship = Ship(game = self,
x = games.screen.width/2,
y = games.screen.height/2)
games.screen.add(self.ship)

Atrybut level reprezentuje aktualny numer poziomu gry. Atrybut sound odpowiada
za efekt dźwiękowy towarzyszący podniesieniu poziomu gry. Atrybut score reprezentuje
wynik punktowy gry — to obiekt klasy Text, który ukazuje się w prawym górnym rogu
ekranu. Właściwość is_collideable tego obiektu ma wartość False, co oznacza, że wynik
400 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

nie występuje w żadnych kolizjach — więc statek gracza nie zderzy się z wynikiem i nie
dojdzie do eksplozji! W końcu ship to atrybut reprezentujący statek kosmiczny gracza.

Metoda play()
Następnie definiuję metodę play(), która rozpoczyna grę.
def play(self):
""" Przeprowadź grę. """
# rozpocznij odtwarzanie tematu muzycznego
games.music.load("temat.mid")
games.music.play(-1)

# załaduj i ustaw tło


nebula_image = games.load_image("mglawica.jpg")
games.screen.background = nebula_image

# przejdź do poziomu 1
self.advance()

# rozpocznij grę
games.screen.mainloop()

Metoda ta ładuje temat muzyczny i odtwarza go w niekończącej się pętli. Ładuje


obraz mgławicy i ustawia go jako tło. Następnie wywołuje własną metodę obiektu klasy
Game o nazwie advance(), która podnosi grę na kolejny poziom. (Wszystkiego o metodzie
advance() dowiesz się w następnym podpunkcie). Na koniec metoda play() wywołuje
metodę games.screen.mainloop(), aby całą grę wprowadzić w ruch!

Metoda advance()
Metoda advance() podnosi grę na kolejny poziom. Zwiększa numer poziomu, tworzy
nową falę asteroid, wyświetla krótko na ekranie numer poziomu oraz odtwarza dźwięk
obwieszczający zmianę poziomu gry.
Moja pierwsza czynność w tej metodzie jest dość prosta — zwiększam numer
poziomu:
def advance(self):
""" Przejdź do następnego poziomu gry. """
self.level += 1
Następnie przechodzę do najciekawszej części metody — utworzenia nowej fali
asteroid. Każdy poziom rozpoczyna się od liczby asteroid równej jego numerowi. Więc
pierwszy poziom rozpoczyna się od jednej asteroidy, drugi — od dwóch itd. Mimo że
utworzenie grupy asteroid jest proste, muszę uzyskać pewność, że żadna nowa asteroida
nie zostanie utworzona na wierzchu statku kosmicznego. Inaczej statek wybuchnie
w momencie rozpoczęcia nowego poziomu gry.
# wielkość obszaru ochronnego wokół statku przy tworzeniu asteroid
BUFFER = 150

# utwórz nowe asteroidy


Dodanie poziomów gry, rejestracji wyników oraz tematu muzycznego 401

for i in range(self.level):
# oblicz współrzędne x i y zapewniające minimum odległości od statku
# określ minimalną odległość wzdłuż osi x oraz wzdłuż osi y
x_min = random.randrange(BUFFER)
y_min = BUFFER - x_min

# wyznacz odległość wzdłuż osi x oraz wzdłuż osi y


# z zachowaniem odległości minimalnej
x_distance = random.randrange(x_min, games.screen.width - x_min)
y_distance = random.randrange(y_min, games.screen.height - y_min)

# oblicz położenie na podstawie odległości


x = self.ship.x + x_distance
y = self.ship.y + y_distance

# jeśli to konieczne, przeskocz między krawędziami ekranu


x %= games.screen.width
y %= games.screen.height

# utwórz asteroidę
new_asteroid = Asteroid(game = self,
x = x, y = y,
size = Asteroid.LARGE)
games.screen.add(new_asteroid)

Stała BUFFER reprezentuje wielkość bezpiecznej strefy, jaką chcę mieć wokół statku.
Następnie uruchamiam pętlę. W trakcie każdej iteracji tworzę nową asteroidę
w bezpiecznej odległości od statku.
Wartość zmiennej x_min wyznacza minimalną odległość miejsca utworzenia nowej
asteroidy od statku obliczoną wzdłuż osi x, podczas gdy zmienna y_min reprezentuje
minimalną odległość miejsca utworzenia nowej asteroidy od statku, obliczoną wzdłuż osi y.
Wprowadzam zmienność poprzez wykorzystanie modułu random, ale suma wartości
x_min i y_min zawsze jest równa stałej BUFFER.
Wartość zmiennej x_distance to odległość miejsca utworzenia nowej asteroidy
wzdłuż osi x. Jest losowo wybraną liczbą, która jednak daje pewność, że nowa asteroida
zostanie utworzona w odległości od statku co najmniej równej x_min. Natomiast zmienna
y_distance reprezentuje odległość miejsca utworzenia nowej asteroidy obliczoną wzdłuż
osi y. Jest losowo wybraną liczbą, która jednak daje pewność, że nowa asteroida zostanie
utworzona w odległości od statku co najmniej równej y_min.
Wartość zmiennej x to współrzędna x nowej asteroidy. Obliczam ją poprzez dodanie
liczby x_distance do wartości współrzędnej x statku kosmicznego. Potem upewniam się,
że wartość x nie usytuuje asteroidy poza ekranem, wymuszając „obieganie” ekranu1 za
pomocą operatora modulo. Z kolei wartość zmiennej y to współrzędna y nowej asteroidy.
Obliczam ją poprzez dodanie liczby y_distance do wartości współrzędnej y statku
kosmicznego. Potem upewniam się, że wartość y nie umieszcza asteroidy poza ekranem,

1
Można sobie wyobrazić ekran jako powierzchnię walca powstałego przez sklejenie jego prawej i lewej
krawędzi — przyp. tłum.
402 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

wymuszając „obieganie” ekranu2 za pomocą operatora modulo. Następnie wykorzystuję


tak obliczone wartości x i y do utworzenia nowiuteńkiej asteroidy.
Zauważ, że w konstruktorze klasy Asteroid pojawił się nowy parametr, game. Zapamiętaj,
że ponieważ każda asteroida musi mieć możliwość wywołania metody obiektu klasy Game,
każdy obiekt klasy Asteroid musi zawierać referencję do obiektu klasy Game. Więc
przekazuję obiekt self do parametru game, który zostanie wykorzystany przez
konstruktor klasy Asteroid do zdefiniowania atrybutu reprezentującego grę.
Moje ostatnie czynności w metodzie advance() to wyświetlenie nowego numeru
poziomu oraz odtworzenie dźwięku obwieszczającego podwyższenie poziomu gry:
# wyświetl numer poziomu
level_message = games.Message(value = "Poziom " + str(self.level),
size = 40,
color = color.yellow,
x = games.screen.width/2,
y = games.screen.width/10,
lifetime = 3 * games.screen.fps,
is_collideable = False)
games.screen.add(level_message)

# odtwórz dźwięk przejścia do nowego poziomu (nie dotyczy pierwszego poziomu)


if self.level > 1:
self.sound.play()

Metoda end()
Metoda end() wyświetla dużymi, czerwonymi literami komunikat „Koniec gry” na
środku ekranu przez mniej więcej pięć sekund. Potem gra się kończy i ekran graficzny
zostaje zamknięty.
def end(self):
""" Zakończ grę. """
# pokazuj komunikat 'Koniec gry' przez 5 sekund
end_message = games.Message(value = "Koniec gry",
size = 90,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 5 * games.screen.fps,
after_death = games.screen.quit,
is_collideable = False)
games.screen.add(end_message)

2
Tym razem można sobie wyobrazić ekran jako powierzchnię walca powstałego przez sklejenie jego
dolnej i górnej krawędzi — przyp. tłum.
Dodanie poziomów gry, rejestracji wyników oraz tematu muzycznego 403

Dodanie do klasy Asteroid zmiennej i stałej klasowej


Dokonuję w klasie Asteroid kilku zmian związanych z dodaniem poziomów gry
i rejestrowaniem wyniku. Dodaję stałą klasową POINTS:
POINTS = 30

Stała ta będzie odgrywać rolę wartości bazowej służącej do wyznaczenia liczby


punktów określających wartość asteroidy. Rzeczywista wartość punktowa będzie
modyfikowana zgodnie z wielkością asteroidy — mniejsze asteroidy będą warte więcej
punktów niż większe.
Aby móc zmieniać poziom gry, program musi rozpoznać moment, w którym
wszystkie asteroidy występujące na aktualnym poziomie gry zostały zniszczone. Dlatego
też śledzę i rejestruję całkowitą liczbę asteroid za pomocą nowej zmiennej klasowej,
total, którą definiuję w początkowej części kodu klasy:
total = 0

Modyfikacja konstruktora klasy Asteroid


W konstruktorze dodaję wiersz zwiększający wartość zmiennej Asteroid.total:
Asteroid.total += 1

Chcę teraz, aby każda asteroida miała możliwość przesyłania komunikatu do obiektu
klasy Game, więc dostarczam do każdego obiektu klasy Asteroid referencję do obiektu
klasy Game. W konstruktorze klasy Asteroid przyjmuję obiekt klasy Game poprzez
utworzenie nowego parametru:
def __init__(self, game, x, y, size):

Parametr game przyjmuje obiekt klasy Game, który następnie wykorzystuję


do utworzenia atrybutu w nowym obiekcie klasy Asteroid:
self.game = game

Tak więc każdy nowy obiekt klasy Asteroid ma atrybut game, który jest referencją do
samej gry. Dzięki temu atrybutowi obiekt klasy Asteroid może wywołać metodę obiektu
klasy Game, taką jak advance().

Modyfikacja metody die() klasy Asteroid


Wprowadzam kilka dodatków do metody die() w klasie Asteroid. Najpierw zmniejszam
wartość zmiennej Asteroid.total:
Asteroid.total -= 1

Następnie zwiększam wynik punktowy na podstawie wartości stałej Asteroid.POINTS


oraz rozmiaru asteroidy (mniejsze asteroidy są warte więcej punktów niż mniejsze). Chcę
mieć również pewność, że wynik będzie zawsze wyrównywany do prawej strony, więc
ustawiam na nowo właściwość right obiektu score na 10 pikseli od prawej krawędzi ekranu.
404 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

self.game.score.value += int(Asteroid.POINTS / self.size)


self.game.score.right = games.screen.width - 10

Kiedy tworzę każdą z dwóch nowych asteroid, muszę przekazać referencję do obiektu
klasy Game, czego dokonuję poprzez zmodyfikowanie pierwszego wiersza wywołania
konstruktora klasy Asteroid:
new_asteroid = Asteroid(game = self.game,

Pod koniec metody die() klasy Asteroid badam wartość zmiennej Asteroid.total,
aby sprawdzić, czy wszystkie asteroidy zostały zniszczone. Jeśli rzeczywiście tak jest,
ostatnia asteroida wywołuje metodę advance() obiektu klasy Game, która przenosi grę
na następny poziom oraz tworzy nową grupę asteroid.
# jeśli wszystkie asteroidy zostały zniszczone, przejdź do następnego poziomu
if Asteroid.total == 0:
self.game.advance()

Dodanie stałej klasowej do klasy Ship


Wprowadzam kilka dodatków do klasy Ship. Tworzę stałą klasową VELOCITY_MAX, którą
wykorzystuję do ograniczenia maksymalnej prędkości statku kosmicznego gracza:
VELOCITY_MAX = 3

Modyfikacja konstruktora klasy Ship


Podobnie jak obiekt klasy Asteroid, obiekt klasy Ship musi mieć dostęp do obiektu klasy
Game, aby mógł wywołać jego metodę. Tak jak to zrobiłem w przypadku klasy Asteroid,
modyfikuję konstruktor klasy Ship:
def __init__(self, game, x, y):

Nowy parametr, game, przyjmuje jako swoją wartość obiekt klasy Game, której potem
używam do utworzenia atrybutu obiektu klasy Ship:
self.game = game
Więc każdy obiekt klasy Ship ma atrybut game, który jest referencją do samej gry.
Dzięki temu atrybutowi obiekt klasy Ship może wywołać metodę obiektu klasy Game,
taką jak end().

Modyfikacja metody update() klasy Ship


W metodzie update() klasy Ship, ograniczam poszczególne składowe prędkości obiektu
tej klasy, dx i dy, wykorzystując stałą klasową VELOCITY_MAX:
# ogranicz prędkość w każdym kierunku
self.dx = min(max(self.dx, -Ship.VELOCITY_MAX), Ship.VELOCITY_MAX)
self.dy = min(max(self.dy, -Ship.VELOCITY_MAX), Ship.VELOCITY_MAX)
Podsumowanie 405

Powyższy kod daje pewność, że dx i dy nigdy nie będą miały wartości mniejszej niż -
Ship.VELOCITY_MAX oraz większej niż Ship.VELOCITY_MAX. Aby to osiągnąć, skorzystałem
z funkcji min() i max(). Funkcja min() zwraca wartość minimalną dwóch liczb, podczas
gdy funkcja max() zwraca wartość maksymalną dwóch liczb. Ograniczam prędkość
statku, aby uniknąć potencjalnych problemów, łącznie z wpadaniem statku na swoje
własne pociski.

Dodanie do klasy Ship metody die()


Kiedy statek gracza zostaje zniszczony, gra się kończy. Dodaję do klasy Ship metodę
die(), która wywołuje metodę end() obiektu klasy Game w celu zakończenia gry.
def die(self):
""" Zniszcz statek i zakończ grę. """
self.game.end()
super(Ship, self).die()

Funkcja main()
Teraz, skoro mam klasę Game, funkcja main() staje się całkiem krótka. Wszystko, co mam
w tej funkcji do zrobienia, to utworzenie obiektu klasy Game oraz wywołanie metody
play() tego obiektu, aby uruchomić grę.
def main():
astrocrash = Game()
astrocrash.play()

# wystartuj!
main()

Podsumowanie
W tym rozdziale rozszerzyłeś swoją wiedzę o programowaniu multimedialnym na obszar
dźwięku, muzyki i animacji. Dowiedziałeś się, jak ładować i odtwarzać pliki dźwiękowe
i muzyczne oraz jak zatrzymywać ich odtwarzanie. Zobaczyłeś również, jak tworzy się
animacje. Nauczyłeś się również techniki tworzenia dużych programów poprzez pisanie
coraz bardziej kompletnych, roboczych wersji finalnego produktu. Zobaczyłeś, jak
można każdorazowo stawiać sobie jeden nowy cel do realizacji, budując w ten sposób
swoją drogę do pełnego programu. Na koniec zobaczyłeś, jak wszystkie te nowe
informacje i techniki zostały wykorzystane przy tworzeniu rozgrywanej w szybkim
tempie gry akcji z efektami dźwiękowymi, animacją i własną muzyką.
406 Rozdział 12. Dźwięk, animacja i rozwijanie programu. Gra Astrocrash

Sprawdź swoje umiejętności


1. Ulepsz grę Astrocrash poprzez utworzenie nowego rodzaju śmiercionośnego
gruzu kosmicznego. Nadaj temu nowemu typowi kosmicznych śmieci pewną
cechę, która odróżni je od asteroid. Na przykład zniszczenie takiego obiektu
mogłoby wymagać dwóch uderzeń pocisku.
2. Napisz wersję gry Simon Says, w której gracz ma za zadanie powtarzanie coraz
bardziej skomplikowanych, przypadkowych sekwencji kolorów i dźwięków
przy użyciu klawiatury.
3. Napisz własną wersję innej klasycznej gry wideo, takiej jak Space Invaders
lub Pac-Man.
4. Wykreuj swoje własne programistyczne wyzwanie, lecz co najważniejsze,
nigdy nie rezygnuj z mobilizowania się do dalszej nauki.
A
Opis pakietu livewires

S trona internetowa tej książki jest dostępna pod adresem


http://www.helion.pl/ksiazki/pytdk3.htm. Możesz z niej pobrać cały kod źródłowy,
pliki pomocnicze oraz pakiety oprogramowania opisane w tym tekście.

Pliki archiwów
Dostępne są dwa pliki do pobrania:
 py3e_source.zip — zawiera kod źródłowy i pliki pomocnicze do każdego
kompletnego programu zaprezentowanego w tej książce;
 py3e_software.zip — zawiera pliki wszystkich pakietów oprogramowania
opisanych w tej książce, łącznie z instalatorem Pythona 3.1.1 dla systemu
Windows.
Tabela A.1 opisuje zawartość archiwum py3e_source.zip, podczas gdy tabela A.2
wymienia szczegółowo zawartość archiwum py3e_software.zip.

Tabela A.1. Zawartość archiwum py3e_source.zip


Nazwa folderu Opis
rozdzial01 Kod źródłowy do rozdziału 1.
rozdzial02 Kod źródłowy do rozdziału 2.
rozdzial03 Kod źródłowy do rozdziału 3.
rozdzial04 Kod źródłowy do rozdziału 4.
rozdzial05 Kod źródłowy do rozdziału 5.
rozdzial06 Kod źródłowy do rozdziału 6.
rozdzial07 Kod źródłowy i pliki z danymi do rozdziału 7.
rozdzial08 Kod źródłowy do rozdziału 8.
rozdzial09 Kod źródłowy do rozdziału 9.
408 Dodatek A. Strona internetowa książki

Tabela A.1. Zawartość archiwum py3e_source.zip (ciąg dalszy)


Nazwa folderu Opis
rozdzial10 Pliki z kodem źródłowym i odpowiadające im pliki wsadowe Windows
do rozdziału 10.
rozdzial11 Pliki z kodem źródłowym, odpowiadające im pliki wsadowe Windows
oraz pliki multimedialne do rozdziału 11.
rozdzial12 Pliki z kodem źródłowym, odpowiadające im pliki wsadowe Windows
oraz pliki multimedialne do rozdziału 12.

Tabela A.2. Zawartość archiwum py3e_software.zip


Nazwa folderu Opis
python Instalator Windows Pythona 3.1.1.
pygame Instalator Windows pakietu pygame 1.9.1 do Pythona 3.1.x.
livewires Pakiet silnika gier livewires.

Wskazówka
Instalator Windows pakietu pygame zawarty w pliku py3e_software.zip jest
kompatybilny z Pythonem 3.1.x, co oznacza zgodność z każdą podwersją
Pythona 3.1 (od Pythona 3.1.0 do Pythona 3.1.9).
B
Opis pakietu livewires

T en dodatek zawiera prawie wszystko, co chciałbyś wiedzieć o zmodyfikowanej wersji


pakietu livewires, a o co bałbyś się zapytać. Opuszczam pewne fragmenty dla uproszczenia
— jeśli potrzebna Ci jest maksymalnie dokładna „dokumentacja”, zawsze możesz zajrzeć
do samego kodu źródłowego modułów pakietu.

Pakiet livewires
Tabela B.1. Moduły pakietu livewires

Moduł Opis
games Definiuje funkcje i klasy, które ułatwiają tworzenie gier.
color Przechowuje zestaw stałych reprezentujących kolory.

Klasy modułu games


Moduł games zawiera grupę klas i funkcji pomocnych przy programowaniu gier.
Tabela B.2 zawiera opis klas.

Tabela B.2. Klasy modułu games

Klasa Opis
Screen Obiekt tej klasy reprezentuje ekran graficzny.
Sprite Obiekt tej klasy zawiera obraz oraz może zostać wyświetlony na ekranie
graficznym.
Text Obiekt tej klasy reprezentuje tekst na ekranie graficznym. Text to podklasa
klasy Sprite.
Message Obiekt tej klasy reprezentuje komunikat wyświetlany na ekranie graficznym;
komunikat ten znika po ustalonym czasie. Message jest podklasą klasy Text.
410 Dodatek B. Opis pakietu livewires

Tabela B.2. Klasy modułu games (ciąg dalszy)


Klasa Opis
Animation Obiekt tej klasy reprezentuje serię obrazów pokazywanych na ekranie
graficznym jeden po drugim. Animation to podklasa klasy Sprite.
Mouse Obiekt tej klasy daje dostęp do myszy.
Keyboard Obiekt tej klasy daje dostęp do klawiatury.
Music Obiekt tej klasy daje dostęp do kanału muzycznego.

Klasa Screen
Obiekt klasy Screen reprezentuje ekran graficzny. Funkcja games.init() tworzy obiekt
klasy Screen o nazwie screen, który reprezentuje ekran graficzny. Generalnie powinieneś
używać obiektu screen, zamiast konkretyzować własny obiekt klasy Screen. W tabeli B.3
zostały opisane właściwości klasy Screen, podczas gdy tabela B.4 wyszczególnia metody
tej klasy.

Tabela B.3. Właściwości klasy Screen


Właściwość Opis
width Szerokość ekranu.
height Wysokość ekranu.
fps Liczba wskazująca, ile razy na sekundę ekran jest aktualizowany.
background Obraz tła ekranu.
all_objects Lista wszystkich duszków na ekranie.
event_grab Wartość typu Boolean, która ustala, czy dane wejściowe są przechwytywane
na ekran. True oznacza przechwytywanie danych wejściowych, False —
brak przechwytywania.

Tabela B.4. Metody klasy Screen


Metoda Opis
get_width() Zwraca szerokość ekranu.
get_height() Zwraca wysokość ekranu.
get_fps() Zwraca liczbę, która wskazuje, ile razy na sekundę ekran jest
aktualizowany.
get_background() Zwraca obraz tła ekranu.
set_background Ustawia new_background jako obraz tła ekranu.
(new_background)
get_all_objects() Zwraca listę wszystkich duszków na ekranie.
get_event_grab() Zwraca stan przechwytywania danych wejściowych przez ekran.
True jeśli dane wejściowe są przechwytywane. False jeśli nie są
przechwytywane.
Klasy modułu games 411

Tabela B.4. Metody klasy Screen (ciąg dalszy)


Metoda Opis
set_event_grab Ustawia stan przechwytywania danych wejściowych przez ekran
(new_status) na new_status. Wartość True oznacza, że dane wejściowe mają
być przechwytywane, a wartość False wyłącza przechwytywanie
danych wejściowych.
add(sprite) Dodaje sprite, obiekt klasy Sprite (lub obiekt podklasy klasy
Sprite) do ekranu graficznego.
remove(sprite) Usuwa sprite, obiekt klasy Sprite (lub obiekt podklasy klasy
Sprite) z ekranu graficznego.
clear() Usuwa wszystkie duszki z ekranu graficznego.
mainloop() Uruchamia główną pętlę ekranu graficznego.
quit() Zamyka okno graficzne.

Klasa Sprite
Obiekt klasy Sprite ma obraz i może być wyświetlany na ekranie graficznym. W tabeli B.5
zostały opisane właściwości klasy Sprite, podczas gdy w tabeli B.6 wyszczególniono
metody tej klasy.

Tabela B.5. Właściwości klasy Sprite


Właściwość Opis
image Obiekt obrazu duszka.
width Szerokość obrazu duszka.
height Wysokość obrazu duszka.
angle Położenie kątowe w stopniach.
x Współrzędna x.
y Współrzędna y.
position Pozycja duszka. Dwuelementowa krotka, która zawiera
współrzędną x oraz współrzędną y obiektu.
top Współrzędna y górnego brzegu duszka.
bottom Współrzędna y dolnego brzegu duszka.
left Współrzędna x lewego brzegu duszka.
right Współrzędna x prawego brzegu duszka.
dx Prędkość wzdłuż osi x.
dy Prędkość wzdłuż osi y.
velocity Prędkość duszka. Dwuelementowa krotka, która zawiera prędkość
obiektu wzdłuż osi x oraz prędkość obiektu wzdłuż osi y.
overlapping_sprites Lista innych obiektów, które zachodzą na duszka.
412 Dodatek B. Opis pakietu livewires

Tabela B.5. Właściwości klasy Sprite (ciąg dalszy)


Właściwość Opis
is_collideable Ustala, czy duszek może, czy nie może wchodzić w kolizje.
Wartość True oznacza, że ewentualne kolizje duszka są
rejestrowane, a wartość False oznacza, że duszek nie może
pojawić się w kolizjach.
interval Ustala interwał metody tick() obiektu.

Tabela B.6. Metody klasy Sprite


Metoda Opis
__init__(image [, angle] Inicjalizuje nowego duszka. Parametr image nie ma
[, x] [, y] [, top] [, wartości domyślnej, więc niezbędne jest jej przekazanie.
bottom] [, left] [, right] Parametry angle, x, y, dx i dy mają wartość domyślną 0.
[, dx] [, dy] [, interval] Parametry top, bottom, left i right mają wartość domyślną
[, is_collideable]) None. Jeśli do parametrów nie zostanie przekazana żadna
wartość, odpowiadającym im właściwościom nie zostanie
przypisana wartość przy inicjalizacji. Parametr interval ma
wartość domyślną 1. Parametr is_collideable ma wartość
domyślną True.
get_image() Zwraca obiekt obrazu duszka.
set_image(new_image) Ustawia obiekt obrazu duszka na new_image.
get_height() Zwraca wysokość obrazu duszka.
get_width() Zwraca szerokość obrazu duszka.
get_angle() Zwraca aktualny kąt położenia duszka w stopniach.
set_angle(new_angle) Ustawia kąt położenia duszka na new_angle.
get_x() Zwraca współrzędną x duszka.
set_x(new_x) Ustawia współrzędną x duszka na new_x.
get_y() Zwraca współrzędną y duszka.
set_y(new_y) Ustawia współrzędną y duszka na new_y.
get_position() Zwraca współrzędne x i y duszka jako dwuelementową
krotkę.
set_position(new_position) Ustawia współrzędne x i y duszka zgodnie
z dwuelementową krotką new_position.
get_top() Zwraca współrzędną y górnego brzegu duszka.
set_top(new_top) Ustawia współrzędną y górnego brzegu duszka na new_top.
get_bottom() Zwraca współrzędną y dolnego brzegu duszka.
set_bottom(new_bottom) Ustawia współrzędną y dolnego brzegu duszka na
new_bottom.
get_left() Zwraca współrzędną x lewego brzegu obiektu.
set_left(new_left) Ustawia współrzędną x lewego brzegu duszka na new_left.
get_right() Zwraca współrzędną x prawego brzegu obiektu.
Klasy modułu games 413

Tabela B.6. Metody klasy Sprite (ciąg dalszy)


Metoda Opis
set_right(new_right) Ustawia współrzędną x prawego brzegu duszka na new_right.
get_dx() Zwraca prędkość duszka w kierunku osi x.
set_dx(new_dx) Ustawia prędkość duszka w kierunku osi x na new_dx.
get_dy() Zwraca prędkość duszka w kierunku osi y.
set_dy(new_dy) Ustawia prędkość duszka w kierunku osi y na new_dy.
get_velocity() Zwraca prędkość duszka w kierunku osi x oraz prędkość
duszka w kierunku osi y jako dwuelementową krotkę.
set_velocity(new_velocity) Ustawia prędkość duszka w kierunku osi x oraz prędkość
duszka w kierunku osi y zgodnie z dwuelementową krotką
new_velocity.
get_overlapping_sprites() Zwraca listę wszystkich duszków z właściwością
collideable ustawioną na True, które zachodzą na obiekt.
overlaps(other) Zwraca True, jeśli obiekt other zachodzi na obiekt self,
i False w przeciwnym wypadku.
get_is_collideable() Zwraca status duszka w odniesieniu do kolizji. Wartość
True oznacza, że ewentualne kolizje duszka są rejestrowane,
a wartość False oznacza, że duszek nie może pojawić się
w kolizjach.
set_is_collideable Ustawia status duszka w odniesieniu do kolizji na
(new_status) new_status. Wartość True oznacza, że ewentualne kolizje
duszka będą rejestrowane, a wartość False oznacza,
że duszek nie może pojawić się w kolizjach.
get_interval() Zwraca interwał metody tick() duszka.
set_interval(new_interval) Ustawia interwał metody tick() duszka na new_interval.
update() Aktualizuje duszka. Domyślnie nic nie robi. Wywoływana
automatycznie w każdym cyklu pętli mainloop(). Możesz
przesłonić tę metodę w podklasie klasy Sprite.
tick() Jest wykonywana co tyle cykli pętli mainloop(), ile wskazuje
właściwość interval obiektu. Domyślnie nic nie robi.
Możesz przesłonić tę metodę w podklasie klasy Sprite.
destroy() Usuwa duszka z ekranu.

Klasa Text
Text jest podklasą klasy Sprite. Obiekt klasy Text reprezentuje tekst na ekranie
graficznym. Oczywiście klasa Text dziedziczy atrybuty, właściwości i metody klasy
Sprite. W tabeli B.7 zostały opisane dodatkowe właściwości klasy Text, podczas gdy
w tabeli B.8 wyszczególniono dodatkowe metody tej klasy.
414 Dodatek B. Opis pakietu livewires

Tabela B.7. Właściwości klasy Text


Właściwość Opis
value Wartość wyświetlana w postaci tekstu.
size Rozmiar czcionki.
color Kolor tekstu. Może zostać ustawiony przy użyciu wartości z modułu color.

Klasa Text używa właściwości value, size i color do tworzenia obiektu graficznego
reprezentującego wyświetlany tekst.

Tabela B.8. Metody klasy Text


Metoda Opis
__init__(value, size, Inicjalizuje nowy obiekt. Parametr value to wartość, która
color [, angle] [, x] ma być wyświetlana jako tekst. Parametr size to rozmiar
[, y] [, top] [, bottom] czcionki. Parametr color to kolor tekstu. Parametry angle,
[, left] [, right] [, dx] x, y, dx i dy mają wartość domyślną 0. Parametry top,
[, dy] [, interval] bottom, left i right mają wartość domyślną None. Jeśli
[, is_collideable]) do parametrów nie zostanie przekazana żadna wartość,
odpowiadającym im właściwościom nie zostanie przypisana
wartość przy inicjalizacji. Parametr interval ma wartość
domyślną 1. Parametr is_collideable ma wartość
domyślną True.
get_value() Zwraca wartość wyświetlaną jako tekst.
set_value(new_value) Ustawia wartość wyświetlaną jako tekst na new_value.
get_size() Zwraca rozmiar czcionki tekstu.
set_size(new_size) Ustawia rozmiar czcionki tekstu na new_size.
get_color() Zwraca kolor tekstu.
set_color(new_color) Ustawia kolor tekstu na new_color. Można do tego użyć
wartości z modułu color.

Klasa Message
Message to podklasa klasy Text. Obiekt klasy Message reprezentuje komunikat na ekranie
graficznym, który znika po określonym czasie. Obiekt klasy Message może również
zawierać specyfikację zdarzenia, które ma wystąpić po zniknięciu obiektu.
Klasa Message dziedziczy atrybuty, właściwości i metody klasy Text. Obiekt klasy
Message ma jednak nowy atrybut, ustawiany poprzez parametr after_death — kod, jaki
ma zostać wykonany po zniknięciu obiektu, reprezentowany na przykład przez nazwę
funkcji lub metody. Jego wartość domyślna to None.
W klasie Message została zdefiniowana nowa metoda __init__(): __init__(value,
size, color [, angle] [, x] [, y] [, top] [, bottom] [, left] [, right] [, dx]
[, dy] [, lifetime] [, is_collideable] [, after_death]). Metoda ta inicjalizuje nowy
obiekt. Parametr value to wartość, która ma być wyświetlana jako tekst. Parametr size
Klasy modułu games 415

to rozmiar czcionki. Parametr color to kolor tekstu. Parametry angle, x, y, dx i dy mają


wartość domyślną 0. Parametry top, bottom, left i right mają wartość domyślną None.
Jeśli do parametrów nie zostanie przekazana żadna wartość, odpowiadającym im
właściwościom nie zostanie przypisana wartość przy inicjalizacji. Parametr lifetime
reprezentuje czas wyświetlania komunikatu na ekranie wyrażony w cyklach pętli
mainloop(), po upływie którego obiekt ulegnie samozniszczeniu. Jeśli nadasz
parametrowi wartość 0, obiekt nie podlega samolikwidacji. Parametr is_collideable
ma wartość domyślną True, a wartością domyślną parametru after_death jest None.

Wskazówka
Wartość parametru lifetime zostaje po prostu przypisana do właściwości
interval obiektu klasy Message. Obiekt klasy Message nie ma atrybutu ani
właściwości lifetime.

Klasa Animation
Klasa Animation jest podklasą klasy Sprite. Obiekt klasy Animation reprezentuje serię
obrazów wyświetlanych jeden po drugim. Klasa Animation dziedziczy atrybuty,
właściwości i metody klasy Sprite. W klasie Animation zostały zdefiniowane dodatkowe
atrybuty, opisane w tabeli B.9.

Tabela B.9. Atrybuty klasy Animation

Atrybut Opis
images Lista obiektów obrazu.
n_repeats Liczba wskazująca, ile razy powinien zostać powtórzony całkowity cykl
animacji. Ustawiany za pośrednictwem parametru n_repeats. Wartość
parametru równa 0 (domyślna) oznacza powtarzanie bez końca.

W klasie Animation została zdefiniowana nowa metoda __init__(): __init__(images


[, angle] [, x] [, y] [, top] [, bottom] [, left] [, right] [, dx] [, dy]
[, repeat_interval] [, n_repeats] [, is_collideable]). Metoda ta inicjalizuje nowy
obiekt. Poprzez parametr images mogą zostać przekazane albo obiekty obrazów, albo
nazwy plików w postaci łańcuchów znaków, na podstawie których zostaną utworzone
obiekty obrazów. Parametry angle, x, y, dx i dy mają wartość domyślną 0. Parametry top,
bottom, left i right mają wartość domyślną None. Jeśli do parametrów nie zostanie
przekazana żadna wartość, odpowiadającym im właściwościom nie zostanie przypisana
wartość przy inicjalizacji. Parametr repeat_interval ustala interwał metody tick()
obiektu, a więc tym samym szybkość animacji. Jego wartość domyślna to 1. Wartością
domyślną parametru n_repeats jest 0, a parametru is_collideable — True.
416 Dodatek B. Opis pakietu livewires

Wskazówka
Wartość parametru repeat_interval zostaje po prostu przypisana do właściwości
interval obiektu klasy Animation. Obiekt klasy Animation nie ma atrybutu ani
właściwości repeat_interval.

Klasa Mouse
Obiekt klasy Mouse obsługuje dostęp do myszy. Funkcja games.init() tworzy obiekt klasy
Mouse, dostępny poprzez zmienną mouse, w celu wykorzystania go przy odczytywaniu
pozycji myszy lub sprawdzaniu, czy przyciski myszy są naciśnięte. Ogólnie rzecz biorąc,
powinieneś używać obiektu mouse, zamiast konkretyzować swój własny obiekt na podstawie
klasy Mouse. W tabeli B.10 zostały opisane właściwości klasy Mouse, podczas gdy w tabeli B.11
wyszczególniono metody tej klasy.

Tabela B.10. Właściwości klasy Mouse


Właściwość Opis
x Współrzędna x wskaźnika myszy.
y Współrzędna y wskaźnika myszy.
position Pozycja wskaźnika myszy. Dwuelementowa krotka zawierająca
współrzędne x i y wskaźnika myszy.
is_visible Wartość typu Boolean dotycząca widoczności wskaźnika myszy. Wartość
True oznacza, że wskaźnik jest widoczny, a wartość False oznacza, że
wskaźnik nie jest widoczny. Wartością domyślną jest True.

Tabela B.11. Metody klasy Mouse


Metoda Opis
get_x() Zwraca współrzędną x wskaźnika myszy.
set_x(new_x) Ustawia współrzędną x wskaźnika myszy na new_x.
get_y() Zwraca współrzędną y wskaźnika myszy.
set_y(new_y) Ustawia współrzędną y wskaźnika myszy na new_y.
get_position() Zwraca współrzędne x i y wskaźnika myszy jako
dwuelementową krotkę.
set_position(new_position) Ustawia współrzędne x i y wskaźnika myszy zgodnie
z zawartością dwuelementowej krotki.
set_is_visible(new_visibility) Ustawia widoczność wskaźnika myszy. Jeśli wartością
parametru new_visibility jest True wskaźnik będzie
widoczny; jeśli wartość new_visibility wynosi False,
wskaźnik będzie niewidoczny.
is_pressed(button_number) Sprawdza, czy przycisk jest naciśnięty. Zwraca wartość
True, jeśli przycisk button_number jest naciśnięty;
w przeciwnym wypadku zwraca wartość False.
Funkcje modułu games 417

Klasa Keyboard
Obiekt klasy Keyboard obsługuje dostęp do klawiatury. Funkcja games.init() tworzy
obiekt klasy Keyboard, dostępny poprzez zmienną keyboard, w celu wykorzystania go
przy sprawdzaniu, czy określone klawisze są naciśnięte. Na ogół powinieneś używać obiektu
keyboard, zamiast konkretyzować swój własny obiekt na podstawie klasy Keyboard.
Klasa zawiera jedną metodę, is_pressed(key), która zwraca wartość True, jeśli
sprawdzany klawisz, key, jest naciśnięty, a False w sytuacji przeciwnej. W module games
zdefiniowano stałe reprezentujące klawisze, które możesz wykorzystać w roli argumentu
tej metody. Lista tych stałych została zaprezentowana w tym dodatku, w podrozdziale
„Stałe modułu games”

Klasa Music
Obiekt klasy Music umożliwia dostęp do pojedynczego kanału muzycznego, pozwalając
na ładowanie, odtwarzanie oraz zatrzymywanie odtwarzania pliku muzycznego. Na ogół
powinieneś wykorzystywać obiekt music, zamiast konkretyzować własny obiekt klasy Music.
Kanał muzyczny akceptuje wiele różnych typów plików, w tym WAV, MP3, OGG
oraz MIDI. Metody klasy Music zostały wymienione w tabeli B.12.

Tabela B.12. Metody klasy Music

Metoda Opis
load(filename) Ładuje plik filename do kanału muzycznego, zastępując nim
aktualnie załadowany plik muzyczny.
play([loop]) Odtwarza muzykę załadowaną do kanału muzycznego i dodatkowo
powtarza to odtwarzanie tyle razy, ile wskazuje parametr loop.
Wartość -1 oznacza powtarzanie bez końca. Wartość domyślna
parametru loop to 0.
fadeout(millisec) Stopniowo wycisza aktualnie odtwarzaną muzykę w ciągu millisec
milisekund.
stop() Zatrzymuje odtwarzanie muzyki na kanale muzycznym.

Funkcje modułu games


W module games zostały zdefiniowane funkcje służące do pracy z obrazami i dźwiękiem,
opisane w tabeli B.13.
418 Dodatek B. Opis pakietu livewires

Tabela B.13. Funkcje modułu games


Funkcja Opis
init([screen_width,] Inicjalizuje ekran graficzny o szerokości screen_width i wysokości
[screen_height,] screen_height, który jest aktualizowany fps razy na sekundę.
[fps]) Tworzy obiekt klasy games.Screen, który umożliwia dostęp do
ekranu gry. Ponadto po imporcie modułu games, zostają
utworzone: obiekt mouse klasy games.Mouse, który umożliwia
dostęp do myszy gracza, obiekt keyboard klasy games.Keyboard,
który umożliwia dostęp do klawiatury gracza, oraz obiekt music
klasy games.Music, który umożliwia dostęp do pojedynczego
kanału muzycznego.
load_image(filename Zwraca obiekt obrazu załadowany z pliku o nazwie podanej
[, transparent]) w postaci łańcucha znaków filename i ustawia przezroczystość
obrazu, jeśli wartością parametru transparent jest True.
Wartość domyślna parametru transparent to True.
scale_image(image, Zwraca nowy obiekt obrazu przeskalowany w kierunku osi x
x_scale [,y_scale]) współczynnikiem x_scale, a w kierunku osi y współczynnikiem
y_scale. Jeśli do y_scale nie zostanie przekazana żadna
wartość, obraz zostanie przeskalowany współczynnikiem
x_scale w obu kierunkach. Oryginalny obraz image pozostaje
niezmieniony.
load_sound(filename) Zwraca obiekt dźwiękowy utworzony na podstawie pliku WAV
o nazwie zawartej w łańcuchu filename.

Obiekt dźwiękowy zwrócony przez funkcję load_sound() ma do dyspozycji kilka


metod, które zostały wymienione w tabeli B.14.

Tabela B.14. Metody obiektu dźwiękowego


Metoda Opis
play([loop]) Odtwarza dźwięk tyle razy, ile wskazuje parametr loop, oprócz
początkowego odtworzenia. Wartość -1 oznacza powtarzanie
odtwarzania w nieskończoność. Wartość domyślna parametru
loop to 0.
fadeout(millisec) Stopniowo wycisza dźwięk w ciągu millisec milisekund.
stop() Zatrzymuje odtwarzanie dźwięku na wszystkich kanałach.

Stałe modułu games


W module games zdefiniowano listę stałych reprezentujących klawisze klawiatury
komputera. Kompletna ich lista została podana w tabeli B.15.
Stałe modułu games 419

Tabela B.15. Stałe modułu games reprezentujące klawisze


Stała Klawisz
K_BACKSPACE Backspace
K_TAB Tab
K_RETURN Enter
K_PAUSE Pause
K_ESCAPE Esc
K_SPACE Klawisz spacji
K_EXCLAIM Wykrzyknik
K_QUOTEDBL Cudzysłów podwójny
K_HASH Znak kratki (#)
K_DOLLAR Znak dolara
K_AMPERSAND Znak etki (&)
K_QUOTE Cudzysłów pojedynczy
K_LEFTPAREN Lewy nawias okrągły
K_RIGHTPAREN Prawy nawias okrągły
K_ASTERISK Asterysk (*)
K_PLUS Znak plusa
K_COMMA Przecinek
K_MINUS Znak minusa
K_PERIOD Kropka
K_SLASH Prawy ukośnik
K_0 0
K_1 1
K_2 2
K_3 3
K_4 4
K_5 5
K_6 6
K_7 7
K_8 8
K_9 9
K_COLON Dwukropek
K_SEMICOLON Średnik
K_LESS Znak mniejszości
K_EQUALS Znak równości
K_GREATER Znak większości
420 Dodatek B. Opis pakietu livewires

Tabela B.15. Stałe modułu games reprezentujące klawisze (ciąg dalszy)


Stała Klawisz
K_QUESTION Pytajnik
K_AT Znak „at” (@)
K_LEFTBRACKET Lewy nawias kwadratowy
K_BACKSLASH Lewy ukośnik
K_RIGHTBRACKET Prawy nawias kwadratowy
K_CARET Kareta (^)
K_UNDERSCORE Znak podkreślenia
K_a A
K_b B
K_c C
K_d D
K_e E
K_f F
K_g G
K_h H
K_i I
K_j J
K_k K
K_l L
K_m M
K_n N
K_o O
K_p P
K_q Q
K_r R
K_s S
K_t T
K_u U
K_v V
K_w W
K_x X
K_y Y
K_z Z
K_DELETE Delete
K_KP0 0 (klawiatura numeryczna)
Stałe modułu games 421

Tabela B.15. Stałe modułu games reprezentujące klawisze (ciąg dalszy)


Stała Klawisz
K_KP1 1 (klawiatura numeryczna)
K_KP2 2 (klawiatura numeryczna)
K_KP3 3 (klawiatura numeryczna)
K_KP4 4 (klawiatura numeryczna)
K_KP5 5 (klawiatura numeryczna)
K_KP6 6 (klawiatura numeryczna)
K_KP7 7 (klawiatura numeryczna)
K_KP8 8 (klawiatura numeryczna)
K_KP9 9 (klawiatura numeryczna)
K_KP_PERIOD Kropka (kl. num.)
K_KP_DEVIDE Znak dzielenia (kl. num.)
K_KP_MULTIPLY Znak mnożenia (kl. num.)
K_KP_MINUS Minus (kl. num.)
K_KP_PLUS Plus (kl. num.)
K_KP_ENTER Enter (kl. num.)
K_KP_EQUALS Znak równości (kl. num.)
K_UP Strzałka w górę
K_DOWN Strzałka w dół
K_RIGHT Strzałka w prawo
K_LEFT Strzałka w lewo
K_INSERT Insert
K_HOME Home
K_END End
K_PAGEUP Page Up
K_PAGEDOWN Page Down
K_F1 F1
K_F2 F2
K_F3 F3
K_F4 F4
K_F5 F5
K_F6 F6
K_F7 F7
K_F8 F8
K_F9 F9
K_F10 F10
422 Dodatek B. Opis pakietu livewires

Tabela B.15. Stałe modułu games reprezentujące klawisze (ciąg dalszy)


Stała Klawisz
K_F11 F11
K_F12 F12
K_NUMLOCK Num Lock
K_CAPSLOCK Caps Lock
K_SCROLLOCK Scroll Lock
K_RSHIFT Prawy Shift
K_LSHIFT Lewy Shift
K_RCTRL Prawy Ctrl
K_LCTRL Lewy Ctrl
K_RALT Prawy Alt
K_LALT Lewy Alt
K_RSUPER Prawy Windows
K_LSUPER Lewy Windows
K_HELP Help
K_PRINT Print Screen
K_BREAK Break

Stałe modułu color


Moduł color udostępnia pewne stałe, których możesz użyć wszędzie, gdzie moduł games
wymaga specyfikacji koloru. Te stałe są następujące:
 red
 green
 blue
 black
 white
 dark_red
 dark_green
 dark_blue
 dark_grey
 gray
 light_gray
 yellow
 brown
 pink
 purple
Skorowidz

A D do właściwości, 248
do zagnieżdżonych
abstrakcja, 171 DBMS, Database krotek, 148
aktualizacja Management System, 90 do zamarynowanych
zmiennej, 79 definiowanie obiektów, 213
algorytm, 93 funkcji, 170 do znaku łańcucha, 109
anagram, 129 klasy, 229 sekwencyjny, 107
animacja, 361, 368, 370 konstruktora, 301 swobodny, 107
argument, 23 metody, 229 duszek, sprite, 332, 335
nazwany, 176, 178 dekorator, 239 dziedziczenie, 263, 265
pozycyjny, 178 dodanie dzielenie
wyjątku, 218 obiektu do ekranu, 335, całkowite, 44
ASCII-Art, 36 338, 341 zmiennoprzecinkowe, 44
atrybut, 227, 232 pary klucz-wartość, 156 dzwonek systemowy, 39
atrybut klasy, 236, 238 dokumentowanie funkcji, dźwięk, 361, 371
Animation, 415 171
Message, 342 domyślne wartości E
atrybuty prywatne, 241 parametrów, 177, 179
dostęp ekran, 341, 344
B do atrybutów, 235, 238, ekran graficzny, 327, 331
245 element, 153
blok kodu, 70 do atrybutów elementy interfejsu GUI, 292
błąd, 23 prywatnych, 242 etykieta, 296, 298
błąd logiczny, 54, 55 do elementów
zagnieżdżonych, 146 F
C do elementu łańcucha,
110 fałsz, 85
ciało pętli, 78 do kodu źródłowego, 19 funkcja, 22
cudzysłów, 32 do metod prywatnych, __additional_cards(),
podwójny, 34 243 285
pojedynczy, 34 do pliku binarnego, 210 __init__(), 285, 354–357,
potrójny, 35 do pliku tekstowego, 203 379, 399
do wartości słownika, __pass_time(), 250
153 advance(), 400
424 Python dla każdego. Podstawy programowania

funkcja next_turn(), 195 gra


append(), 142 open_file, 220 Astrocrash, 361, 376
ask_number(), 189 pieces(), 189 Blackjack, 255, 277
ask_yes_no(), 189 play(), 251, 286 Jaka to liczba, 63, 95
capitalize(), 53 pop(), 144 Kółko i krzyżyk, 167,
change_global(), 184 print(), 22, 34, 35 185
check_catch(), 355 randint(), 66 Mad Lib, 289, 323
check_drop(), 358 random.randrange(), Pizza Panic, 323, 353
close(), 208 111 Szubienica, 133, 159
computer_move(), 192 randrange(), 66 Turniej wiedzy, 199,
congrat_winner, 195 range(), 102, 103 218
count(), 144 read_global(), 184 Wymieszane litery, 99,
create_widgets(), 318, 319 read(), 208 128
die(), 391–395, 403 readline(), 204, 208 graficzny interfejs
display(), 173 readlines(), 205, 208 użytkownika, GUI, 15,
display_board(), 190 remove(), 142 289, 300
display_instruct(), 188 replace(), 53 granice ekranu, 344
dump(), 212 reverse(), 144 grupowanie obiektów, 262
eat(), 250 shelve.open(), 212 GUI, Graphical User
end(), 402 sort(), 143 Interface, 16, 289, 300
end_game(), 356 strip(), 53 GUI toolkit, 291
get(), 155, 159 swapcase(), 53
human_move(), 192 talk(), 250 H
handle_caught(), 356 tell_story(), 321
index(), 144 title(), 53 hermetyzacja, 174
init(), 348 update(), 345, 354, hermetyzacja obiektów, 240
input(), 49, 60 380–395, 404
insert(), 144 upper(), 52 I
instructions(), 170 values(), 159
int(), 58 welcome(), 221 IDLE, 20
is_pressed(), 364 winner(), 191 import modułu, 65, 96, 276
items(), 159 write(), 207 indeksowanie
keys(), 159 writelines(), 207 krotek, 126
legal_moves(), 190 funkcje list, 137
len(), 106, 125, 137 konwersji typów, 58 łańcuchów, 107
load(), 212 modułu games, 417 inicjalizacja
lower(), 53 modułu pickle, 212 atrybutów, 234
main(), 195, 222, 287, ekranu graficznego, 327
359, 405 zmiennych, 163
G instalacja w systemie
mainloop(), 322, 349,
365 generowanie Windows, 19
new_board(), 189 błędu, 23 instrukcja, 23
next_block(), 221, 223 liczb losowych, 64 break, 87
next_line(), 220 losowej pozycji, 130 continue, 87
Skorowidz 425

if, 67, 68, 71 klasy operator in, 125


przypisania, 46 bazowe, 265, 270 wycinanie, 126
try, 215 modułu games, 327, 409 wypisywanie, 123
pochodne, 266, 272 zawierająca elementy,
J programu Blackjack, 122
279 kryptografia, 68
języki klauzula
obiektowe, 17 elif, 73, 75 L
wysokiego poziomu, 17 else, 71, 73, 218
except, 215 liczby
K klawisz, 419–422 całkowite, 44, 57
Enter, 61 losowe, 64
klasa, 227, 229 Tab, 38 pseudolosowe, 64
Animation, 370, 415 klient funkcji, 240 zmiennoprzecinkowe,
Application, 301 klucz, 110, 154 44
Asteroid, 379, 392, 403 kod, 23 liczenie
BJ_Card, 281 kod samodokumentujący, co pięć, 104
BJ_Dealer, 284 48 do przodu, 103
BJ_Deck, 281 kolizje, 350, 352, 390 do tyłu, 105
BJ_Game, 284 kombinacje obiektów, 259 lista, 133
BJ_Hand, 282 komentarz, 27, 59, 96 funkcja len(), 137
BJ_Player, 283 komunikat, 256, 339 indeksowanie, 137
Card, 259 konfiguracja konkatenacja, 137
Chef, 357 w innych systemach, 20 mutowalność, 138
Collider, 394 w systemie Windows, 19 operator in, 137
Critter, 249 konfigurowanie programu, przypisanie wartości
Explosion, 396 188 elementowi, 138
Game, 399 konkatenacja przypisanie wartości
Hand, 261 krotek, 127 wycinkowi, 139
Keyboard, 417 list, 137 usuwanie elementu, 139
Label, 312 łańcuchów, 41 usuwanie wycinka, 139
Message, 340, 414 konkretyzacja obiektu, 230 lista mailingowa, 18
Missile, 386, 390, 396 konstruktor, 230, 232, 249 literał, 23
Mouse, 416 konwersja
Music, 417 łańcuchów na liczby, 57 Ł
Pan, 354 wartości, 56
Pizza, 355 kończenie programu, 28, 97 ładowanie
Screen, 410 krotka, 99, 120 dźwięku, 372
Ship, 381–396, 404 funkcja len(), 125 muzyki, 374
Sprite, 333–336, 342, indeksowanie, 126 obrazu, 330, 333
367, 411 jako warunek, 122 łańcuch, 23, 28, 105–115
Text, 338, 413 konkatenacja, 127 łańcuch dokumentacyjny,
Wrapper, 394 niemutowalność, 127 171
426 Python dla każdego. Podstawy programowania

M N odmarynowanie, 211
odtwarzanie
marynowanie, 209, 210 nadklasa, superclass, 271 dźwięku, 373, 374
menedżer układu Grid, 305, nazwa zmiennej, 47 muzyki, 375
306 niemutowalność okno
metoda, Patrz funkcja krotek, 127 główne, root window, 293
metody, 229 łańcuchów, 111 graficzne, 325
instancji, 229, 230 notacja z kropką, 66 konsoli, 16
klasy bazowej, 271 numery pozycji Python Shell, 21
klasy Mouse, 416 dodatnie, 109 OOP, object-oriented
klasy Music, 417 ujemne, 110 programming, 17, 225
klasy Screen, 410 opcja Edit with IDLE, 27
klasy Sprite, 412, 413 O operacje na liczbach, 42
klasy Text, 414 operator
listy, 140, 144 obiekt, 230 in, 107, 125, 137, 154
łańcucha, 52, 53 klasy Card, 261 logiczny and, 91
obiektu dźwiękowego, klasy Ship, 382 logiczny not, 90
418 klasy Sprite, 342 logiczny or, 92
obiektu pliku, 208 klasy Text, 338 modulo, 44
obiektu screen, 328 modułu games, 327 operatory
prywatne, 241, 243 mouse, 350 matematyczne, 44
słownika, 159 screen, 328, 341 porównania, 69, 70
statyczne, 236, 239 obiekty operatory
moduł, 274 graficzne, 331 rozszerzonego
color, 338, 398, 422 programowe, 225 przypisania, 59
decimal, 45 obliczanie sekwencji, 105
games, 327, 353, liczby sekund, 61 otrzymywanie
418–422 wagi, 61 informacji, 173
karty, 277 obsługa wartości, 175
math, 382 danych wejściowych, 347 otwieranie pliku, 202
pakietu livewires, 409 kolizji, 352, 390
pickle, 211 wyjątków, 214–217
P
random, 65, 96 zdarzeń, 292, 303
shelve, 212 odbieranie komunikatów, pakiet
tkinter, 295, 297, 301, 256 livewires, 325, 347, 384,
318 odczyt klawiatury, 363 398, 407
modyfikowanie odczytywanie pygame, 325
metod, 269 danych, 200 para klucz-wartość, 156–158
okna głównego, 296 wartości zmiennej, 183 parametr, 173
mutowalność, 111 z pliku, 203, 211 lifetime, 415
mutowalność list, 138 z wiersza, 204 pozycyjny, 177
muzyka, 371, 374 zawartości pliku, 205 self, 230
mysza, 348
Skorowidz 427

pętla licznik_klikniec.py, 303 wybor_filmow.py, 311


for, 100–102 maitre_d.py, 84 wybor_filmow2.py, 315
nieskończona, 79 manipulacje_cytatami.py, wybredny_licznik.py, 86
nieskończona umyślna, 51 plik wygrales.py, 340
86 metkownica.py, 297 wymieszane_litery.py,
while, 76, 78, 129 najlepsze_wyniki.py, 128
zdarzeń, 292, 296, 298, 141 wysoki_wynik.py, 337
300 najlepsze_wyniki2.py, zabawne_podziekowania.
piksel, 327 147 py, 37
plik nieuchwytna_pizza.py, zadania_tekstowe.py, 43
analizator_komunikatow. 350 zwariowany_lancuch.py,
py, 105 nowe_okno_graficzne.py, 101
astrocrash01.py, 378 326 zwierzak_z_klasa.py, 237
astrocrash06.py, 390 obraz_tla.py, 329 zwierzak_
dlugowiecznosc.py, 306 obroc_duszka.py, 366 z_konstruktorem.py,
dostep_swobodny.py, odczytaj_klawisz.py, 363 231
108 odczytaj_to.txt, 200 zyczenia_urodzinowe.py,
duszek_pizzy.py, 332 opiekun_zwierzaka.py, 177
dzwiek_i_muzyka.py, 249 pliki
372 osobisty_pozdrawiacz.py, dźwiękowe, 361
eksplozja.py, 369 48 muzyczne, 361
fundusz_powierniczy_ patelnia_w_ruchu.py, tekstowe, 200, 209
zly.py, 54 347 z obrazami, 370
globalny_zasieg.py, 182 pizza_panic.py, 353 pobieranie
glupie_lancuchy.py, 41 pizza_w_ruchu.py, 343 danych, 48
gra_w_karty.py, 259 pobierz_i_zwroc.py, 172 wartości, 156
gry.py, 275 pozdrawiacz.py, 45 podświetlanie składni, 24
haslo.py, 67 przegrana_bitwa- pola wyboru, 310, 312, 314
instrukcja.py, 170 dobry.py, 82 polimorfizm, 273
inwentarz_bohatera.py, przegrana_bitwa-zly.py, ponowne wykorzystanie
121 80 kodu, 176
inwentarz_bohatera2.py, py3e_software.zip, 407 powielanie łańcuchów, 40,
124 py3e_source.zip, 407 42
inwentarz_bohatera3.py, rzut_koscmi.py, 65 powrót do początku pętli, 87
135 sprezysta_pizza.py, 345 poziomy gry, 397
karty.py, 277 szubienica.py, 160 półka, shelf, 212
komputer_nastrojow.py, translator_slangu.py, prawda, 85
74 153 prędkość, 343, 384
koniec_gry2.py, 33 trzylatek.py, 77 procedura obsługi zdarzeń,
krajacz_pizzy.py, 116 uczestnik_funduszu_ 292, 303
kwiz.txt, 219 powierniczego_ program
leniwe_przyciski2.py, dobry.py, 56 Analizator
301 udzielony_odmowiony. komunikatów, 105
licznik.py, 102 py, 71, 88 Astrocrash01, 377
428 Python dla każdego. Podstawy programowania

program Nowe okno graficzne, Zwariowany łańcuch,


Astrocrash02, 381 326 100
Astrocrash03, 382 Obraz tła, 329 Zwierzak z atrybutem,
Astrocrash04, 385 Obróć duszka, 366 233
Astrocrash05, 388 Obsłuż to, 214 Zwierzak z klasą, 237
Astrocrash06, 390 Odczytaj klawisz, 363 Zwierzak
Astrocrash07, 393 Odczytaj to, 200 z konstruktorem, 231
Astrocrash08, 398 Opiekun zwierzaka, 225, Zwierzak z właściwością,
Długowieczność, 305 249 245
Dostęp swobodny, 108 Osobisty pozdrawiacz, Życzenia urodzinowe,
Duszek pizzy, 332 48 177
Dźwięk i muzyka, 371 Patelnia w ruchu, 347 programowanie
Ekskluzywna sieć, 88 Pizza w ruchu, 342 obiektowe, OOP, 17,
Eksplozja, 368 Pobierz i zwróć, 172 225
Globalny zasięg, 182 Pogromca Obcych, 257 sterowane zdarzeniami,
Głupie łańcuchy, 40 Pozdrawiacz, 45 292
Gra w karty, 259 Prosta gra, 274 w trybie interaktywnym,
Gra w karty 2.0, 264 Prosty interfejs GUI, 21
Gra w karty 3.0, 269 293 w trybie skryptowym,
Hasło, 67 Prywatny zwierzak, 241 24
Instrukcja, 169 Przegrana bitwa, 80 projektowanie
Inwentarz bohatera, 120 Rzut kośćmi, 64 klas, 279
Inwentarz bohatera 2.0, Sprężysta pizza, 344 programów, 93
124 Symulator trzylatka, 77 prywatność obiektu, 244
Inwentarz bohatera 3.0, Translator przechwytywanie sygnałów
135 slangu komputerowego, wejściowych, 349
Komputer nastrojów, 73 152 przeciążanie operatora, 56
Koniec gry, 15 Turniej wiedzy, 199 przekazywanie wartości, 23
Kółko i krzyżyk, 194 Uczestnik funduszu przemieszczanie duszków,
Krajacz pizzy, 116 powierniczego, 54 342
Leniwe przyciski, 298 Udzielony-odmówiony, przesłanianie metod, 269,
Leniwe przyciski 2, 300 71 345
Licznik, 102 Wybór filmów, 311 przesłonięcie zmiennej
Licznik kliknięć, 303 program globalnej, 184
Mad Lib, 289, 318 Wybór filmów 2, 315 przesuwanie kursora, 38
Maitre D’, 83 Wybredny licznik, 86 przesyłanie komunikatu,
Manipulacje cytatami, Wygrałeś, 339 258
51 Wymieszane litery, 99 przycisk, 298, 299
Metkownica, 296 Wysoki wynik, 336 przyciski opcji, 315–317
Najlepsze wyniki, 140 Zabawne pseudokod, 93, 95, 186, 280
Najlepsze wyniki 2.0, podziękowania, 37 punkt tabulacji, 38
145 Zadania tekstowe, 42 puste krotki, 122
Nieistotne fakty, 31 Zamarynuj to, 209 pusty wiersz, 28
Nieuchwytna pizza, 350 Zapisz to, 206
Skorowidz 429

R struktura słownika, 159 komentarzy, 59, 96


sygnał konstruktora, 232
radian, 384 dzwonka systemowego, krotek, 120
ramka, frame, 297, 369 39 listy, 135
referencja do widżetu, 312 wejściowy, 349 łańcuchów, 32, 35, 52
referencje współdzielone, system zarządzania bazą menu, 251
145, 151 danych, 90 metod, 229
rejestracja wyników, 397 metod prywatnych, 243
reprezentowanie danych, 186 metod statycznych, 239
Ś
rodzaje cudzysłowu, 34 modułów, 273, 274
rozgałęzianie kodu, 63, 77 śledzenie programu, 81 nowego łańcucha, 113,
rozpakowanie sekwencji, środowisko 115, 130
147 programowania, 20 obiektu, 230, 302
rozszerzanie klasy, 263 okna głównego, 295
rozszerzanie klasy okna graficznego, 325
pochodnej, 266
T
pętli, 96, 102, 163
rozszerzenie tekst, 336 pól wyboru, 312
.bat, 294 testowanie stanu klawiszy, procedury obsługi
.py, 25 364 zdarzeń, 304
Tkinter, 293 przycisków, 299
S tło, 330 przycisków opcji, 316
tryb pustego łańcucha, 129
sekwencja niemutowalna, interaktywny, 21, 26 pustej krotki, 122
111 skryptowy, 24 ramki, 297
sekwencje tryby dostępu do pliku, 203, sekwencji
specjalne, 36, 40 210, 212 zagnieżdżonych, 146
zagnieżdżone, 145, 146 tworzenie skutku ubocznego, 193
składowe prędkości, 384 algorytmów, 93 słowników, 153
słownik, 133, 152, 159 anagramu, 129 stałych, 114, 160
sortowanie, 143 animacji, 368 warunków, 69
specyfikacja typu wyjątku, atrybutów prywatnych, widżetów, 302, 308
215 241 wielu obiektów, 232
sprawdzanie atrybutu klasy, 238 właściwości, 246
klucza, 154 duszka, 335 wycinków, 118, 137
wartości zmiennej, 79 etykiety, 298 zmiennych, 46
stała, 114, 160, 181 funkcji, 169 typ danych, 54
stała klasowa, 383, 388, 392, interfejsu GUI, 289, typy
403 291, 300 liczbowe, 44
stałe modułu klasy, 229, 259 sekwencji, 105
color, 422 klasy bazowej, 265, 270 wyjątków, 216
games, 418–422 klasy pochodnej, 345
stan kombinacji obiektów,
klawiszy, 364 259
pola wyboru, 314
430 Python dla każdego. Podstawy programowania

U W wstawianie
tekstu, 309
układ współrzędnych, 331 wartości znaku cudzysłowu, 38
umiejscowienie widżetu, mutowalne, 151 znaku nowego wiersza,
306 początkowe, 96 38
umieszczanie na półce, 212 zwrotne, 23, 172, 174 wycinanie
UML, Unified Modeling wartość krotek, 126
Language, 258 False, 82, 85 łańcuchów, 116
umyślne pętle None, 118 wycinek, 118
nieskończone, 86 True, 82, 85 wycinki list, 137
uruchamianie programu, 24 wartownik, 79 wyjątek, 214
ustawienie tła, 330 warunek, 69 wyjątek AttributeError, 242
usuwanie prosty, 88 wyjście z pętli, 87
elementu listy, 139 złożony, 88 wykrywanie kolizji, 350, 352
pary klucz-wartość, 158 wcięcie, 70 wypisywanie
wycinka listy, 139 wczytywanie wierszy, 205 krotki, 123
używanie wiązanie widżetów, 303 wartości, 34
cudzysłowów, 32 widoczność wskaźnika znaku lewego ukośnika,
domyślnych wartości myszy, 349 38
parametrów, 179 widżet, 296 wyrażenie, 44
etykiet, 296 Button, 298, 299 wysyłanie komunikatów,
klas pochodnych, 272 Entry, 305, 308 256
klucza, 154 Frame, 297 wyświetlanie
komentarza, 27 Label, 298 duszka, 332
używanie Text, 305, 308 komunikatu, 339
konstruktorów, 230 właściwości menu, 141
krotek, 123, 144 klasy Mouse, 416 obiektu, 236
list, 144 klasy Screen, 410 tekstu, 336
metod łańcucha, 50 klasy Sprite, 336, 411 wartości zmiennej, 60
parametrów zwrotnych, klasy Text, 339, 414 wyników, 142, 148
172 obiektu mouse, 350 wywołanie funkcji, 171
przycisków, 298 obiektu screen, 328 wywoływanie metody, 230
sekwencji specjalnych, właściwość, 245, 246 klasy bazowej, 271
36 angle, 367 statycznej, 240
sekwencji bottom, 346
zagnieżdżonych, 145 mood, 250 Z
słowników, 152 still_playing, 285
stałych, 185 wprowadzanie danych, 60 zagnieżdżanie wywołań
widżetów, 305 współrzędne funkcji, 58
zmiennych globalnych, myszy, 348 zakres, 181
181, 185 punktu, 331 zamykanie pliku, 202
znaku kontynuacji
wiersza, 42
Skorowidz 431

zapisywanie wartości zmiennej lewego ukośnika, 38


do pliku, 206, 210 globalnej, 184 nowego wiersza, 38
łańcuchów, 207 zmienna, 45, 46 tabulacji, 38
programu, 24 globalna, 181 zachęty, 21
zdarzenia, 292 lokalna, 181 Zunifikowany Język
złożone struktury danych, typu Boolean, 313 Modelowania, UML, 258
209 znak zwracanie
zmiana cudzysłowu, 38 informacji, 174
parametru kontynuacji wiersza, 42 wartości, 175
mutowalnego, 193 kratki, 27

You might also like