Programista samouk. Profesjonalny przewodnik do samodzielnej nauki kodowania 9788328339514 [PDF]


137 26 3MB

Polish Pages 256 [252] Year 2018

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Spis treści
Część I. Wprowadzenie do programowania
Rozdział 1. Wprowadzenie
Struktura książki
Zacznijmy od końca
Nie jesteś sam
Zalety samodzielnej nauki
Dlaczego należy programować?
Konsekwencja i zainteresowanie
Postać książki
Technologie stosowane w książce
Słownictwo
Wyzwanie
Rozdział 2. Zaczynamy
Czym jest programowanie
Czym jest Python
Instalowanie Pythona
Rozwiązywanie problemów
Interaktywna powłoka
Zapisywanie programów
Uruchamianie programów przykładowych
Słownictwo
Wyzwanie
Rozdział 3. Wprowadzenie do programowania
Przykłady
Komentarze
Wyświetlanie
Wiersze
Słowa kluczowe
Odstępy
Typy danych
Stałe i zmienne
Składnia
Błędy i wyjątki
Operatory arytmetyczne
Operatory porównania
Operatory logiczne
Instrukcje warunkowe
Instrukcje
Słownictwo
Wyzwania
Rozdział 4. Funkcje
Reprezentacja koncepcji
Funkcje
Definiowanie funkcji
Funkcje wbudowane
Wielokrotne stosowanie funkcji
Parametry wymagane i opcjonalne
Zasięg
Obsługa wyjątków
Łańcuchy dokumentujące
Używanie zmiennych tylko wtedy, gdy to konieczne
Słownictwo
Wyzwania
Rozdział 5. Kontenery
Metody
Listy
Krotki
Słowniki
Kontenery w kontenerach
Słownictwo
Wyzwania
Rozdział 6. Operacje na łańcuchach znaków
Potrójne łańcuchy
Indeksy
Łańcuchy znaków są niezmienne
Konkatencja
Powielanie łańcuchów znaków
Zmiana wielkości liter
Formatowanie
Dzielenie łańcuchów
Metoda join
Usuwanie odstępów
Zastępowanie
Znajdowanie indeksu
Metoda in
Zabezpieczanie znaków specjalnych
Znak nowego wiersza
Wycinki
Słownictwo
Wyzwania
Rozdział 7. Pętle
Pętle for
Funkcja range
Pętle while
Instrukcja break
Instrukcja continue
Pętle zagnieżdżone
Słownictwo
Wyzwania
Rozdział 8. Moduły
Moduły wbudowane
Importowanie innych modułów
Słownictwo
Wyzwania
Rozdział 9. Pliki
Zapisywanie danych w pliku
Automatyczne zamykanie plików
Odczyt z plików
Pliki CSV
Słownictwo
Wyzwania
Rozdział 10. Łączenie wszystkiego w całość
Wisielec
Wyzwania
Rozdział 11. Praktyka
Do przeczytania
Inne zasoby
Poszukiwanie pomocy
Część II. Wprowadzenie do programowania obiektowego
Rozdział 12. Paradygmaty programowania
Stan
Programowanie proceduralne
Paradygmat programowania funkcyjnego
Paradygmat programowania obiektowego
Słownictwo
Wyzwania
Rozdział 13. Cztery filary programowania obiektowego
Hermetyzacja
Abstrahowanie
Polimorfizm
Dziedziczenie
Kompozycja
Słownictwo
Wyzwania
Rozdział 14. Więcej o programowaniu obiektowym
Zmienne klasowe a zmienne instancyjne
Metody magiczne
Is
Słownictwo
Wyzwania
Rozdział 15. Łączenie wszystkiego w całość
Karty
Talia
Klasa gracza
Klasa gry
Wojna
Część III. Wprowadzenie do narzędzi programistycznych
Rozdział 16. Bash
Co dalej
Znajdowanie Bash
Polecenia
Ostatnie polecenia
Ścieżki względne i bezwzględne
Poruszanie się
Flagi
Pliki ukryte
Potoki
Zmienne środowiskowe
Użytkownicy
Dalsza nauka
Słownictwo
Wyzwania
Rozdział 17. Wyrażenia regularne
Konfiguracja
Proste dopasowania
Dopasowywanie początku i końca
Dopasowywanie różnych znaków
Dopasowywanie cyfr
Powtórzenia
Dosłowne traktowanie znaków
Narzędzia do tworzenia wyrażeń regularnych
Słownictwo
Wyzwania
Rozdział 18. Menedżery pakietów
Pakiety
Pip
Środowiska wirtualne
Słownictwo
Wyzwania
Rozdział 19. Kontrola wersji
Repozytoria
Rozpoczynanie projektu
Wypychanie i wciąganie zmian
Przykład wypychania
Przykład wciągania
Przywracanie wersji
diff
Dalsze kroki
Słownictwo
Wyzwania
Rozdział 20. Łączenie wszystkiego w całość
Kod HTML
Pozyskiwanie informacji z witryny Google Wiadomości
Słownictwo
Wyzwanie
Część IV. Wprowadzenie do informatyki
Rozdział 21. Struktury danych
Struktury danych
Stosy
Odwracanie łańcucha znaków przy użyciu stosu
Kolejki
Kolejka po bilety
Słownictwo
Wyzwania
Rozdział 22. Algorytmy
FizzBuzz
Wyszukiwanie sekwencyjne
Palindrom
Anagram
Zliczanie wystąpień liter
Rekurencja
Słownictwo
Wyzwania
Część V. Zdobywanie pracy
Rozdział 23. Najlepsze praktyki programistyczne
Pisz kod w ostateczności
Zasada DRY
Prostopadłość
Każdy element danych powinien mieć jedną reprezentację
Funkcje powinny robić tylko jedną rzecz
Jeśli to trwa zbyt długo, zapewne robimy coś źle
Wykonuj operacje w optymalny sposób już od samego początku
Zachowaj zgodność z konwencjami
Używaj dobrego IDE
Rejestracja
Testowanie
Przeglądanie kodu
Bezpieczeństwo
Słownictwo
Rozdział 24. Pierwsza praca w charakterze programisty
Określ ścieżkę
Zdobywanie początkowego doświadczenia
Przygotowania do rozmowy kwalifikacyjnej
Rozmowa kwalifikacyjna
Jak radzić sobie na rozmowie
Rozdział 25. Praca w zespole
Opanowanie podstaw
Nie pytaj o to, co możesz znaleźć w internecie
Modyfikowanie kodu
Syndrom oszusta
Rozdział 26. Dalsza lektura
Klasyka
Kursy internetowe
Hacker News
Rozdział 27. Dalsze kroki
Poszukaj mentora
Skacz na głęboką wodę
Kolejna rada
Podziękowania
Skorowidz

Programista samouk. Profesjonalny przewodnik do samodzielnej nauki kodowania
 9788328339514 [PDF]

  • Commentary
  • decrypted from 1FBE921E1C9C4443197807026BA1EF4F source file
  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

P R O G R A M I S TA samouk

Profesjonalny przewodnik do samodzielnej nauki kodowania

Tytuł oryginału: The Self-Taught Programmer: The Definitive Guide to Programming Professionally Tłumaczenie: Piotr Rajca ISBN: 978-83-283-3951-4 Copyright © 2017 by Cory Althoff All rights reserved. This book or any portion thereof may not be reproduced or used in any manner whatsoever without the express written permission of the publisher. Polish edition copyright © 2018 by Helion SA All rights reserved. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek)

Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/proprs_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

 Poleć książkę na Facebook.com  Kup w wersji papierowej  Oceń książkę

 Księgarnia internetowa  Lubię to! » Nasza społeczność

a9c8a50d59776690f264c310a5514dcc a

Tę książkę dedykuję moim rodzicom, Abby i Jamesowi Althoffom, za to, że bezustannie mnie wspierają.

a9c8a50d59776690f264c310a5514dcc a

a9c8a50d59776690f264c310a5514dcc a

Spis treści Część I. Wprowadzenie do programowania

13

Rozdział 1.

Rozdział 2.

Wprowadzenie

15

Struktura książki Zacznijmy od końca Nie jesteś sam Zalety samodzielnej nauki Dlaczego należy programować? Konsekwencja i zainteresowanie Postać książki Technologie stosowane w książce Słownictwo Wyzwanie

16 17 17 17 18 18 19 19 20 20

Zaczynamy

21

Czym jest programowanie Czym jest Python Instalowanie Pythona Rozwiązywanie problemów Interaktywna powłoka Zapisywanie programów Uruchamianie programów przykładowych Słownictwo Wyzwanie

21 22 22 23 23 24 25 25 26

5

a9c8a50d59776690f264c310a5514dcc a

6

Rozdział 3.

Rozdział 4.

Rozdział 5.

Spis treści

Wprowadzenie do programowania

27

Przykłady Komentarze Wyświetlanie Wiersze Słowa kluczowe Odstępy Typy danych Stałe i zmienne Składnia Błędy i wyjątki Operatory arytmetyczne Operatory porównania Operatory logiczne Instrukcje warunkowe Instrukcje Słownictwo Wyzwania

28 28 29 30 30 31 31 33 36 36 37 40 41 43 47 49 51

Funkcje

53

Reprezentacja koncepcji Funkcje Definiowanie funkcji Funkcje wbudowane Wielokrotne stosowanie funkcji Parametry wymagane i opcjonalne Zasięg Obsługa wyjątków Łańcuchy dokumentujące Używanie zmiennych tylko wtedy, gdy to konieczne Słownictwo Wyzwania

54 54 55 57 59 60 61 64 66 67 67 68

Kontenery

69

Metody Listy Krotki Słowniki Kontenery w kontenerach Słownictwo Wyzwania

69 70 73 75 79 81 82

a9c8a50d59776690f264c310a5514dcc a

7

Spis treści

Rozdział 6.

Rozdział 7.

Rozdział 8.

Operacje na łańcuchach znaków

83

Potrójne łańcuchy Indeksy Łańcuchy znaków są niezmienne Konkatencja Powielanie łańcuchów znaków Zmiana wielkości liter Formatowanie Dzielenie łańcuchów Metoda join Usuwanie odstępów Zastępowanie Znajdowanie indeksu Metoda in Zabezpieczanie znaków specjalnych Znak nowego wiersza Wycinki Słownictwo Wyzwania

84 84 85 85 86 86 86 87 88 89 89 89 90 90 91 91 93 93

Pętle

95

Pętle for Funkcja range Pętle while Instrukcja break Instrukcja continue Pętle zagnieżdżone Słownictwo Wyzwania

95 99 99 100 101 102 104 104

Moduły

105

Moduły wbudowane Importowanie innych modułów Słownictwo Wyzwania

Rozdział 9.

105 107 108 108

Pliki

109

Zapisywanie danych w pliku Automatyczne zamykanie plików Odczyt z plików Pliki CSV Słownictwo Wyzwania

109 111 111 112 114 114

a9c8a50d59776690f264c310a5514dcc a

8

Spis treści

Rozdział 10. Łączenie wszystkiego w całość

115

Wisielec Wyzwania

116 119

Rozdział 11. Praktyka

121

Do przeczytania Inne zasoby Poszukiwanie pomocy

121 121 122

Część II. Wprowadzenie do programowania obiektowego

123

Rozdział 12. Paradygmaty programowania

125

Stan Programowanie proceduralne Paradygmat programowania funkcyjnego Paradygmat programowania obiektowego Słownictwo Wyzwania

Rozdział 13. Cztery filary programowania obiektowego Hermetyzacja Abstrahowanie Polimorfizm Dziedziczenie Kompozycja Słownictwo Wyzwania

125 126 127 128 133 134

135 135 138 138 140 142 143 144

Rozdział 14. Więcej o programowaniu obiektowym Zmienne klasowe a zmienne instancyjne Metody magiczne Is Słownictwo Wyzwania

Rozdział 15. Łączenie wszystkiego w całość Karty Talia Klasa gracza Klasa gry Wojna

145 145 147 149 150 150

151 151 153 154 155 156

a9c8a50d59776690f264c310a5514dcc a

9

Spis treści

Część III. Wprowadzenie do narzędzi programistycznych

159

Rozdział 16. Bash

161

Co dalej Znajdowanie Bash Polecenia Ostatnie polecenia Ścieżki względne i bezwzględne Poruszanie się Flagi Pliki ukryte Potoki Zmienne środowiskowe Użytkownicy Dalsza nauka Słownictwo Wyzwania

162 162 163 164 164 165 166 167 168 168 169 170 170 171

Rozdział 17. Wyrażenia regularne

173

Konfiguracja Proste dopasowania Dopasowywanie początku i końca Dopasowywanie różnych znaków Dopasowywanie cyfr Powtórzenia Dosłowne traktowanie znaków Narzędzia do tworzenia wyrażeń regularnych Słownictwo Wyzwania

Rozdział 18. Menedżery pakietów

173 175 176 177 178 179 181 182 182 182

183

Pakiety Pip Środowiska wirtualne Słownictwo Wyzwania

183 184 186 186 186

Rozdział 19. Kontrola wersji

187

Repozytoria Rozpoczynanie projektu Wypychanie i wciąganie zmian

188 189 190

a9c8a50d59776690f264c310a5514dcc a

10

Spis treści

Przykład wypychania Przykład wciągania Przywracanie wersji diff Dalsze kroki Słownictwo Wyzwania

191 194 194 195 197 197 198

Rozdział 20. Łączenie wszystkiego w całość

199

Kod HTML Pozyskiwanie informacji z witryny Google Wiadomości Słownictwo Wyzwanie

200 201 204 204

Część IV. Wprowadzenie do informatyki

205

Rozdział 21. Struktury danych

207

Struktury danych Stosy Odwracanie łańcucha znaków przy użyciu stosu Kolejki Kolejka po bilety Słownictwo Wyzwania

Rozdział 22. Algorytmy

207 208 210 211 212 214 214

215

FizzBuzz Wyszukiwanie sekwencyjne Palindrom Anagram Zliczanie wystąpień liter Rekurencja Słownictwo Wyzwania

215 216 217 218 218 219 221 222

a9c8a50d59776690f264c310a5514dcc a

11

Spis treści

Część V. Zdobywanie pracy

223

Rozdział 23. Najlepsze praktyki programistyczne

225

Pisz kod w ostateczności Zasada DRY Prostopadłość Każdy element danych powinien mieć jedną reprezentację Funkcje powinny robić tylko jedną rzecz Jeśli to trwa zbyt długo, zapewne robimy coś źle Wykonuj operacje w optymalny sposób już od samego początku Zachowaj zgodność z konwencjami Używaj dobrego IDE Rejestracja Testowanie Przeglądanie kodu Bezpieczeństwo Słownictwo

Rozdział 24. Pierwsza praca w charakterze programisty Określ ścieżkę Zdobywanie początkowego doświadczenia Przygotowania do rozmowy kwalifikacyjnej Rozmowa kwalifikacyjna Jak radzić sobie na rozmowie

Rozdział 25. Praca w zespole

226 226 226 226 227 227 227 228 228 229 229 230 230 231

233 233 234 235 235 236

237

Opanowanie podstaw Nie pytaj o to, co możesz znaleźć w internecie Modyfikowanie kodu Syndrom oszusta

Rozdział 26. Dalsza lektura

237 238 238 238

239

Klasyka Kursy internetowe Hacker News

239 240 240

Rozdział 27. Dalsze kroki

241

Poszukaj mentora Skacz na głęboką wodę Kolejna rada

241 242 242

Podziękowania Skorowidz

245 247

a9c8a50d59776690f264c310a5514dcc a

12

Spis treści

a9c8a50d59776690f264c310a5514dcc a

Część

I Wprowadzenie do programowania

a9c8a50d59776690f264c310a5514dcc a

a9c8a50d59776690f264c310a5514dcc a

Rozdział

1 Wprowadzenie

„Większość dobrych programistów programuje nie dlatego, że oczekują zapłaty lub powszechnego uwielbienia, ale dlatego, że jest to świetna zabawa”. — Linus Torvalds

Studiowałem nauki polityczne na Uniwersytecie Clemson. Zwróciłem wtedy uwagę na informatykę i nawet zapisałem się na pierwszym roku na kurs „Wprowadzenie do programowania”, ale szybko z niego zrezygnowałem. Był dla mnie zbyt trudny. Kiedy jednak po zakończeniu studiów zamieszkałem w Dolinie Krzemowej, uznałem, że muszę się nauczyć programowania. Po roku pracowałem jako programista drugiego stopnia w eBay (to lepiej niż programista pierwszego stopnia i gorzej niż starszy programista). Nie chciałbym jednak sprawiać wrażenia, że to było łatwe. Było to niezwykle wymagające, ale w przerwach pomiędzy rzucaniem czym popadnie po ścianach miałem sporo frajdy. Swoją przygodę z nauką programowania rozpocząłem od języka Python, popularnego języka programowania. Jednak celem tej książki nie jest jedynie nauczenie czytelników programowania w konkretnym języku, choć —oczywiście — chodzi także to. Jej celem jest przedstawienie wszystkiego, czego nie można znaleźć w standardowych źródłach informacji. Traktuje ona o rzeczach, których sam nauczyłem się po drodze, zostając programistą. Książka nie jest przeznaczona dla kogoś poszukującego zwyczajnego wprowadzenia do zagadnień programowania, by potem pisać kod w ramach hobby. Napisałem ją specjalnie z myślą o osobach, które pragną zajmować się programowaniem zawodowo. Niezależnie od tego, czy celem jest zostanie programistą, przedsiębiorcą, czy też wykorzystanie nowych umiejętności programistycznych w jeszcze innej profesji, tę książkę napisałem właśnie dla Ciebie. 15

a9c8a50d59776690f264c310a5514dcc a

16

Rozdział 1



Wprowadzenie

Nauczenie się nowego języka programowania to tylko część całej batalii. Są także inne umiejętności, które trzeba posiąść, by porozumiewać się w języku informatyków. Nauczę Cię wszystkiego, czego sam nauczyłem się na drodze od zupełnego amatora do profesjonalnego programisty. Napisałem tę książkę, by pokazać osobom marzącym o zostaniu programistami zarys tego, co muszą umieć. Jako programista samouk nie wiedziałem, czego mam się uczyć. Książki zawierające wprowadzenie do programowania wszystkie są takie same. Uczą podstaw programowania w językach Python lub Ruby i pozostawiają nas samym sobie. Osoby, które przeczytały takie książki, często pytały: „Co mam teraz zrobić? Jeszcze nie jestem programistą i nie wiem, czego mam się uczyć w następnej kolejności”. Ta książka jest moją odpowiedzią na to pytanie.

Struktura książki Wielu zagadnieniom poruszanym w pojedynczym rozdziale tej książki można by poświęcić odrębną pozycję — i w wielu przypadkach tak się właśnie dzieje. Moim celem jednak nie jest opisywanie każdego szczegółu każdego zagadnienia, które może Ci się przydać. Moim celem jest przedstawienie mapy — zarysu wszystkich umiejętności, które będziesz musiał zdobyć, by zawodowo zajmować się programowaniem. Książka została podzielona na pięć części. Część I. — Wprowadzenie do programowania. Swój pierwszy program napiszesz tak szybko, jak to tylko możliwe. Miejmy nadzieję, że będzie to już dziś. Część II. — Wprowadzenie do programowania obiektowego. W tej części opisuję różne paradygmaty programowania, koncentrując się na programowaniu obiektowym. Napiszesz w tej części grę, która pokaże ogromne możliwości, jakie daje programowanie. Jestem pewny, że po przeczytaniu tej części książki będziesz zafascynowany programowaniem. Część III. — Wprowadzenie do narzędzi programistycznych. W tej części książki pokażę różne narzędzia programistyczne, które przeniosą wydajność pracy na wyższy poziom. Na tym etapie jesteś już zapewne zafascynowany programowaniem i chcesz być coraz lepszy. W tym miejscu dowiesz się nieco więcej o systemach operacyjnych, o możliwościach stosowania wyrażeń regularnych w celu poprawy wydajności pracy, o instalowaniu programów napisanych przez innych i zarządzaniu nimi oraz o współpracy z innymi programistami przy wykorzystaniu systemów kontroli wersji. Część IV. — Wprowadzenie do informatyki. Ta część zawiera krótkie wprowadzenie do informatyki i jest poświęcona dwóm podstawowym zagadnieniom, czyli algorytmom i strukturom danych. Cześć V. — Zdobywanie pracy. Ostatnia część książki jest poświęcona najlepszym praktykom programistycznym, zdobywaniu pracy w charakterze programisty, pracy w zespole oraz samodoskonaleniu programistów. Zawiera porady, jak przejść techniczną rozmowę kwalifikacyjną na stanowisko programisty i jak pracować w zespole oraz informacje o tym, jak dalej poszerzać swoje umiejętności. Jeśli nie dysponujesz jeszcze żadnymi umiejętnościami w zakresie programowania, podczas lektury wszystkich części książki jak najwięcej samodzielnie ćwicz programowanie. Nie staraj się przeczytać tej książki zbyt szybko. Warto jej używać jak przewodnika i pomiędzy lekturą kolejnych rozdziałów jak najwięcej ćwiczyć.

a9c8a50d59776690f264c310a5514dcc a

17

Zacznijmy od końca

Zacznijmy od końca Sposób, w jaki nauczyłem się programować, jest dokładnie odwrotny od standardowego sposobu nauki informatyki i programowania, a struktura tej książki odpowiada mojemu tokowi nauki. W tradycyjnym sposobie nauki na początku wiele czasu poświęca się na poznanie teorii — w rzeczywistości przeznacza się na to tak wiele czasu, że wiele osób, które skończyły kursy informatyki, wcale nie potrafi pisać programów. Jeff Atwood na swoim blogu, w poście „Why Can’t Programmers… Program?”1 pisze: „Autor, podobnie jak ja, nie może zrozumieć faktu, że 199 na 200 starających się o posadę programisty nie potrafi pisać kodu. Powtarzam: w ogóle nie potrafi napisać żadnego kodu”. To spostrzeżenie skłoniło Atwooda do stworzenia programistycznego wyzwania o nazwie FizzBuzz — programistycznego testu używanego na rozmowach kwalifikacyjnych w celu eliminacji kandydatów. Większość kandydatów oblewa ten test i właśnie z tego powodu podczas lektury tej książki tak wiele czasu poświęcimy na naukę umiejętności, które będą niezbędne w praktyce. Jednak nie masz się czego obawiać: nauczysz się także wszystkiego, czego potrzeba, by zdać test FizzBuzz. Bohater filmu Szachowe dzieciństwo, w swojej książce Sztuka nauki, opisuje, jak nauczył się grać w szachy w sposób odwrotny do standardowo przyjętego. Zamiast studiować otwarcia, zaczął od nauki gry końcowej (w której na planszy pozostaje jedynie kilka figur). Taka strategia pozwoliłam mu lepiej zrozumieć grę i wygrać wiele zawodów. Na podobnej zasadzie uważam, że lepsze efekty daje nauczenie się najpierw programowania i poznawanie teorii później, kiedy już będziemy chcieli wiedzieć, jak wszystko działa. Właśnie z tego względu teoretyczne zagadnienia informatyki wprowadzam dopiero w czwartej części książki, a w dodatku przedstawiam je w jak najkrótszej postaci. Choć teoria jest ważna, jednak jej znajomość stanie się jeszcze cenniejsza, kiedy zostanie poparta praktycznymi doświadczeniami programistycznymi.

Nie jesteś sam Nauka programowania poza szkołami czy uczelniami staje się coraz popularniejsza. W 2015 roku serwis Stack Overflow (internetowa społeczność programistów) przeprowadził ankietę, z której wynikało, że ponad 48% respondentów nie ma żadnego wykształcenia związanego z programowaniem lub informatyką2.

Zalety samodzielnej nauki Kiedy zostałem zatrudniony w eBay, znalazłem się w zespole programistów mających tytuły magistrów informatyki zdobyte na uniwersytetach Stanforda, Cal, Duke, były w nim nawet dwie osoby po

1

Dlaczego programiści nie mogą… programować? — przyp. tłum.

2

http://www.infoworld.com/article/2908474/application-development/stack-overflowsurvey-finds-nearly-halfhave-no-degree-in-computer-science.html

a9c8a50d59776690f264c310a5514dcc a

18

Rozdział 1



Wprowadzenie

doktoratach. Dla mnie, osoby w wieku 25 lat, było krępujące, że moi 21-letni koledzy wiedzieli o programowaniu i informatyce 10 razy więcej niż ja. Niezależnie od tego, jak onieśmielające byłoby pracowanie z osobami z tytułami licencjatów, magistrów czy też nawet doktorów informatyki, nigdy nie zapominaj, że będziesz dysponował tym, czym dysponowałem ja — „zaletą samodzielnej nauki”. Nie sięgnąłeś po tę książkę, bo kazał Ci to zrobić nauczyciel czy wykładowca, ale dlatego, że pragniesz się czegoś nauczyć, a pragnienie poznania czegoś jest największą zaletą i przewagą, jaką można dysponować. Poza tym nie wolno zapominać, że wiele z osób, które w świecie komputerów odniosło największe sukcesy, było właśnie samoukami. Steve Wozniak, współzałożyciel firmy Apple, jest programistą samoukiem. Podobnie jak Margaret Hamilton, która otrzymała Prezydencki Medal Wolności za swoją pracę w NASA nad misją Apollo, David Karp — założyciel Tumblr, Jack Dorsey — założyciel Twittera oraz Kevin Systrom — założyciel Instagrama.

Dlaczego należy programować? Programowanie może pomóc w karierze niezależnie do profesji. Uczenie się programowania wzmacnia. Uwielbiam wymyślanie nowych pomysłów i zawsze chodzi mi po głowie jakiś nowy projekt, który chciałbym rozpocząć. Kiedy nauczyłem się programowania, mogłem siąść i realizować swoje pomysły bez poszukiwania kogoś, kto mógłby to zrobić dla mnie. Programowanie sprawi, że można być lepszym we wszystkim, co się robi. Nie ma wielu zagadnień, które nie skorzystałyby na umiejętności dokładnego przeanalizowania i określenia rozwiązania problemu. Ostatnio musiałem wykonać bardzo żmudne zadanie polegające na poszukiwaniu lokum na witrynie Craiglist. Potrafiłem jednak napisać program, który robił to za mnie i wysyłał mi wyniki e-mailem. Nauka programowania może więc uwolnić od konieczności wykonywania powtarzających się czynności. Jeśli ktoś chce stać się programistą, warto wiedzieć, że zapotrzebowanie na nich jest coraz większe i nie ma dostatecznie wielu odpowiednio wykwalifikowanych kandydatów, by zaspokoić potrzeby. Szacuje się, że do 2020 roku ponad milion wakatów na programistów pozostanie nieobsadzonych3. Jeśli nawet zostanie zawodowym programistą nie jest Twoim celem, umiejętność programowania jest także ceniona w takich branżach jak nauka oraz finanse.

Konsekwencja i zainteresowanie By poprawiać swoje umiejętności programistyczne, należy programować codziennie. Jedyną rzeczą, jaka ewentualnie mogłaby powstrzymać Cię przed codziennym ćwiczeniem, jest brak zainteresowania programowaniem. Poznaj zatem dwie metody, by do tego nie dopuścić. Jeśli nie masz żadnego doświadczenia programistycznego, a rozpoczęcie tej podróży napawa Cię obawą, jestem pewny, że i tak sobie poradzisz. Istnieje wiele nieprawdziwych opinii dotyczących 3

http://www.wsj.com/articles/computer-programming-is-a-trade-lets-act-like-it-1407109947?mod=e2fb

a9c8a50d59776690f264c310a5514dcc a

19

Postać książki

programowania, takich jak ta, że wymaga ono doskonałej znajomości matematyki. Otóż nie wymaga. Wcale nie trzeba być świetnym z matematyki, by się uczyć programowania, choć jej znajomość może pomóc. A zatem bardzo wiele zagadnień opisywanych w tej książce będzie łatwiejszych do opanowania, niż można przypuszczać. Kiedy zaczynałem, używałem listy zadań do wykonania, która zapewniała, że programowałem każdego dnia i zachowywałem koncentrację. Jeśli potrzebujesz dodatkowej pomocy, Tim Ferriss, specjalista do spraw efektywności, zaleca stosowanie następującej techniki pozwalającej na zachowanie wysokiego poziomu motywacji: daj przyjacielowi lub jakiemuś członkowi rodziny pewną sumę pieniędzy wraz z instrukcją, by Ci ją oddał, kiedy zrealizujesz cele w zadanych ramach czasowych, bądź też, by przekazał jakiej nielubianej organizacji, jeśli Ci się to nie uda.

Postać książki Każdy kolejny rozdział tej książki bazuje na poprzednich. Starałem się unikać w niej wielokrotnego wyjaśniania tych samych pojęć, więc warto starać się je zapamiętywać. Ważne terminy w miejscu ich pierwszego wystąpienia są zapisywane pogrubioną czcionką. Na końcu każdego rozdziału znajduje się słowniczek, w którym wszystkie te ważne terminy są definiowane. Na końcu każdego rozdziału znajdują się także zadania, których celem jest poprawienie umiejętności programistycznych, tam także znajdują się odnośniki do rozwiązań tych zadań.

Technologie stosowane w książce Książka uczy pewnych technologii, by zapewnić Ci możliwość zdobycia jak największej liczby praktycznych umiejętności programistycznych. Staram się jednak zachować neutralność w doborze technologii, koncentrując się nie na nich, lecz raczej na ogólnych koncepcjach. W niektórych przypadkach musiałem jednak wybrać jedną spośród wielu dostępnych technologii. W rozdziale 19., „Kontrola wersji” (jeśli nie wiesz, czym ona jest, wyjaśnię o co chodzi nieco później), przedstawiam podstawowe informacje o stosowaniu systemu Git — popularnego systemu kontroli wersji. Wybrałem Git, gdyż uważam że stanowi przemysłowy standard kontroli wersji. W większości przykładów programistycznych używam języka Python, gdyż jest bardzo popularnym językiem do nauki programowania, a jego kod jest łatwy do czytania i analizy nawet dla osób, które nigdy wcześniej go nie używały. Oprócz tego osoby znające Pythona są bardzo poszukiwane niemal na każdym rynku pracy. Oczywiście, do wykonywania przykładów prezentowanych w tej książce konieczny będzie komputer. Każdy komputer ma system operacyjny — program pełniący rolę pośrednika pomiędzy komponentami sprzętowymi komputera a człowiekiem. To, co widzimy, patrząc na ekran komputera, jest nazywane graficznym interfejsem użytkownika (w skrócie GUI od angielskich słów: graphical user interface) i stanowi jeden z elementów systemu operacyjnego.

a9c8a50d59776690f264c310a5514dcc a

20

Rozdział 1



Wprowadzenie

Istnieją trzy najpopularniejsze systemy operacyjne używane na komputerach biurkowych i laptopach; są to Windows, Unix oraz Linux. Windows jest systemem stworzonym przez firmę Microsoft. Unix z kolei powstał już w latach 70. ubiegłego wieku. Obecny system operacyjny używany przez firmę Apple bazuje właśnie na Uniksie. Od tej pory będę używał nazwy Unix także wtedy, gdy będę miał na myśli system operacyjny stosowany na komputerach Apple. Z kolei Linux — system rozpowszechniany jako oprogramowanie otwarte (typu open source) — jest używany na ogromnej większości istniejących obecnie serwerów. Serwer to komputer lub program komputerowy wykonujący jakieś zadanie, na przykład udostępniający witrynę WWW. Oprogramowanie otwarte (nazywane także oprogramowaniem typu open source) to program, który nie jest własnością żadnej firmy ani osoby i który może być rozpowszechniany i modyfikowany bez żadnych ograniczeń. Linux oraz Unix są systemami uniksowymi, co oznacza, że są do siebie bardzo podobne. W tej książce zakładam, że używasz komputera działającego pod kontrolą systemów Windows, Unix lub Ubuntu (popularnej wersji systemu operacyjnego Linux).

Słownictwo FizzBuzz. Test programistyczny stosowany do odsiewania kandydatów. Graficzny interfejs użytkownika (GUI). Ta część systemu operacyjnego, którą użytkownik może oglądać na ekranie komputera. Linux. System operacyjny typu open source używany na większości serwerów na świecie. Oprogramowanie typu open source. Oprogramowanie, które nie jest własnością żadnej firmy ani konkretnej osoby; nad jego rozwojem pracuje grupa ochotników. Serwer. Komputer lub program komputerowy wykonujący konkretne zadanie, takie jak udostępnianie witryny WWW. System operacyjny. Program pełniący rolę pośrednika pomiędzy fizycznymi komponentami komputera a człowiekiem. Uniksowy system operacyjny. Unix lub Linux. Unix. System operacyjny stworzony w latach 70. ubiegłego wieku. Bazuje na nim obecny system używany na komputerach firmy Apple. Windows. System operacyjny firmy Microsoft.

Wyzwanie 1. Przygotuj codzienną listę rzeczy do wykonania, obejmującą ćwiczenie programowania. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

2 Zaczynamy

„Dobry programista to ktoś, kto przechodząc przez jednokierunkową ulicę, zawsze rozgląda się w obie strony”. — Doug Linder

Czym jest programowanie Programowanie to pisanie instrukcji, które komputer ma wykonać. Instrukcje te mogą nakazywać komputerowi wyświetlenie na ekranie tekstu Witaj, świecie!, wyciągnięcie danych ze strony WWW lub odczytanie zawartości jakiegoś pliku i zapisanie jej w bazie danych. Wszystkie te instrukcje ogólnie nazywamy kodem. Programiści piszą kod w wielu różnych językach programowania. W przeszłości programowanie było znacznie trudniejsze, gdyż programiści musieli używać bardziej tajemniczych języków niskiego poziomu, takich jak język asemblera. Kod w języku niższego poziomu łatwiej można zapisać w postaci binarnej (czyli używając zer i jedynek), czego nie można powiedzieć o kodzie napisanym w języku programowania wyższego poziomu (który jest bardziej zbliżony do języka, w jakim porozumiewają się ludzie). Kod w języku niższego poziomu trudniej zrozumieć. Poniżej przedstawiony został przykład kodu napisanego w języku asemblera: # bash_lst00.sh global

_start section .text

_start: mov mov

rax, 1 rdi, 1

21

a9c8a50d59776690f264c310a5514dcc a

22

Rozdział 2

mov rsi, mov rdx, syscall ; exit(0) mov eax, xor rdi, syscall



Zaczynamy

message 13 60 rdi

message: db

"Witaj, swiecie!", 10

A to dokładnie ten sam program napisany w nowoczesnym języku programowania: 1 # python_lst000.py 2 3 print("Witaj, świecie!")

Łatwo stwierdzić, że obecnie programiści mają znacznie łatwiej. Dziś, żeby programować, nie trzeba uczyć się tajemniczych języków programowania niskiego poziomu. Zamiast tego można poznać któryś z nowoczesnych języków, taki jak Python, którego kod jest znacznie prostszy do czytania i analizy.

Czym jest Python Python to język programowania dostępny jako oprogramowanie typu open source, opracowany przez holenderskiego programistę Guido van Rossuma i nazwany na cześć brytyjskiej grupy komediowej Monthy Python. Jedynym z kluczowych spostrzeżeń von Rossuma było to, że programiści poświęcają wiele więcej czasu na czytanie kodu niż na jego pisanie, dlatego też stworzył język, który ułatwiał czytanie kodu. Python jest jednym z najbardziej popularnych i najłatwiejszych do nauki języków programowania. Jest dostępny w wersjach przeznaczonych na wszystkie najpopularniejsze systemy operacyjne oraz komputery i używany do tworzenia serwerów WWW, aplikacji na komputery biurkowe oraz wszystkich innych rozwiązań pośrednich. Ze względu na swoją popularność zapotrzebowanie na programistów potrafiących pisać w tym języku jest bardzo duże.

Instalowanie Pythona Aby można było wykonywać przykłady prezentowane w tej książce, należy zainstalować język Python w wersji 3. Język ten w wersjach na systemy Windows i Unix można pobrać ze strony http://python.org/downloads. Jeśli używasz systemu Ubuntu, Python jest w nim instalowany domyślnie. Koniecznie należy pobrać i zainstalować Python 3, a nie Python 2, gdyż niektóre z prezentowanych przykładów nie będą działać w starszej wersji języka. Python jest dostępny w wersjach na komputery 32- oraz 64-bitowe. Jeśli kupiłeś swój komputer po 2007 roku, najprawdopodobniej będzie to maszyna 64-bitowa. Jeśli nie masz pewności, jakiego komputera używasz, krótkie poszukiwania w internecie pewnie pozwolą to ustalić.

a9c8a50d59776690f264c310a5514dcc a

23

Rozwiązywanie problemów

Użytkownicy systemów Windows lub MacOS powinni pobrać odpowiednią, 32- lub 64-bitową wersję Pythona, otworzyć pobrany plik i dalej postępować zgodnie z instrukcjami.

Rozwiązywanie problemów Od tego momentu zakładam, że dysponujesz zainstalowanym środowiskiem języka Python. Gdyby wystąpiły jakieś problemy, można zajrzeć do rozdziału 11., do podrozdziału „Poszukiwanie pomocy”.

Interaktywna powłoka Python jest dostarczany wraz z programem o nazwie IDLE, co stanowi skrót od angielskich słów interactive develompment environment (czyli: interaktywne środowisko programistyczne; choć jednocześnie jest to nazwisko Erica Idle, jednego z członków Latającego Cyrku Monthy Pythona). IDLE jest właśnie tym miejscem, w którym będziesz wpisywał kod pisany w języku Python. Po zainstalowaniu Pythona należy poszukać IDLE w Eksploratorze Windows (na komputerach PC), w programie Finder (na komputerach Mac) lub w Nautiliusie (w systemie Ubuntu). Sugeruję także, by utworzyć na pulpicie skrót do tego programu. Kliknięcie ikony IDLE spowoduje uruchomienie programu i wyświetlenie w jego oknie komunikatu podobnego do przedstawionego poniżej (treść tego komunikatu może być nieco inna, jednak nie należy się tym przejmować): Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>>

Program ten jest nazywany interaktywną powłoką (ang. interactive shell). Można w nim bezpośrednio wpisywać kod w języku Python, a on będzie go wykonywał i wyświetlał wyniki. Tuż za znakiem zachęty >>> należy wpisać: print("Witaj, świecie!")

i nacisnąć klawisz Enter. IDLE może odmówić wykonania kodu skopiowanego i wklejonego z czytnika e-booków, programu do wyświetlania e-booków czy też procesora tekstów, takiego jak Microsoft Word. Jeśli po skopiowaniu i wklejeniu tekstu IDLE wyświetli niezrozumiały komunikat o błędzie, należy spróbować wpisać kod bezpośrednio. Kod musi zostać wpisany dokładnie w takiej postaci, w jakiej jest widoczny w przykładzie, włącznie ze znakami cudzysłowów, nawiasami i wszelkimi innymi znakami przestankowymi. Interaktywna powłoka w efekcie wyświetli tekst Witaj, świecie!, co pokazano na rysunku 2.1.

a9c8a50d59776690f264c310a5514dcc a

24

Rozdział 2



Zaczynamy

Rysunek 2.1. Kod Pythona wykonany w programie IDLE

W świecie programowania istnieje tradycja, że pierwszy program, jaki pisze się w nowym języku, to krótki program wyświetlający na ekranie tekst Witaj, świecie!. A zatem, gratuluję! Właśnie udało Ci się napisać swój pierwszy program.

Zapisywanie programów Interaktywna powłoka świetnie nadaje się do wykonywania szybkich obliczeń, testowania krótkich fragmentów kodu czy też pisania prostych programów, których nie planujemy używać wielokrotnie. Jednak programu IDLE można używać także do zapisywania programów, które kiedyś jeszcze mogą się nam przydać. Oto, co trzeba zrobić, by zapisać program: z menu głównego programu IDLE należy wybrać opcję File (widoczną na pasku menu, umieszczonym bezpośrednio poniżej górnej krawędzi okna), a następnie opcję New File. Wybranie tej opcji spowoduje wyświetlenie nowego okna edytora tekstów, które początkowo jest puste i ma białe tło. W tym edytorze można wpisać kod, a następnie zapisać go, by wykonać kiedyś w przyszłości. Po wykonania takiego kodu jego wyniki zostaną wyświetlone w oknie interaktywnej powłoki. Przed wykonaniem edytowanego kodu należy go zapisać. Spróbujmy wpisać pogram Witaj, świecie! w edytorze (patrz rysunek 2.2).

Rysunek 2.2. Wpisywanie nowego programu w edytorze tekstów

Teraz z menu głównego należy ponownie wybrać opcję File, a następnie Save As. W wyświetlonym oknie dialogowym należy podać nazwę pliku, taką jak hello_world.py, i zapisać go. Po zapisaniu pliku trzeba wybrać z menu głównego opcję Run (dostępną na pasku umieszczonym pod górną

a9c8a50d59776690f264c310a5514dcc a

25

Uruchamianie programów przykładowych

krawędzią okna), a następnie opcję Run Module. Ewentualnie można także nacisnąć klawisz F5, co jest odpowiednikiem wybrania z menu opcji Run Module. W efekcie, w oknie interaktywnej powłoki zostanie wyświetlony komunikat Witaj, świecie!, dokładnie tak samo, jakby odpowiednia instrukcja została wpisana bezpośrednio w tym oknie. Jednak obecnie, ponieważ program został już zapisany, można go będzie wykonywać dowolnie wiele razy. Program, który przed chwilą utworzyłeś, jest zwyczajnym plikiem z rozszerzeniem .py, zapisanym w wybranym wcześniej katalogu. Nazwa tego pliku, w naszym przypadku jest to hello_world.ph, jest całkowicie dowolna — można go nazwać, jak tylko zechcesz. Jak pokazano w tym przykładzie, pisanie programów w Pythonie polega na wpisywaniu kodu w plikach tekstowych i wykonywaniu ich z wykorzystaniem interaktywnej powłoki. Proste, prawda?

Uruchamianie programów przykładowych W tej książce zamieszczam przykładowe kody oraz wyniki generowane podczas ich wykonywania. Zawsze wtedy, kiedy tak robię, powinieneś wpisać lub skopiować kod i samodzielnie go wykonać. Krótkie przykłady najlepiej wykonywać w interaktywnej powłoce, natomiast większe lepiej zapisać i wykonać, korzystając z edytora. Jeśli podczas wpisywania kodu w interaktywnej powłoce popełnimy jakiś błąd — choćby prostą literówkę — w efekcie którego kod nie będzie działał poprawnie, trzeba będzie wszystko wpisywać od nowa. Natomiast przy użyciu edytora będzie można zapisać plik, a w razie jakiegoś błędu wystarczy wprowadzić w nim odpowiednie poprawki i ponownie wykonać. Kolejnym powodem, dla którego to rozróżnienie ma znaczenie, są drobne różnice, które mogą występować pomiędzy wynikami programów uruchamianych z plików oraz z poziomu powłoki. Jeśli w powłoce wpiszemy 100 i naciśniemy Enter, powłoka wyświetli liczbę 100. Jeśli to samo 100 wpiszemy w pliku z rozszerzeniem .py, który następnie wykonamy, nie uzyskamy żadnych wyników. Te różnice mogą wprowadzać pewne zamieszanie, dlatego też jeśli podczas wykonywania przykładów nie uzyskamy takich samych wyników jak prezentowane w książce, należy zwrócić uwagę na to, gdzie został wykonany program.

Słownictwo Język asemblera. Typ języka programowania, którego kod jest trudny do czytania i analizy. Język programowania niskiego poziomu. Język programowania, który łatwiej można zapisać w postaci zer i jedynek niż tak zwane języki wysokiego poziomu. Język programowania wysokiego poziomu. Język programowania, którego kod jest w większym stopniu zbliżony do języka używanego przez ludzi niż kod języków niskiego poziomu. Kod. Instrukcje pisane przez programistę, które komputer ma wykonać. Programowanie. Pisanie instrukcji, które komputer ma wykonać.

a9c8a50d59776690f264c310a5514dcc a

26

Rozdział 2



Zaczynamy

Python. Język programowania dostępny w formie oprogramowania typu open source, którego będziesz się uczył w tej książce; cechuje go kod łatwy do analizy i czytania. Został stworzony przez Guido van Rossuma i nazwany na cześć brytyjskiej grupy komediowej Latający Cyrk Monty Pythona.

Wyzwanie 1. Spróbuj wyświetlić coś innego niż tekst Witaj, świecie!

Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

3 Wprowadzenie do programowania

„To jedyny zawód, jaki potrafię wskazać, w którym mogę jednocześnie być inżynierem i artystą. Ma w sobie niesamowity, rygorystyczny i techniczny pierwiastek, który bardzo lubię, gdyż wymusza niezwykle precyzyjne myślenie. Z drugiej strony, ma też drugą wybitnie kreatywną stronę, w której jedyną granicą jest nasza wyobraźnia”. — Andy Hertzfeld

Nasz pierwszy program wyświetlał tekst Witaj, świecie!. Wyświetlmy teraz ten komunikat 100 razy. Wpisz poniższy kod w interaktywnej powłoce (przed słowem print trzeba wpisać dokładnie cztery znaki odstępu): 1 # python_lst003.py 2 3 for i in range(100): 4 print("Witaj, świecie!")

W efekcie słowa Witaj, świecie! zostaną wyświetlone 100 razy. Choć najprawdopodobniej nigdy nie będziesz musiał wyświetlać sto razy takiego tekstu, jednak powyższy przykład pokazuje, jak ogromne możliwości ma programowanie. Czy możesz wskazać coś innego, co mógłbyś równie łatwo wykonać 100 razy? Właśnie na tym polega potęga programowania. 27

a9c8a50d59776690f264c310a5514dcc a

28

Rozdział 3



Wprowadzenie do programowania

Przykłady Od tej chwili przykłady prezentowane w książce będą mieć następującą postać: 1 # python_lst004.py 2 3 for i in range(100): 4 print("Witaj, świecie!")

>> Witaj, świecie! >> Witaj, świecie! >> Witaj, świecie! ...

Tekst # python_lst004.py to komentarz zawierający nazwę pliku, w którym jest zapisany prezentowany poniżej kod. Dzięki temu w każdej chwili będziesz mógł odszukać ten plik, skopiować jego kod, wkleić do edytora IDLE i wykonać. Tekst poprzedzony znakami >> to wyniki wyświetlane w interaktywnej powłoce. W książce teksty poprzedzone tymi znakami są umieszczane po każdym listingu i prezentują jego wyniki (wyświetlone w powłoce). Trzykropek (...) oznacza „i tak dalej”. Jeśli po przykładzie nie będzie wyników poprzedzonych znakami >>, znaczy to, że program nie generuje żadnych wyników bądź też wyjaśniam jakieś pojęcie i wyniki kodu nie mają w danym przypadku znaczenia. Każdy tekst prezentowany czcionką Courier New jest jakąś formą kodu, wyników generowanych przez kod bądź żargonem programistycznym. Gdybym na przykład opisywał słowo for użyte w ostatnim przykładzie, byłoby ono zapisane właśnie czcionką Courier New. Courier New jest czcionką o stałej szerokości znaków (nieproporcjonalną), bardzo często używaną do prezentowania kodów programów. W tej czcionce każdy znak ma tę samą szerokość, dzięki czemu znacznie lepiej widoczne są formatowanie oraz inne charakterystyczne cechy tekstu. Przykłady można wykonywać bądź to w interaktywnej powłoce, bądź też bezpośrednio z plików .py. Trzeba jednak pamiętać, że zgodnie z tym, o czym wspominałem już wcześniej, wyniki uzyskiwane podczas wykonywania programów w powłoce mogą być nieco inne. Jeśli zatem faktycznie okaże się, że wyświetlone wyniki różnią się od tych w książce, przyczyną będzie właśnie sposób wykonania kodu. Jeśli przykład generuje jakieś wyniki, lecz nie zawiera w kodzie słowa print, oznacza to, że należy go wpisać w powłoce. Jeśli kod przykładu zawiera print, oznacza to, że przykład należy wykonywać z pliku .py.

Komentarze Komentarz to wiersz tekstu (lub jego fragment) napisany w języku używanym przez ludzi i poprzedzony symbolem informującym język programowania o tym, że należy go zignorować, a nie wykonywać. W języku Python symbolem używanym do oznaczania komentarzy jest znak krzyżyka (#).

a9c8a50d59776690f264c310a5514dcc a

29

Wyświetlanie

Komentarze wyjaśniają znaczenie kodu. Programiści używają ich, by ułatwiać innym zrozumienie przeznaczenia i sposobu działania kodu. W komentarzach można zapisywać co tylko chcemy, o ile tylko ich treść zostanie umieszczona w jedynym wierszu: 1 # python_lst005.py 2 3 # To jest komentarz 4 print("Witaj, świecie!")

>> Witaj, świecie!

Komentarze należy dodawać wyłącznie wtedy, kiedy robimy w kodzie coś niezwykłego lub wyjaśniamy coś, co nie jest oczywiste. Powinno się ich używać oszczędnie — nie należy dodawać komentarzy do każdego wiersza kodu — lepiej oszczędzać je na wyjątkowe sytuacje. Oto przykład zupełnie niepotrzebnego komentarza: 1 # python_lst007.py 2 3 # wyświetla Witaj, świecie! 4 print("Witaj, świecie!")

W tym przypadku komentarz nie jest potrzebny, gdyż przeznaczenie tego wiersza kodu jest całkowicie oczywiste. Kolejny przykład przedstawia dobre i uzasadnione zastosowanie komentarza: 1 # python_lst008.py 2 3 import math 4 5 # długość przekątnej 6 l = 4 7 w = 10 8 d = math.sqrt(l**2 + w**2)

Jeśli nawet doskonale rozumiemy, jak ten kod działa, wciąż możemy nie pamiętać, jak się liczy długość przekątnej prostokąta, dlatego komentarz może się przydać.

Wyświetlanie Pisane programy mogą wyświetlać coś więcej niż Witaj, świecie!. Mogą wyświetlać dowolne teksty, pod warunkiem, że zostaną one zapisane pomiędzy znakami cudzysłowu: 1 # python_lst009.py 2 3 print("Python")

>> Python 1 # python_lst010.py 2 3 print("Siemka!")

>> Siemka!

a9c8a50d59776690f264c310a5514dcc a

30

Rozdział 3



Wprowadzenie do programowania

Wiersze Programy pisane w języku Python składają się z wierszy. Przyjrzyjmy się poniższemu programowi: 1 # python_lst011.py 2 3 # wiersz1 4 # wiersz2 5 # wiersz3

Składa się on z trzech wierszy kodu. Możliwość odwoływania się do kodu na podstawie wiersza, w którym został zapisany, jest bardzo wygodna. W edytorze IDLE wystarczy wybrać opcję Edit, a następnie Go to Line, aby przejść do podanego wiersza kodu. W interaktywnej powłoce można wpisywać jedynie po jednym wierszu kodu. Nie można skopiować i wkleić do niej kilku wierszy kodu jednocześnie. Czasami może się zdarzyć, że kod będzie zbyt długi, by zmieścić się w jednym wierszu. Kod zapisany pomiędzy trzema znakami cudzysłowu, nawiasami, nawiasami kątowymi lub kwadratowymi może zajmować więcej niż jeden wiersz: 1 # python_lst012.py 2 3 print("""To jest naprawdę 4 bardzo, bardzo, bardzo długi 5 wiersz kodu.""")

Można także użyć znaku odwrotnego ukośnika (\), by przenieść dalszą część kodu do kolejnego wiersza: 1 # python_lst013.py 2 3 print\ 4 ("""To jest naprawdę bardzo, bardzo, 5 bardzo długi wiersz kodu.""")

Dwa ostatnie przykłady dają dokładnie takie same wyniki. Zastosowanie odwrotnego ukośnika pozwoliło umieścić ("""To jest naprawdę bardzo, bardzo, bardzo długi wiersz kodu.""") oraz print w osobnych wierszach kodu, co w innych sytuacjach nie byłoby dozwolone.

Słowa kluczowe W językach programowania, takich jak Python, istnieją pewne słowa, które mają specjalne znaczenie. Są one nazywane słowami kluczowymi. Przykładem takiego słowa, które zostało przedstawione już wcześniej, jest for — służy ono do wielokrotnego wykonywania fragmentu kodu. Dalej w tym rozdziale poznasz więcej takich słów kluczowych.

a9c8a50d59776690f264c310a5514dcc a

31

Odstępy

Odstępy Przyjrzyjmy się jeszcze raz programowi, który 100 razy wyświetla tekst Witaj, świecie!: 1 # python_lst014.py 2 3 for i in range(100): 4 print("Witaj, świecie!")

Jak już zwróciłem uwagę wcześniej, przed print są zapisane cztery znaki odstępu. Niebawem wyjaśnię dlaczego, lecz najkrócej rzecz ujmując, pozwala to językowi zorientować się, gdzie zaczyna się blok kodu oraz gdzie się on kończy. Na razie zapamiętaj, że wszędzie tam, gdzie w kodach jest wcięcie, ma ono wielkość czterech znaków odstępu. Bez zastosowania odpowiednich wcięć program nie będzie prawidłowo działać. W innych językach programowania wcięcia nie są stosowane w podobny sposób — zazwyczaj bloki kodu są oznaczane przy użyciu słów kluczowych lub par nawiasów klamrowych. Oto ten sam program napisany w języku o nazwie JavaScript: 1 # python_lst015.py 2 3 # To kod w języku JavaScript - nie będzie działać w Pythonie! 4 5 for (i = 0; i < 100; i++) { 6 console.log("Witaj, świecie!"); 7 }

Zwolennicy Pythona uważają, że wymóg stosowania odpowiednich wcięć sprawia, iż pisanie i czytanie kodu w tym języku jest łatwiejsze niż w innych. Jak pokazałem w powyższym przykładzie, choć stosowanie wcięć nie jest elementem innych języków, programiści i tak ich używają, by poprawić czytelność kodu.

Typy danych Python dzieli dane na dwie różne kategorie nazywane typami danych. W tym języku każda wartość, taka jak 2 czy "Witaj, świecie!", jest nazywana obiektem. Więcej na temat obiektów dowiesz się w drugiej części książki, na razie jednak wystarczy, że będziesz wyobrażać sobie te obiekty jak dane dysponujące trzema właściwościami: tożsamością, typem danych oraz wartością. Tożsamość obiektu to miejsce w pamięci komputera, w którym obiekt jest zapisany i które nigdy się nie zmienia. Typ danych to kategoria danych, do której obiekt należy; określa ona jego właściwości i także nigdy nie ulega zmianie. I w końcu wartość obiektu to dane, które on reprezentuje; na przykład liczba 2 ma wartość 2. "Witaj, świecie!" to obiekt typu str (co jest skrótem od string — angielskiego słowa określającego łańcuch znaków), którego wartością jest "Witaj, świecie!". Kiedy odnosimy się do obiektu typu str, mówimy, że jest to łańcuch znaków. Łańcuch znaków to sekwencja składająca się z jednego

a9c8a50d59776690f264c310a5514dcc a

32

Rozdział 3



Wprowadzenie do programowania

lub większej liczby znaków zapisanych pomiędzy cudzysłowami. Znak to pojedynczy symbol, taki jak a lub 1. Do zapisu łańcuchów można używać cudzysłowów lub apostrofów, trzeba jednak pamiętać, by znak umieszczony na początku łańcucha odpowiadał temu, który został umieszczony na końcu: 1 # python_lst016.py 2 3 "Witaj, świecie!"

>> 'Witaj, świecie! 1 # python_lst017.py 2 3 'Witaj, świecie!'

>> 'Witaj, świecie!'

Łańcuchy znaków są używane do reprezentowania tekstów i mają unikalne cechy. Liczby, których użyliśmy do wykonywania operacji arytmetycznych w jednym z poprzednich przykładów, także są obiektami — jednak nie są to łańcuchy znaków. Liczby całkowite (1, 2, 3, 4 i tak dalej) są typu int, co jest skrótem od słowa integer. Podobnie jak łańcuchy znaków, także liczby całkowite mają swoje unikalne cechy. Przykładowo dwie liczby całkowite można przez siebie pomnożyć, natomiast na łańcuchach znaków takiej operacji wykonać nie można. Liczby dziesiętne (mające jakieś cyfry po przecinku dziesiętnym) są typu float. Przykładami liczb tego typu są 2.1, 8.2 czy też 9.9999. Są one nazywane liczbami zmiennoprzecinkowymi. Podobnie jak wartości innych typów, także liczby zmiennoprzecinkowe mają swoje własne unikalne cechy i zachowują się w określony sposób, podobny do liczb całkowitych: 1 # python_lst018.py 2 3 2.2 + 2.2

>> 4.4

Obiekty typu bool są określane jako wartości logiczne i mogą przyjmować wartości True lub False: 1 # python_lst19.py 2 3 True

>> True 1 # python_lst20.py 2 3 False

>> False

I w końcu obiekty, których typem danych jest NoneType, zawsze mają wartość None. Służą one do reprezentacji braku wartości: 1 # python_lst21.py 2 3 None

Dalej w tym rozdziale wyjaśnię, jak stosować różne typy danych.

a9c8a50d59776690f264c310a5514dcc a

33

Stałe i zmienne

Stałe i zmienne Języka Python można używać do wykonywania obliczeń matematycznych, tak samo jak korzystamy z kalkulatora. Liczby można dodawać, odejmować, dzielić, mnożyć, podnosić do potęgi i tak dalej. Pamiętaj, że wszystkie przykłady prezentowane w tym podrozdziale należy wpisywać i wykonywać w interaktywnej powłoce: 1 # python_lst22.py 2 3 2+2

>> 4 1 # python_lst23.py 2 3 2 - 2

>> 0 1 # python_lst24.py 2 3 4 / 2

>> 2 1 # python_lst25.py 2 3 2 * 2

>> 4

Stała jest wartością, która nigdy nie ulega zmianie. Każda z liczb zastosowanych w kilku ostatnich przykładach była stałą; liczba 2 zawsze będzie reprezentować wartość 2. Z drugiej strony, zmienna odnosi się do wartości, która może się zmieniać. Każda zmienna ma nazwę, która składa się z co najmniej jednego znaku. Nazwa jest przypisywana zmiennej przy użyciu operatora przypisania (znaku równości, =). Niektóre języki programowania wymagają od programisty „deklarowania” zmiennych w celu poinformowania języka, jakiego typu będzie dana zmienna. Przykładowo w języku C zmienne są tworzone w następujący sposób: 1 # python_lst026.py 2 3 # Nie uruchamiać. 4 5 int a; 6 a = 144;

W Pythonie tworzenie zmiennych jest prostsze: wystarczy przypisać zmiennej wartość, używając w tym celu operatora przypisania: 1 # python_lst027.py 2 3 b = 100 4 b

>> 100

a9c8a50d59776690f264c310a5514dcc a

34

Rozdział 3



Wprowadzenie do programowania

Kolejny przykład pokazuje, w jaki sposób można zmieniać wartość zmiennej: 1 # python_lst028.py 2 3 x = 100 4 x 5 6 7 x = 200 8 x

>> 100 >> 200

Takich dwóch zmiennych można także używać do wykonywania operacji arytmetycznych: 1 # python_lst029.py 2 3 x = 10 4 y = 10 5 z = x + y 6 z 7 a = x - y 8 a

>> 20 >> 0

W kodzie programu może się zdarzyć, że będziemy chcieli inkrementować (powiększyć) lub dekrementować (zmniejszyć) wartość jakiejś zmiennej. Ponieważ są to bardzo popularne operacje, Python udostępnia specjalną skróconą składnię do ich wykonywania. Aby inkrementować wartość zmiennej, należy ją przypisać do niej samej, a dodatkowo po prawej stronie operatora przypisania umieścić znak plus i liczbę, o którą chcemy powiększyć wartość zmiennej; oto przykład takiego działania: 1 # python_lst030.py 2 3 x = 10 4 x = x + 1 5 x

>> 11

I analogicznie, aby dekrementować wartość zmiennej, należy postąpić tak samo, z tym że zamiast dodawać, należy odjąć liczbę: 1 # python_lst031.py 2 3 x = 10 4 x = x - 1 5 x

>> 9

a9c8a50d59776690f264c310a5514dcc a

35

Stałe i zmienne

Te przykłady są całkowicie prawidłowe, jednak istnieje skrócony sposób zapisu tych samych operacji: 1 # python_lst032.py 2 3 x = 10 4 x += 1 5 x

>> 11 1 # python_lst033.py 2 3 x = 10 4 x -= 1 5 x

>> 9

W zmiennych można także zapisywać wartości innych typów, a nie tylko liczby całkowite. Można tworzyć zmienne każdego z istniejących typów danych: 1 # python_lst34.py 2 3 hi = "Witaj, świecie!" 1 # python_lst35.py 2 3 my_float = 2.2 1 # python_lst36.py 2 3 my_boolean = True

Nazwy zmiennych mogą być dowolne, przy czym trzeba pamiętać o zachowaniu kilku zasad. 1. W nazwach zmiennych nie mogą występować znaki odstępu. Jeśli chcemy, by nazwa zmiennej składała się z dwóch słów, należy je rozdzielić znakiem podkreślenia, na przykład moja_zmienna = "Łańcuch znaków!". 2. Nazwy zmiennych mogą się składać tylko z liter, cyfr oraz znaku podkreślenia. 3. Nazwy zmiennych nie mogą zaczynać się od cyfry. Choć można na ich początku umieszczać znak podkreślenia, jednak takie rozwiązanie ma specjalne znaczenie (o którym piszę nieco później), więc aż do tego momentu należy go unikać. 4. Nazwy zmiennych nie mogą być słowami kluczowymi języka Python; listę słów kluczowych można znaleźć na stronie https://theselftaughtprogrammer.io/keywords.

a9c8a50d59776690f264c310a5514dcc a

36

Rozdział 3



Wprowadzenie do programowania

Składnia Składnia to zespół reguł, zasad i procesów określających strukturę zdań w danym języku, a zwłaszcza kolejność słów1. Tak samo jak język angielski ma swoją składnię, ma ją także i Python. W języku Python łańcuchy znaków zawsze są zapisywane w cudzysłowach lub apostrofach. To właśnie przykład składni Pythona. Poniżej przedstawiony został prawidłowy program napisany w tym języku: # python_lst037.py print("Witaj, świecie!")

Jest prawidłowy, gdyż jest zgodny ze składnią Pythona — zdefiniowany w tym kodzie łańcuch znaków został zapisany w cudzysłowach. Gdybyśmy jednak umieścili tylko jeden cudzysłów na jednym z końców łańcucha, byłoby to sprzeczne z regułami składni Pythona i kod nie zostałby wykonany.

Błędy i wyjątki Jeśli pisząc program w Pythonie, nie będziemy się stosować do zasad jego składni, podczas wykonywania takiego programu mogą się pojawić błędy. Interaktywna powłoka Pythona poinformuje nas, jeśli kod programu nie będzie działać, i wyświetli informacje o przyczynie problemów. Przekonajmy się, co się stanie, kiedy spróbujemy zdefiniować łańcuch znaków, lecz zapomnimy o jednym znaku cudzysłowu: 1 # python_lst038.py 2 3 # W tym kodzie jest błąd! 4 5 my_string = "Witaj, świecie!.

>>

File "H:\Self-Taught Programmer\kody\python\python_lst038.py", line 5 my_string = "Witaj, świecie! ^ SyntaxError: EOL while scanning string literal

Wyświetlony komunikat informuje o wykryciu w programie błędu składniowego (SyntaxError). Błędy tego typu są błędami krytycznymi. Kod zawierający taki błąd nie może zostać wykonany. W przypadku próby wykonania programu zawierającego błąd składniowy Python poinformuje o jego wykryciu. Wyświetlony komunikat będzie zawierał nazwę pliku zawierającego błąd, numer wiersza, w którym został wykryty, oraz rodzaj błędu. Choć takie błędy mogą się wydawać trochę przerażające, zdarzają się często. Kiedy okaże się, że w pisanym kodzie wystąpił błąd, należy przejść do wiersza, w którym został wykryty, i postarać się określić jego przyczynę. W powyższym przypadku musielibyśmy przejść do trzeciego wiersza kodu. Po chwili analizy kodu na pewno zauważylibyśmy, że zawiera on tylko 1

https://pl.wikipedia.org/wiki/Syntaktyka_(językoznawstwo)

a9c8a50d59776690f264c310a5514dcc a

37

Operatory arytmetyczne

jeden znak cudzysłowu. Aby poprawić ten błąd, wystarczy dodać drugi znak cudzysłowu na końcu łańcucha i ponownie wykonać program. Od tej chwili wszelkie błędy będę prezentował w książce w następujący sposób: >> SyntaxError: EOL while scanning string literal

Aby ułatwić lekturę, będę przedstawiał tylko ostatni wiersz komunikatu o błędzie. W języku Python występują dwa rodzaje błędów; są to błędy składniowe i wyjątki. To, co nie jest błędem składniowym, jest wyjątkiem. Przykładowo ZeroDivisionError to wyjątek występujący w przypadku wykrycia próby dzielenia przez zero. W odróżnieniu od błędów składniowych, wyjątki wcale nie muszą być krytyczne (istnieje sposób, który pozwala na dalsze wykonywanie programu nawet po zgłoszeniu wyjątku, który przedstawię w następnym rozdziale). W przypadku zgłoszenia wyjątku programiści używający Pythona mówią: „Python (lub mój program) zgłosił wyjątek”. Oto przykład kodu, który zgłasza wyjątek: 1 # python_lst039.py 2 3 # W tym kodzie jest błąd. 4 5 10 / 0

>> ZeroDivisionError: integer division or modulo by zero

W przypadku zastosowania nieprawidłowych wcięć zostanie zgłoszony wyjątek IndentationError: 1 # pyton_lst040.py 2 3 # W tym kodzie jest błąd. 4 y = 2 5 x = 1

>> IndentationError: unexpected indent

Podczas nauki programowania takie błędy składniowe oraz wyjątki (w tym także te, o których jeszcze nie wspominałem) będą występować dość często, lecz z upływem czasu będzie ich coraz mniej. Pamiętaj, że w razie wystąpienia błędu składniowego lub wyjątku trzeba przejść do wiersza, w którym został wykryty, i spróbować znaleźć rozwiązanie (jeśli utkniesz i nie będziesz mieć pomysłu, co zrobić z danym błędem, poszukaj rozwiązania w internecie).

Operatory arytmetyczne Wcześniej przedstawione zostały jedynie przykłady prostych obliczeń arytmetycznych, takich jak 4 / 2. Symbole, których w nich używaliśmy, są nazywane operatorami. Python dzieli te operatory na kilka kategorii, a te, które mogliśmy zobaczyć w przykładach, są nazywane operatorami arytmetycznymi. Poniższa tabela 3.1. zawiera niektóre najczęściej stosowane operatory arytmetyczne dostępne w języku Python.

a9c8a50d59776690f264c310a5514dcc a

38

Rozdział 3



Wprowadzenie do programowania

Tabela 3.1. Najczęściej stosowane operatory arytmetyczne Operator

Znaczenie

Przykład

Wynik

**

Potęgowanie

2 ** 2

4

%

Modulo / reszta z dzielenia

14 % 4

2

//

Dzielenie całkowite / iloraz zaokrąglony w dół

13 // 8

1

/

Dzielenie

13 / 8

1.625

*

Mnożenie

8 * 2

16

-

Odejmowanie

7 - 1

6

+

Dodawanie

2 + 2

4

Po podzieleniu dwóch liczb uzyskujemy iloraz oraz resztę z dzielenia. Iloraz to wynik dzielenia, natomiast resztą jest to, co zostaje po dzieleniu. Resztę z dzielenia zwraca operator modulo. Przykładowo 13 podzielone przez 5 daje 2 i resztę 3. 1 # python_lst041.py 2 3 13 // 5

>> 2 1 # python_lst042.py 2 3 13 % 5

>> 3

Kiedy zastosowano operator modulo z użyciem liczby 2 jako drugim argumentem, a nie ma żadnej reszty z dzielenia (zwracaną wartością jest 0), oznacza to, że liczba będąca pierwszym argumentem jest parzysta. Jeśli wynik będzie różny od zera, oznacza to, że liczba jest nieparzysta: 1 # python_lst043.py 2 3 # liczba parzysta 4 12 % 2

>> 0 1 #python_lst044.py 2 3 # liczba nieparzysta 4 11 % 2

>> 1

Dostępne są jeszcze dwa inne operatory służące do dzielenia liczb. Pierwszy z nich — // — zwraca iloraz: 1 # python_lst045.py 2 3 14 // 3

>> 4

a9c8a50d59776690f264c310a5514dcc a

39

Operatory arytmetyczne

Drugim jest /; zwraca on liczbę zmiennoprzecinkową stanowiącą wynik dzielenia pierwszego argumentu przez drugi: 1 # python_lst046.py 2 3 14 / 3

>> 4.666666666666667

Do potęgowania liczb używany jest operator wykładniczy: 1 # python_lst047.py 2 3 2 ** 2

>> 4

Wartości (w powyższych przykładach były nimi liczby) umieszczane po obu stronach operatora są nazywane operandami. Operandy wraz z operatorem tworzą wyrażenie. Podczas działania programu Python przetwarza wszystkie wyrażenia i dla każdego z nich zwraca pojedynczą wartość. Kiedy wpiszemy wyrażenie 2+2 w interaktywnej powłoce, Python zwróci wartość 4. Kolejność wykonywania działań to zestaw reguł używany w wyrażeniach matematycznych do wyznaczania ich wartości. Zapamiętaj zdanie: Nowy Paradygmat Matematyki Daje Dużo Okazji. To akronim, który pomoże zapamiętać kolejność działań w równaniach matematycznych: nawiasy, potęgowanie, mnożenie, dzielenie, dodawanie i odejmowanie. Nawiasy mają najwyższy priorytet — wyższy od potęgowania, które z kolei ma wyższy priorytet od mnożenia i dzielenia, a te z kolei są ważniejsze od dodawania i odejmowania. Jeśli operatory mają ten sam priorytet, jak w wyrażeniu 15 / 3 * 2, działania są wykonywane od lewej do prawej. W tym przypadku wartością wyrażenia jest wynik dzielenia 15 przez 3 pomnożony razy 2. Python wykorzystuje dokładnie te same zasady podczas wyznaczania wartości wyrażeń matematycznych: 1 # python_lst048.py 2 3 2 + 2 * 2

>> 6 1 # python_lst049.py 2 3 (2 + 2) * 2

>> 8

W pierwszym przykładzie działanie 2 * 2 jest wykonywane jako pierwsze, gdyż operator mnożenia ma wyższy priorytet niż dodawanie. W drugim przykładzie najpierw jest obliczana wartość (2+2), gdyż Python zawsze w pierwszej kolejności oblicza wartość wyrażeń zapisanych w nawiasach.

a9c8a50d59776690f264c310a5514dcc a

40

Rozdział 3



Wprowadzenie do programowania

Operatory porównania Kolejną grupą operatorów stosowanych w języku Python są operatory porównania (przedstawione w tabeli 3.2). Podobnie jak operatory arytmetyczne, także operatory porównania są używane w wyrażeniach i umieszczane pomiędzy dwoma operandami. Jednak w odróżnieniu od operatorów arytmetycznych, wyrażenia z operatorami porównania zwracają jedną z dwóch wartości: True lub False. Tabela 3.2. Operatory porównania Operator

Znaczenie

Przykład

Wynik

>

Większe

100 > 10

True


=

Większe lub równe

2 >= 2

True

10

>> True

Wyrażenie z operatorem < zwróci wartość True, jeśli liczba po lewej stronie operatora będzie mniejsza od liczby umieszczonej po jego prawej stronie; w przeciwnym razie wyrażenie zwróci wartość False: 1 # python_lst051.py 2 3 100 < 10

>> False

Wyrażenie z operatorem >= zwróci wartość True, jeśli liczba umieszczona po jego lewej stronie będzie większa od liczby umieszczonej po prawej stronie lub jej równa; w przeciwnym razie wyrażenie zwróci wartość False: 1 # python_052.py 2 3 2 >= 2

>> True

Wyrażenie z operatorem True

Wyrażenie z operatorem == zwróci wartość True, jeśli oba operandy będą sobie równe, oraz wartość False w przeciwnym przypadku: 1 # python_054.py 2 3 2 == 2

>> True 1 # python_055.py 2 3 1 == 2

>> False

Wyrażenie z operatorem != zwróci wartość True, jeśli oba operandy będą różne, oraz wartość False w przeciwnym przypadku: 1 # python_056.py 2 3 1 != 2

>> True 1 # python_057.py 2 3 2 != 2

>> False

Wcześniej przypisywaliśmy wartości zmiennym, używając znaku =, na przykład x = 100. Można by sądzić, że taki zapis należy czytać „x jest równe 100”, jednak wcale tak nie jest. Jak już mieliśmy okazję się przekonać, znak = jest używany do przypisywania wartości zmiennym, a nie do sprawdzania równości dwóch wartości. A zatem, kiedy zobaczymy zapis x = 100, powinniśmy go rozumieć jako „przypisz x wartość 100”. Do testowania równości jest używany operator ==; a zatem kiedy zobaczymy zapis x == 100, wtedy należy go zinterpretować jak „czy x jest równe 100”.

Operatory logiczne Kolejną kategorią operatorów stosowanych w języku Python są operatory logiczne (przedstawione tabeli 3.3). Podobnie jak operatory porównania, także i operatory logiczne zwracają wartość True lub False.

a9c8a50d59776690f264c310a5514dcc a

42

Rozdział 3



Wprowadzenie do programowania

Tabela 3.3. Operatory logiczne Operator

Znaczenie

Przykład

Wynik

and

Koniunkcja logiczna

True and True

True

or

Alternatywa logiczna

True or False

True

not

Negacja

not True

False

W języku Python operator and ma dwa operandy, którymi mogą być wyrażenia, i zwraca wartość True wyłącznie w przypadku, jeśli oba mają wartość True. Jeśli którekolwiek z wyrażeń użytych jako operandy przyjmie wartość False, operator and także zwróci False: 1 # python_lst058.py 2 3 1 == 1 and 2 == 2

>> True 1 # python_lst059.py 2 3 1 == 2 and 2 == 2

>> False 1 # python_lst060.py 2 3 1 == 2 and 2 == 1

>> False 1 # python_lst061.py 2 3 2 == 1 and 1 == 1

>> False

W jednej instrukcji można wielokrotnie użyć operatora and: 1 # python_lst062.py 2 3 1 == 1 and 10 != 2 and 2 < 10

>> True

Także operator or wymaga podania dwóch operandów, lecz wartość True zwraca, jeśli którykolwiek z nich przyjmie wartość True; na przykład: 1 # python_lst063.py 2 3 1==1 or 1==2

>> True 1 # python_lst064.py 2 3 1==1 or 2==2

>> True

a9c8a50d59776690f264c310a5514dcc a

43

Instrukcje warunkowe

1 # python_lst065.py 2 3 1==2 or 2==1

>> False 1 # python_lst066.py 2 3 2==1 or 1==2

>> False

Podobnie jak operatora and, także i or można wielokrotnie używać w jednej instrukcji: 1 # python_lst067.py 2 3 1==1 or 1==2 or 1==3

>> True

To wyrażenie zwróci wartość True, gdyż 1==1 ma wartość True; fakt, że pozostałe dwa porównania przyjmą wartość False, nie ma już znaczenia. Umieszczenie słowa kluczowego not przed wyrażeniem zmieni jego wyznaczoną wartość na przeciwną. A zatem, jeśli wyrażenie zwróciłoby wartość True, poprzedzone operatorem not zwróci False: 1 # python_lst068.py 2 3 not 1 == 1

>> False 1 # python_lst069.py 2 3 not 1 == 2

>> True

Instrukcje warunkowe Słowa kluczowe if, elif oraz else to tak zwane instrukcje warunkowe. Instrukcje warunkowe to jeden z rodzajów struktur sterujących — są to bloki kodu, które podejmują decyzje na podstawie analizy wartości zmiennych. Instrukcja warunkowa to kod, który warunkowo wykonuje inny blok kodu. Poniżej przedstawiony został przykład pseudokodu (zapisu przypominającego kod programu i mającego na celu zilustrowanie przykładu), który wyjaśnia, jak działają instrukcje warunkowe: 1 2 3 4 5 6 7

# python_lst070.py # Proszę tego nie wykonywać! If (wyrażenie) Then (blok_kodu_1) Else (blok_kodu_2)

a9c8a50d59776690f264c310a5514dcc a

44

Rozdział 3



Wprowadzenie do programowania

Powyższy pseudokod pokazuje, że można zdefiniować dwie instrukcje warunkowe, które będą działać wspólnie. Jeśli wyrażenie zdefiniowane w pierwszej instrukcji warunkowej przyjmie wartość True, zostanie wykonany cały kod umieszczony w bloku blok_kodu_1. Jeśli jednak wyrażenie zdefiniowane w pierwszej instrukcji warunkowej przyjmie wartość False, zostanie wykonany blok blok_kodu_2. Pierwsza część tego przykładu jest nazywana instrukcją if, a druga — instrukcją else; ich połączenie tworzy natomiast instrukcję if-else, która odpowiada programistycznemu sposobowi stwierdzenia: „jeśli stanie się to, zrób to, a w przeciwnym razie zrób tamto”. Poniżej przedstawiony został przykład zastosowania instrukcji if-else w Pythonie: 1 2 3 4 5 6 7 8

# python_lst071.py home = "Ameryka" if home == "Ameryka": print("Witaj, Ameryko!") else: print("Witaj, świecie!")

>> Witaj, Ameryko!

Wiersze 5. i 6. tworzą instrukcję if. Instrukcja ta składa się z wiersza kodu zaczynającego się od słowa kluczowego if, po którym jest umieszczone wyrażenie, dwukropka oraz wcięcia i jednego lub większej liczby wierszy kodu, które zostaną wykonane, jeśli wyrażenie przyjmie wartość True. Z kolei wiersze 7. i 8. tworzą instrukcję else. Jak widać, zaczyna się ona od słowa kluczowego else, po którym jest umieszczony dwukropek, wcięcie i jeden lub większa ilość wierszy kodu, które zostaną wykonane, gdy wyrażenie umieszczone w instrukcji if przyjmie wartość False. Połączenie obu tych konstrukcji tworzy instrukcję if-else. Wykonanie powyższego przykładu spowoduje wyświetlenie komunikatu Witaj, Ameryko!, gdyż warunek umieszczony w instrukcji if będzie mieć wartość True. Gdybyśmy zmienili wartość zmiennej home na Canada, wyrażenie miałoby wartość False, a to z kolei oznaczałoby, że zostanie wykonany kod umieszczony w instrukcji else, czyli przykład wyświetliłby komunikat Witaj, świecie!. 1 2 3 4 5 6 7 8

# python_lst072.py home = "Kanada" if home == "Ameryka": print("Witaj, Ameryko!") else: print("Witaj, świecie!")

>> Witaj, świecie!

Instrukcję if można też stosować samodzielnie: 1 # python_lst073.py 2 3 4 home = "Ameryka" 5 if home == "Ameryka": 6 print("Witaj, Ameryko!")

>> Witaj, Ameryko!

a9c8a50d59776690f264c310a5514dcc a

45

Instrukcje warunkowe

Można także użyć kilku instrukcji if jedna za drugą: 1 2 3 4 5 6 7 8 9 10

# python_lst074.py

x = 2 if x == 2: print("Liczba jest równa 2.") if x % 2 == 0: print("Liczba jest parzysta.") if x % 2 != 0: print("Liczba jest nieparzysta.")

>> Liczba jest równa 2. >> Liczba jest parzysta.

Kod umieszczony w każdej instrukcji if zostanie wykonany wyłącznie w przypadku, gdy użyte w niej wyrażenie zwróci wartość True. W powyższym przykładzie wartość True będą miały wyrażenia zastosowane w pierwszych dwóch instrukcjach if. Wyrażenie użyte w trzeciej instrukcji if będzie mieć wartość False, dlatego umieszczony w niej kod nie zostanie wykonany. Jeśli ktoś chce zaszaleć, może nawet umieszczać jedne instrukcje if w innych (takie rozwiązanie jest nazywane zagnieżdżaniem): 1 2 3 4 5 6 7 8 9 10

# python_lst075.py x = 10 y = 11 if x == 10: if y == 11: print(x + y)

>> 21

W tym przykładzie wartość wyrażenia x + y zostanie wyświetlona wyłącznie w przypadku, gdy wyrażenia podane w obu instrukcjach if przyjmą wartość True. Instrukcji else nie można używać samodzielnie, może ona zostać umieszczona wyłącznie na końcu instrukcji if-else. Można także użyć słowa kluczowego elif w celu utworzenia instrukcji elif. Instrukcja elif to połączenie dwóch angielskich słów, czyli else oraz if. Instrukcje elif można wielokrotnie dodawać do instrukcji if-else, aby definiować kolejne decyzje. Jeśli do instrukcji if-else jest dołączona instrukcja elif, najpierw zostanie przetworzona instrukcja if. Jeśli podane w niej wyrażenie przyjmie wartość True, zostanie wykonany wyłącznie jej kod. Jeśli jednak wyrażenie to przyjmie wartość False, zostanie sprawdzona wartość wyrażenia podanego w następnej instrukcji elif. Jeśli będzie ono mieć wartość True, zostanie wykonany kod umieszczony wewnątrz tej instrukcji elif i na tym zakończy się wykonywanie całej sekwencji instrukcji warunkowych. Jeśli żadne z wyrażeń podanych w instrukcjach elif nie będzie mieć wartości True, zostanie wykonany kod umieszczony w instrukcji else. Poniżej przedstawiony został przykład instrukcji if-else z dołączoną instrukcją elif:

a9c8a50d59776690f264c310a5514dcc a

46

Rozdział 3

1 2 3 4 5 6 7 8 9 10 11 12 13 14



Wprowadzenie do programowania

# python_lst076.py

home = "Tajlandia" if home == "Japonia": print("Witaj, Japonio!") elif home == "Tajlandia": print("Witaj, Tajlandio!") elif home == "India": print("Witajcie, Indie!") elif home == "China": print("Witajcie, Chiny!") else: print("Witaj, świecie!")

>> Witaj, Tajlandio!

W kolejnym przykładzie żaden z warunków umieszczonych w instrukcjach elif nie przyjmuje wartości True, przez co zostanie wykonana instrukcja else: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

# python_lst077.py

home = "Mars" if home == "Ameryka": print("Witaj, Ameryko!") elif home == "Canada": print("Witaj, Kanado!") elif home == "Thailand": print("Witaj, Tajlandio!") elif home == "Mexico": print("Witaj, Meksyku!") else: print("Witaj, świecie!")

>> Witaj, świecie!

I w końcu nic nie stoi na przeszkodzie, by jednocześnie użyć większej ilości instrukcji if oraz elif: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

# python_lst078.py

x = 100 if x == 10: print("10!") elif x == 20: print("20!") else: print("Nie wiem!") if x == 100: print("x jest równe 100!") if x % 2 == 0:

a9c8a50d59776690f264c310a5514dcc a

47

Instrukcje

18 print("x jest parzyste!") 19 else: 20 print("x jest nieparzyste!")

>> Nie wiem! >> x jest równe 100! >> x jest parzyste!

Instrukcje Instrukcja to termin techniczny opisujący różne części języka Python. Taką instrukcję języka Python można sobie wyobrazić jako polecenie lub obliczenie. W tym podrozdziale przyjrzymy się dokładniej składni instrukcji. Nie przejmuj się, jeśli na początku wszystko będzie się wydawać onieśmielające i trudne. Im więcej czasu będziesz poświęcał na programowanie w Pythonie, tym bardziej to wszystko będzie zrozumiałe, a nawet pomocne w rozumieniu innych pojęć związanych z programowaniem. Python udostępnia dwa rodzaje instrukcji — proste i złożone. Instrukcje proste można zapisać w jednym wierszu kodu, natomiast złożone zazwyczaj zajmują kilka. Poniżej przedstawione zostały przykłady instrukcji prostych: 1 # python_lst079.py 2 3 4 print("Witaj, świecie!")

>> Witaj, świecie! 1 # python_lst080.py 2 3 4 2 + 2

>> 4

Instrukcje if, if-else oraz pierwszy program, który napisaliśmy w tym rozdziale, wyświetlający Witaj, świecie! sto razy, były przykładami instrukcji złożonych. Instrukcje złożone składają się z jednej lub większej liczby klauzul. Z kolei klauzula składa się z dwóch lub większej liczby wierszy — nagłówka oraz zestawu (zestawów). Nagłówek to wiersz kodu klauzuli, który zawiera słowo kluczowe wraz z dwukropkiem, za którym umieszczany jest jeden lub więcej odpowiednio wciętych wierszy kodu. Zestaw to po prostu wiersz kodu należący do klauzuli. Zestawy podlegają nagłówkowi. Nasz program wypisujący sto razy Witaj, świecie! składa się z jednej instrukcji złożonej: 1 # python_lst081.py 2 3 4 for i in range(100): 5 print("Witaj, świecie!")

a9c8a50d59776690f264c310a5514dcc a

48

Rozdział 3



Wprowadzenie do programowania

>> Witaj, świecie! >> Witaj, świecie! >> Witaj, świecie! ...

Pierwszy wiersz kodu programu, czyli wiersz 4., jest nagłówkiem. Zawiera on słowo kluczowe for, a na jego końcu jest umieszczony dwukropek. Poniżej, poprzedzony wcięciem znajduje się zestaw — instrukcja print("Witaj, świecie!"). W tym przypadku nagłówek używa zestawu do wyświetlenia tekstu Witaj, świecie! sto razy. Taki kod jest nazywany pętlą; więcej na temat pętli dowiesz się w rozdziale 7. Kod przedstawiony w tym przykładzie zawiera tylko jedną klauzulę. Jednak instrukcje złożone mogą się składać z wielu klauzul. Już wcześniej przedstawione zostały przykłady takich instrukcji — konkretnie rzecz biorąc, była nim instrukcja if-else. Zawsze wtedy, gdy za instrukcją if jest umieszczona instrukcja else, mamy do czynienia z instrukcją złożoną składającą się z kilku klauzul. Kiedy instrukcja złożona składa się z kilku klauzul, jej nagłówek oraz pozostałe klauzule współpracują ze sobą. Kiedy w instrukcji if-else warunek w instrukcji if przyjmie wartość True, wykonany zostanie zestaw z tej instrukcji, w przeciwnym razie zostanie wykonany zestaw z instrukcji else. Ostatni przykład z poprzedniego podrozdziału składa się z trzech instrukcji złożonych: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# python_lst082.py

x = 100 if x == 10: print("10!") elif x == 20: print("20!") else: print("Nie wiem!") if x == 100: print("x jest równe 100!") if x % 2 == 0: print("x jest parzyste!") else: print("x jest nieparzyste!")

>> Nie wiem! >> x jest równe 100! >> x jest parzyste!

Pierwsza z instrukcji złożonych przedstawionych w tym przykładzie składa się z trzech klauzul, druga tylko z jednej, a trzecia z dwóch. I jeszcze ostatnia rzecz dotycząca instrukcji: pomiędzy nimi należy umieszczać odstępy. Choć nie mają one znaczenia dla samych instrukcji, jednak mogą poprawić czytelność kodu, co pokazano na poniższym przykładzie:

a9c8a50d59776690f264c310a5514dcc a

49

Słownictwo

1 # python_lst083.py 2 3 4 print("Michael") 5 6 7 8 9 10 11 print("Jordan")

>> Michael >> Jordan

Słownictwo Błąd składniowy. Błąd krytyczny spowodowany naruszeniem zasad składni języka. Bool. Typ danych reprezentujący obiekty logiczne. Boolean. Obiekt typu bool. Jego wartością jest True lub False. Dekrementacja. Zmniejszenie wartości zmiennej. Float. Typ danych reprezentujący liczby dziesiętne. Inkrementacja. Powiększenie wartości zmiennej. Instrukcja elif. Instrukcje, które można bez końca dodawać do instrukcji if-else, aby umożliwić podejmowanie dodatkowych decyzji. Instrukcja if. Pierwsza część instrukcji if-else. Instrukcja if-else. Sposób używany przez programistów do stwierdzenia: „Jeśli stanie się to, zrób to, a w przeciwnym razie zrób tamto”. Instrukcja prosta. Instrukcja, którą można zapisać w jednym wierszu kodu. Instrukcja warunkowa. Kod, który warunkowo może spowodować wykonanie innego kodu. Instrukcja złożona. Instrukcja, która zazwyczaj zajmuje więcej niż jeden wiersz kodu. Instrukcja. Polecenie lub obliczenie. Int. Typ danych reprezentujący liczby całkowite. Klauzula. Element konstrukcyjny instrukcji złożonych. Klauzula składa się z dwóch lub większej liczby wierszy kodu: nagłówka i zestawu (lub zestawów). Kolejność operacji. Zestaw reguł stosowanych w obliczeniach matematycznych w celu wyznaczania wartości wyrażeń.

a9c8a50d59776690f264c310a5514dcc a

50

Rozdział 3



Wprowadzenie do programowania

Komentarz. Wiersz (lub część wiersza) kodu napisany w normalnym języku używanym przez ludzi, poprzedzony specjalnym znakiem informującym język programowania, że dalszą część wiersza należy zignorować. Liczba całkowita. Obiekt typu int. Jego wartością jest liczba całkowita. Liczba zmiennoprzecinkowa. Obiekt typu float. Jego wartością jest liczba dziesiętna. Łańcuch znaków. Obiekt typu str. Jego wartość to sekwencja składająca się z jednego lub większej liczby znaków zapisanych pomiędzy cudzysłowami lub apostrofami. Nagłówek. Wiersz kodu klauzuli składający się ze słowa kluczowego, następnie dwukropka oraz sekwencji jednego lub większej liczby wciętych wierszy kodu. None. Obiekt typu NoneType. Jego wartością jest zawsze None. NoneType. Typ danych obiektów None. Obiekt. W języku Python to dana mająca trzy właściwości: tożsamość, typ danych oraz wartość. Operand. Wartość zapisana po każdej ze stron operatora. Operator arytmetyczny. Kategoria operatorów używanych w wyrażeniach arytmetycznych. Operator logiczny. Kategoria operatorów służących do łączenia dwóch wyrażeń i zwracania wartości True lub False. Operator porównania. Kategoria operatorów używanych w wyrażeniach, które mają w wyniku zwrócić wartość True lub False. Operator przypisania. W Pythonie to znak =. Operatory. Symbole używane wraz z operandami w wyrażeniach. Pseudokod. Zapis przypominający kod, używany do obrazowania przykładów. Składnia. Zestaw reguł, zasad i procesów zarządzających strukturą zdań w danym języku, zwłaszcza kolejnością słów2. Słowo kluczowe. Słowo mające specjalne znaczenie dla języka programowania. Listę wszystkich słów kluczowych stosowanych w języku Python można znaleźć na stronie https://theselftaughtprogrammer.io/keywords. Stała. Wartość, która nigdy się nie zmienia. Str. Typ danych reprezentujący łańcuchy znaków. Struktura sterująca. Blok kodu, który podejmuje decyzje na podstawie analizy wartości zmiennych. Typ danych. Kategoria danych.

2

https://pl.wikipedia.org/wiki/Syntaktyka_(językoznawstwo)

a9c8a50d59776690f264c310a5514dcc a

51

Wyzwania

Wyjątek. Niekrytyczny błąd w programie. Wyrażenie. Kod składający się z operatora oraz jego dwóch operandów. Zestaw. Wiersz kodu klauzuli zarządzany przez nagłówek. Zmienna. Nazwa przypisana wartości przy użyciu operatora przypisania. Znak. Pojedynczy symbol, taki jak a lub 1.

Wyzwania 1. Wyświetl trzy różne łańcuchy znaków. 2. Napisz program wyświetlający komunikat, jeśli wartość zmiennej będzie mniejsza od 10 oraz inny komunikat, jeśli wartość tej zmiennej będzie większa lub równa 10. 3. Napisz program, który będzie wyświetlał komunikat, jeśli wartość zmiennej będzie mniejsza lub równa 10, inny komunikat, jeśli wartość tej zmiennej będzie większa od 10, lecz mniejsza lub równa 25 oraz jeszcze inny komunikat, kiedy wartość ta będzie większa od 25. 4. Napisz program, który podzieli dwie zmienne i wyświetli uzyskaną resztę z dzielenia. 5. Napisz program, który podzieli dwie zmienne i wyświetli ich iloraz. 6. Napisz program ze zmienną age przypisaną do liczby całkowitej, który w zależności od jej wartości będzie wyświetlał różne łańcuchy znaków. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

52

Rozdział 3

a9c8a50d59776690f264c310a5514dcc



Wprowadzenie do programowania

a

Rozdział

4 Funkcje

„Funkcje powinny robić jedną rzecz. I powinny robić to dobrze. Powinny robić tylko jedną rzecz”. — Robert C. Martin

W tym rozdziale dowiesz się o funkcjach — instrukcjach złożonych, które mogą pobierać dane wejściowe, wykonywać instrukcje i zwracać wynik, co pokazano na rysunku 4.1. Funkcje pozwalają na definiowanie i wielokrotne stosowanie możliwości funkcjonalnych w programach.

Rysunek 4.1. Poglądowa reprezentacja funkcji

53

a9c8a50d59776690f264c310a5514dcc a

54

Rozdział 4



Funkcje

Reprezentacja koncepcji Od tej pory będę używał nowej konwencji (czyli zaakceptowanego sposobu wykonywania pewnych czynności) do wyjaśniania nowych pojęć programistycznych. Oto przykład tej konwencji: print("[to_co_chcesz_wyświetlić]"). Prezentuje ona sposób korzystania z funkcji. Jak widać, w celu zademonstrowania tej koncepcji połączyłem kod w języku Python z nawiasami kwadratowymi. W przykładach, takich jak ten, wszystko — z wyjątkiem nawiasów kwadratowych oraz umieszczonego w nich tekstu — jest prawidłowym kodem w języku Python; natomiast podczas wykonywania przykładu nawiasy wraz z całą zawartością należy zastąpić prawidłowym kodem. Tekst umieszczony w nawiasach jest podpowiedzią dotyczącą kodu, którym należy go zastąpić. W Pythonie używa się nawiasów kwadratowych jako elementu składni, dlatego tam, gdzie trzeba je będzie stosować, w kodzie będę korzystał z podwójnych nawiasów.

Funkcje Wywołanie funkcji oznacza przekazanie jej danych wejściowych wymaganych do wykonania zawartych w niej instrukcji oraz zwrócenie wyniku. Każda informacja przekazywana do funkcji jest nazywana parametrem. Proces przesyłania parametru do funkcji jest określany „przekazywaniem” parametru. Funkcje w języku Python przypominają nieco funkcje matematyczne. Jeśli nie pamiętasz funkcji z lekcji algebry, poniżej przedstawiłem prosty przykład: 1 # python_lst084.py 2 3 # Nie próbuj tego wykonywać! 4 f(x) = x * 2

Lewa strona powyższej instrukcji definiuje funkcję f pobierającą jeden parametr x. Prawa strona tej instrukcji jest natomiast definicją funkcji, która używa przekazanego parametru (x) do wykonania obliczeń i zwrócenia wyniku. W powyższym przykładzie wartość funkcji została zdefiniowana jako podwojona wartość jej parametru. Zarówno w języku Python, jak i w algebrze funkcje są wywoływane przy użyciu zapisu [nazwa_funkcji]([parametry_rozdzielone_przecinkami]). Innymi słowy, w celu wywołania funkcji należy za jej nazwą umieścić parę nawiasów. Wewnątrz tych nawiasów umieszcza się wszelkie parametry oddzielone przecinkami. W przypadku funkcji matematycznej zdefiniowanej jako f(x) = 2 * x wartość f(2) wynosi 4, a f(10) wynosi 20.

a9c8a50d59776690f264c310a5514dcc a

55

Definiowanie funkcji

Definiowanie funkcji Aby zdefiniować funkcję w języku Python, należy wybrać dla niej nazwę, ustalić jej parametry, następnie określić, co funkcja ma robić, i w końcu zdefiniować wartość, którą funkcja będzie zwracać. Poniżej przedstawiona została składnia definicji funkcji: 1 # python_085.py 2 # Nie próbuj tego wykonywać! 3 4 def [nazwa_funkcji]([parameters]): 5 [definicja_funkcji]

Funkcję matematyczną f(x) = x * 2 w języku Python można zdefiniować w następujący sposób: 1 # python_lst086.py 2 3 def f(x): 4 return x * 2

Słowo kluczowe def informuje język, że definiujemy funkcję. Za nim podawana jest nazwa funkcji, która musi być zgodna z tymi samymi regułami, które określają nazwy zmiennych. Zwyczajowo w nazwach funkcji nigdy nie należy stosować wielkich liter, a poszczególne słowa tworzące te nazwy mają być oddzielane znakami podkreślenia — nazwa_funkcji. Za nazwą funkcji należy zapisać parę nawiasów. Wewnątrz nich można zdefiniować wszystkie parametry, które funkcja będzie pobierać. Za nawiasem zamykającym trzeba zapisać dwukropek i przejść do nowego wiersza, umieszczając na jego początku cztery odstępy (podobnie jak w przypadku wszystkich innych instrukcji złożonych). Każdy wiersz kodu rozpoczynający się od wcięcia o wielkości czterech znaków odstępu będzie traktowany jak definicja funkcji. Definicja funkcji przedstawionej na powyższym przykładzie składa się tylko z jednego wiersza — return x * 2. Słowo kluczowe return definiuje wartość zwracaną przez wywołanie funkcji, nazywaną także wartością wynikową funkcji. Tej samej składni, czyli [nazwa_funkcji]([parametry_rozdzielone_przecinkami]), można używać do wywoływania funkcji. Poniżej przedstawiony został przykład wywołania funkcji f zdefiniowanej w poprzednim przykładzie: 1 2 3 4 5 6

# python_lst087.py # ciąg dalszy # poprzedniego przykładu f(2)

Wykonanie tego kodu nie powoduje wyświetlenia żadnych wyników. Wynik zwrócony przez funkcję można zapisać w zmiennej i przekazać w wywołaniu funkcji print: 1 # python_lst088.py 2 3 # ciąg dalszy 4 # poprzedniego przykładu

a9c8a50d59776690f264c310a5514dcc a

56

Rozdział 4



Funkcje

5 6 result = f(2) 7 print(result)

>> 4

Zawsze wtedy, gdy konieczne jest użycie wartości zwracanej przez funkcję w dalszej części kodu, można ją zapisać w zmiennej, tak jak pokazano na poniższym przykładzie: 1 2 3 4 5 6 7 8 9 10 11 12 13

# python_lst089.py def f(x): return x + 1 z = f(4) if z == 5: print("z jest równe 5") else: print("z nie jest równe 5")

>> z jest równe 5

Funkcja może mieć jeden parametr, wiele parametrów bądź żadnego. Aby zdefiniować funkcję, która nie ma żadnego parametru, wystarczy zostawić nawiasy puste, tak jak na poniższym przykładzie: 1 2 3 4 5 6 7 8 9

# python_lst090.py

def f(): return 1 + 1 result = f() print(result)

>> 2

Jeśli funkcja ma pobierać więcej niż jeden parametr, należy je oddzielić od siebie przecinkami: 1 # python_lst091.py 2 3 def f(x, y, z): 4 return x + y + z 5 6 7 result = f(1, 2, 3) 8 print(result)

>> 6

I jeszcze jedno: funkcja wcale nie musi zawierać instrukcji return. Jeśli funkcja nie zawiera instrukcji return, oznacza to, że zwraca wartość None:

a9c8a50d59776690f264c310a5514dcc a

57

Funkcje wbudowane

1 2 3 4 5 6 7 8

# python_lst092.py def f(): z = 1 + 1 result = f() print(result)

>> None

Funkcje wbudowane Python jest wyposażony w bibliotekę funkcji dostarczanych wraz z językiem, które są nazywane funkcjami wbudowanymi. Funkcje te wykonują wszelkiego rodzaju obliczenia oraz zadania i są gotowe do użycia bez żadnego dodatkowego nakładu pracy z naszej strony. Widziałeś już jeden przykład takiej funkcji wbudowanej: większość programów przedstawionych wcześniej w tej książce używała funkcji print do wyświetlania łańcuchów znaków, takich jak "Witaj, świecie!". Przykładem innej funkcji wbudowanej jest len. Zwraca ona długość obiektu — na przykład długość łańcucha znaków (liczbę tworzących go znaków): 1 # python_lst093.py 2 3 len("Monty")

>> 5 1 # python_lst094.py 2 3 len("Python")

>> 6

Wbudowana funkcja str pobiera obiekt i zwraca nowy obiekt, którego typem danych będzie str. Można na przykład użyć jej, by skonwertować liczbę całkowitą na łańcuch znaków: 1 # python_lst095.py 2 3 str(100)

>> '100'

Z kolei funkcja int pobiera obiekt i zwraca obiekt typu int: 1 # python_lst096.py 2 3 int("1")

>> 1

Funkcja float pobiera obiekt i zwraca nowy obiekt, którego typem danych będzie float:

a9c8a50d59776690f264c310a5514dcc a

58

Rozdział 4



Funkcje

1 # python_lst097.py 2 3 float(100)

>> 100.0

Parametry przekazywane do funkcji str, int oraz float muszą zapewniać możliwość skonwertowania na — odpowiednio — łańcuch znaków, liczbę całkowitą oraz liczbę zmiennoprzecinkową. Funkcja str akceptuje jako parametr większość obiektów, jednak do funkcji int można przekazywać wyłącznie łańcuchy będące reprezentacją liczby całkowitej, a do funkcji float wyłącznie łańcuchy stanowiące reprezentacje liczb całkowitych lub zmiennoprzecinkowych: 1 2 3 4 5 6 7 8

# python_lst098.py int("110") int(20.54) float("16.4") float(99)

>> >> >> >>

110 20 16.4 99.0

Próba przekazania do funkcji int lub float parametru, którego funkcje te nie będą w stanie poprawnie skonwertować, spowoduje zgłoszenie wyjątku: 1 # python_lst099.py 2 3 int("Prince")

>> ValueError: invalid literal for int() with base 10: 'Prince'

Kolejną funkcją wbudowaną jest input, która pobiera dane od użytkownika używającego programu: 1 2 3 4 5 6 7 8

# python_lst100.py age = input("Ile masz lat:") int_age = int(age) if int_age < 21: print("Jesteś młody!") else: print("Rany... aleś ty stary!")

>> Ile masz lat:

Parametrem funkcji input jest łańcuch znaków, który funkcja wyświetla w oknie powłoki. Następnie użytkownik może wpisać odpowiedź, którą można zapisać w zmiennej — w powyższym przykładzie jest to zmienna age. W dalszej części programu używamy funkcji int, by skonwertować zmienną age z łańcucha znaków na liczbę całkowitą. Funkcja input pobiera dane od użytkownika w formie łańcucha znaków (obiektu typu str), lecz nam zależy na tym, by zmienna była typu int, żeby można ją było

a9c8a50d59776690f264c310a5514dcc a

59

Wielokrotne stosowanie funkcji

porównywać z innymi liczbami całkowitymi. Po wykonaniu tej konwersji używamy instrukcji if-else, by na podstawie informacji podanej przez użytkownika określić, jaki komunikat wyświetlić. Jeśli wartość wpisana przez użytkownika będzie mniejsza od 21, wyświetlamy komunikat Jesteś młody!; a w przeciwnym razie komunikat Rany... aleś ty stary!.

Wielokrotne stosowanie funkcji Funkcje nie są używane wyłącznie do wykonywania obliczeń i zwracania wartości. Mogą one także hermetyzować logikę, której chcemy wielokrotnie używać: 1 2 3 4 5 6 7 8 9 10 11 12

# python_lst101.py

def even_odd(x): if x % 2 == 0: print("parzysta") else: print("nieparzysta") even_odd(2) even_odd(3)

>> parzysta >> nieparzysta

W tym przykładzie funkcja nie zwraca żadnej wartości, lecz i tak jest przydatna. Sprawdza wartość wyrażenia x % 2 == 0 i w zależności od tego, czy wartość x jest parzysta, czy nieparzysta, wyświetla stosowny komunikat. Funkcja pozwalają pisać mniej kodu, gdyż dzięki nim możemy wielokrotnie używać tych samych możliwości funkcjonalnych. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

# python_lst102.py

n = input("wpisz liczbę: ") n = int(n) if n % 2 == 0: print("liczba jest parzysta.") else: print("liczba jest nieparzysta.") n = input("wpisz liczbę: ") n = int(n) if n % 2 == 0: print("liczba jest parzysta.") else: print("liczba jest nieparzysta.")

a9c8a50d59776690f264c310a5514dcc a

60

Rozdział 4

20 21 22 23 24 25 26 27



Funkcje

n = input("wpisz liczbę: ") n = int(n) if n % 2 == 0: print("liczba jest parzysta.") else: print("liczba jest nieparzysta.")

>> wpisz liczbę:

Powyższy program trzy razy prosi użytkownika o podanie liczby. Następnie używa instrukcji if-else, by sprawdzić, czy podana wartość jest parzysta, czy nie. W zależności od wyniku sprawdzenia wyświetlany jest odpowiedni komunikat: liczba jest parzysta. lub liczba jest nieparzysta.. Problem związany z powyższym przykładem polega na tym, że trzykrotnie powtarzamy w nim ten sam fragment kodu. Kod można skrócić i uprościć, umieszczając powtarzające się możliwości funkcjonalne w funkcji i wywołując ją trzy razy: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# python_lst103.py

def even_odd(): n = input("wpisz liczbę: ") n = int(n) if n % 2 == 0: print("liczba jest parzysta.") else: print("liczba jest nieparzysta.") even_odd() even_odd() even_odd()

>> wpisz liczbę:

Nowa wersja programu działa dokładnie tak samo, ale ze względu na to, że jej możliwości funkcjonalne umieściliśmy w funkcji, którą można wywoływać zawsze wtedy, gdy będzie potrzebna, program jest znacznie krótszy i łatwiejszy do zrozumienia.

Parametry wymagane i opcjonalne W języku Python można wyróżnić dwa rodzaje parametrów funkcji. Wszystkie parametry, które widzieliśmy do tej pory, były parametrami wymaganymi. Kiedy użytkownik wywołuje funkcję, musi do niej przekazać wszystkie parametry wymagane, w przeciwnym razie Python zgłosi wyjątek. Jednak dostępny jest także inny rodzaj parametrów, czyli parametry opcjonalne. W ich przypadku programista może w wywołaniu funkcji podać wartość takiego parametru, lecz nie jest to konieczne. Jeśli parametr opcjonalny nie zostanie przekazany, funkcja użyje jego wartości domyślnej.

a9c8a50d59776690f264c310a5514dcc a

61

Zasięg

Parametry opcjonalne są definiowane przy użyciu zapisu [nazwa_funkcji]([nazwa_parametru]= [wartość_parametru]). Parametry opcjonalne, podobnie jak wymagane, muszą być rozdzielane przecinkami. Poniżej przedstawiony został przykład funkcji z parametrem opcjonalnym: 1 2 3 4 5 6 7 8

# python_lst104.py def f(x=2): return x**x print(f()) print(f(4))

>> 4 >> 256

W powyższym przykładzie najpierw wywołaliśmy funkcję bez przekazywania żadnego parametru. Jest to parametr opcjonalny, dlatego też w tej sytuacji x automatycznie przyjmuje wartość 2, a funkcja zwraca 4. Następnie wywołaliśmy tę samą funkcję, przekazując do niej wartość 4. W tym przypadku funkcja zignoruje domyślną wartość parametru, zatem x przyjmie wartość 4, a funkcja zwróci wynik 256. Można definiować funkcje mające zarówno parametry wymagane, jak i opcjonalne; jednak w takim przypadku wszystkie parametry wymagane muszą zostać umieszczone przed parametrami opcjonalnymi: 1 2 3 4 5 6 7 8

# pyton_lst105.py def add_it(x, y=10): return x + y result = add_it(2) print(result)

>> 12

Zasięg Wszystkie zmienne mają ważną cechę określaną jako zasięg. Kiedy definiujemy zmienną, zasięg ustala obszar programu, w którym zmienna ta może być odczytywana i zapisywana. Odczytywanie zmiennej oznacza pobranie jej wartości. Z kolei zapisywanie zmiennej oznacza zmianę jej wartości. Zasięg zmiennej jest zależny od miejsca programu, w którym została zdefiniowana. Jeśli zmienna zostanie zdefiniowana poza funkcją (czy też klasą — klasami zajmiemy się w drugiej części książki), będzie miała zasięg globalny: oznacza to, że będzie można ją odczytywać i zapisywać w dowolnym miejscu programu. Zmienne o zasięgu globalnym są nazywane zmiennymi globalnymi. Jeśli funkcja zostanie zdefiniowana wewnątrz funkcji (lub klasy), będzie mieć zasięg lokalny: oznacza to, że będzie można ją odczytywać i zapisywać jedynie wewnątrz tej funkcji (lub klasy), w której została zdefiniowana. Poniżej przedstawione zostały przykłady zmiennych globalnych:

a9c8a50d59776690f264c310a5514dcc a

62

Rozdział 4

1 2 3 4 5



Funkcje

# python_lst106.py x = 1 y = 2 z = 3

Te zmienne nie zostały zdefiniowane wewnątrz funkcji (lub klasy) i dlatego mają zasięg globalny. Oznacza to, że można je odczytywać i zapisywać w dowolnym miejscu kodu programu — w tym także wewnątrz zdefiniowanych w nim funkcji: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

# python_lst107.py x = 1 y = 2 z = 3 def f(): print(x) print(y) print(z) f()

>> 1 >> 2 >> 3

Jeśli te same zmienne zostaną zdefiniowane wewnątrz funkcji, będzie można je odczytywać i zapisywać wyłącznie wewnątrz tej funkcji. Próba odwołania się do takiej zmiennej poza kodem funkcji, w której została zdefiniowana, spowoduje, że Python zgłosi wyjątek: 1 2 3 4 5 6 7 8 9 10 11

# python_lst108.py def f(): x = 1 y = 2 z = 3 print(x) print(y) print(z)

>> NameError: name 'x' is not defined

Jeśli odwołania do zmiennych znajdą się wewnątrz funkcji, w której zostały zdefiniowane, kod będzie działać prawidłowo: 1 # python_lst109.py 2 3 def f(): 4 x = 1 5 y = 2 6 z = 3

a9c8a50d59776690f264c310a5514dcc a

63

Zasięg

7 print(x) 8 print(y) 9 print(z) 10 11 12 f()

>> 1 >> 2 >> 3

Próba użycia zmiennej zdefiniowanej wewnątrz funkcji poza tą funkcją przypomina próbę użycia zmiennej, która jeszcze nie została zdefiniowana — właśnie z tego powodu Python zgłasza w takich przypadkach taki sam wyjątek: 1 # python_lst110.py 2 3 if x > 100: 4 print("x jest > 100")

>> NameError: name 'x' is not defined

Zapisywać w zmiennej globalnej można w dowolnym miejscu programu, jednak wykonanie takiej operacji wewnątrz zasięgu lokalnego wymaga dodatkowego kroku. Trzeba jawnie użyć słowa kluczowego global, po nim zostanie podana nazwa zmiennej, której wartość chcemy zmodyfikować. Python wymaga tej dodatkowej czynności jako zabezpieczenia — by w razie zdefiniowania zmiennej x wewnątrz funkcji nie zostały w przypadkowy sposób zmodyfikowane wartości jakichkolwiek zmiennych zdefiniowanych poza funkcją. Poniżej został przedstawiony przykład zmiany wartości zmiennej globalnej wewnątrz funkcji: 1 2 3 4 5 6 7 8 9 10 11 12

# python_lst111.py x = 100 def f(): global x x += 1 print(x) f()

>> 101

Gdyby nie było zasięgów, można by odwoływać się do wszystkich zmiennych w każdym miejscu programu, co byłoby bardzo problematyczne. W takim przypadku, gdybyśmy pisali duży program, a w nim napisali funkcję używającą zmiennej x, moglibyśmy przypadkowo zmienić wartość zmiennej o tej samej nazwie zdefiniowanej w innym miejscu programu. Takie błędy mogą zmieniać działanie całego programu, powodować błędy oraz nieoczekiwane wyniki. Im większy jest program oraz im więcej zmiennych w nim używanych, tym większe będzie prawdopodobieństwo wystąpienia problemów tego typu.

a9c8a50d59776690f264c310a5514dcc a

64

Rozdział 4



Funkcje

Obsługa wyjątków Korzystanie z danych wprowadzanych przez użytkowników przy użyciu funkcji input oznacza, że tracimy kontrolę nad danymi wejściowymi do programu — użytkownik je wpisuje, a dane mogą doprowadzić do wystąpienia błędów. Załóżmy na przykład, że mamy napisać program, który pobiera od użytkownik dwie liczby, a następnie wyświetla wynik dzielenia pierwszej liczby przez drugą: 1 2 3 4 5 6 7

# python_lst112.py a = input("wpisz liczbę: ") b = input("wpisz drugą liczbę: ") a = int(a) b = int(b) print(a / b)

>> >> >> >> >>

wpisz liczbę: 10 wpisz drugą liczbę: 5 2

Program wydaje się działać prawidłowo. Niemniej jednak, kiedy użytkownik wpisze 0 jako drugą liczbę, pojawią się problemy: 1 2 3 4 5 6 7

# python_lst113.py a = input("wpisz liczbę: ") b = input("wpisz drugą liczbę: ") a = int(a) b = int(b) print(a / b)

>> >> >> >> >>

wpisz liczbę: 10 wpisz drugą liczbę: 0 ZeroDivisionError: integer division or modulo by zero

Nie możemy bazować jedynie na nadziei, że użytkownik korzystający z programu nie wpisze zera jako drugiej liczby. Jednym ze sposobów rozwiązania tego problemu jest skorzystanie z obsługi wyjątków — techniki pozwalającej na testowanie wystąpienia błędu, „przechwycenie” wyjątku, jeśli się pojawi, i przedsięwzięcie jakiegoś sposobu rozwiązania problemu. Do obsługi wyjątków służą słowa kluczowe try oraz except. Gdybyśmy zmienili powyższy program, tak by korzystał z obsługi wyjątków, to po wpisaniu przez użytkownika 0 zamiast komunikatu wyjątku moglibyśmy wyświetlić komunikat z prośbą, by nie używać 0 jako drugiej liczby. W języku Python każdy wyjątek jest obiektem, dzięki czemu można z nich korzystać w programach. Pełną listę wbudowanych wyjątków można znaleźć na stronie https://www.tutorialspoint.com/ python/standard_exceptions.htm. Jeśli przypuszczasz, że pisany kod może zgłaszać wyjątki, przechwyć je i obsłuż, używając w tym celu instrukcji złożonej ze słowami kluczowymi try i except.

a9c8a50d59776690f264c310a5514dcc a

65

Obsługa wyjątków

Klauzula try zawiera kod, w którym może wystąpić błąd. Z kolei klauzula except zawiera kod, który zostanie wykonany, jeśli w klauzuli try faktycznie wystąpi błąd. Poniższy przykład pokazuje, w jaki sposób można obsługiwać wyjątki w programie, a konkretnie, jak zabezpieczyć go przed dzieleniem przez zero: 1 2 3 4 5 6 7 8 9 10 11

# python_lst114.py

>> >> >> >> >>

wpisz liczbę: 10 wpisz drugą liczbę: 0 drugą liczbą nie może być 0.

a = input("wpisz liczbę: ") b = input("wpisz drugą liczbę: ") a = int(a) b = int(b) try: print(a / b) except ZeroDivisionError: print("drugą liczbą nie może być 0.")

Jeśli użytkownik jako drugą liczbę wpisze cokolwiek innego niż 0, kod w bloku try zostanie wykonany prawidłowo, a blok except w ogóle nie zostanie wykonany. Jeśli jednak drugą liczbą wpisaną przez użytkownika będzie 0, zamiast zgłaszania wyjątku zostanie wykonany blok except, a program wyświetli komunikat: drugą liczbą nie może być 0.. Powyższy program zgłosi problemy także w przypadku, gdy użytkownik wpisze łańcuch znaków, którego nie będzie można skonwertować na liczbę całkowitą: 1 2 3 4 5 6 7 8 9 10 11

# python_lst115.py

>> >> >> >> >>

wpisz liczbę: Monty wpisz drugą liczbę: Python ValueError: invalid literal for int() with base 10: 'Monthy'

a = input("wpisz liczbę: ") b = input("wpisz drugą liczbę: ") a = int(a) b = int(b) try: print(a / b) except ZeroDivisionError: print("drugą liczbą nie może być 0.")

Ten problem można rozwiązać, przenosząc fragment programu pobierający i konwertujący dane wejściowe do bloku try i nakazując, by instrukcja except przechwytywała nie jeden, lecz dwa wyjątki — ZeroDivisionError oraz ValueError. Wyjątek ValueError jest zgłaszany, gdy do funkcji

a9c8a50d59776690f264c310a5514dcc a

66

Rozdział 4



Funkcje

wbudowanych int, string lub float zostaną przekazane błędne dane. Aby instrukcja except przechwytywała dwa rodzaje wyjątków, należy je podać w nawiasach za słowem kluczowym except i rozdzielić przecinkiem: 1 # python_lst116.py 2 3 try: 4 a = input("wpisz liczbę: ") 5 b = input("wpisz drugą liczbę: ") 6 a = int(a) 7 b = int(b) 8 print(a / b) 9 except(ZeroDivisionError, ValueError): 10 print("Nieprawidłowe dane wejściowe.")

>> >> >> >> >>

wpisz liczbę: Monty wpisz drugą liczbę: Python Nieprawidłowe dane wejściowe.

Zmiennych zdefiniowanych w bloku try nie należy używać w bloku except, gdyż błąd może wystąpić, jeszcze zanim wykonywanie programu dotrze do miejsca, w którym zmienna jest definiowana, a to może spowodować zgłoszenie wyjątku wewnątrz bloku except: 1 # python_lst117.py 2 3 try: 4 10 / 0 5 c = "Nigdy nie zostanę zdefiniowana." 6 except ZeroDivisionError: 7 print(c)

>> NameError: name 'c' is not defined

Łańcuchy dokumentujące Podczas definiowania funkcji z parametrami często do jej poprawnego działania konieczne będzie przekazywanie parametrów odpowiednich typów. W jaki sposób przekazać te informacje wszelkim innym programistom, którzy będą używać tej funkcji? Podczas definiowania funkcji dobrym zwyczajem jest umieszczanie w niej specjalnych komentarzy, nazywanych łańcuchami dokumentującymi, które będą przekazywać informacje o typach poszczególnych parametrów oraz przeznaczeniu całej funkcji: 1 # python_lst118.py 2 3 def add(x, y): 4 """ 5 Zwraca x + y. 6 :param x: int. 7 :param y: int.

a9c8a50d59776690f264c310a5514dcc a

67

Używanie zmiennych tylko wtedy, gdy to konieczne

8 9 10

:return: int suma x i y. """ return x + y

Pierwszy wiersz tego komentarza w jasny sposób tłumaczy działanie funkcji, dzięki czemu kiedy ktoś zechce jej użyć, nie będzie musiał analizować całego kodu, by dowiedzieć się, do czego dana funkcja służy. W pozostałych wierszach komentarza są wymienione wszystkie parametry funkcji i ich typy oraz zwracany przez nią wynik. Dzięki takim komentarzom można programować szybciej, gdyż wystarczy je przeczytać, by zorientować się, co dana funkcja robi; nie trzeba analizować jej kodu. Aby jak najbardziej skrócić przykłady prezentowane w tekście książki, usunąłem z nich łańcuchy dokumentujące, które zazwyczaj zawsze piszę. Kiedy jednak normalnie piszę kod, dodaję do niego te komentarze, aby ułatwić zrozumienie mojego kodu wszystkim innym programistom, którzy mogą w przyszłości go używać.

Używanie zmiennych tylko wtedy, gdy to konieczne Dane należy zapisywać w zmiennych wyłącznie wtedy, kiedy planujemy ich używać w przyszłości. Nie warto na przykład zapisywać liczby całkowitej w zmiennej tylko po to, by ją wyświetlić: 1 # python_lst119.py 2 3 x = 100 4 print(x)

>> 100

W takim przypadku należy przekazać wartość bezpośrednio w wywołaniu funkcji print: 1 # python_120.py 2 3 print(100)

>> 100

W wielu miejscach książki łamię tę zasadę, lecz robię tak celowo — by ułatwić Ci zrozumienie opisywanych zagadnień. Pisząc własny kod, nie powinieneś jednak tak postępować.

Słownictwo Funkcje wbudowane. Funkcje dostępne bezpośrednio w języku Python. Funkcje. Instrukcje złożone pobierające dane wejściowe, wykonujące inne instrukcje i zwracające wyniki. Konwencja. Uzgodniony sposób wykonywania pewnych czynności.

a9c8a50d59776690f264c310a5514dcc a

68

Rozdział 4



Funkcje

Łańcuchy dokumentujące. Specjalne komentarze wyjaśniające, co dana funkcja robi i jakiego typu są jej parametry. Obsługa wyjątków. Rozwiązania programistyczne polegające na sprawdzaniu wystąpienia błędu, „przechwytywaniu” go, jeśli wystąpi, i podejmowaniu decyzji, jak postąpić w danej sytuacji. Parametr opcjonalny. Parametr, który nie musi zostać podany. Parametr wymagany. Parametr, który trzeba podać. Parametr. Dana przekazywana do funkcji. Wywoływanie. Przekazywanie do funkcji danych wejściowych niezbędnych do jej działania i zwrócenia wyniku. Zasięg globalny. Zasięg zmiennej, który pozwala na jej odczyt i zapis w całym kodzie programu. Zasięg lokalny. Zasięg, w którym zmienna może być odczytywana i zapisywana wyłącznie wewnątrz funkcji (lub klasy), w jakiej została zdefiniowana. Zasięg. Obszar kodu, w którym można odczytywać i zapisywać zmienną. Zmienna globalna. Zmienna mająca zasięg globalny.

Wyzwania 1. Napisz funkcję, która pobiera liczbę i zwraca tę liczbę podniesioną do kwadratu. 2. Napisz funkcję pobierającą łańcuch znaków, która wyświetla ten łańcuch. 3. Napisz funkcję, która ma trzy wymagane parametry i dwa parametry opcjonalne. 4. Napisz program definiujący dwie funkcje. Pierwsza z nich powinna pobierać parametr będący liczbą całkowitą i zwracać tę liczbę podzieloną przez 2. Druga funkcja także ma pobierać parametr typu int, lecz zwracać wartość tego parametru pomnożoną razy 4. Główna część programu ma wywoływać pierwszą funkcję, zapisywać zwrócony przez nią wynik w zmiennej, a następnie przekazywać go w wywołaniu drugiej funkcji. 5. Napisz funkcję, która konwertuje łańcuch znaków na wartość typu float i zwraca uzyskaną wartość. Zastosuj obsługę wyjątków, by przechwytywać ewentualne błędy. 6. Dodaj łańcuchy dokumentujące do wszystkich funkcji napisanych w wyzwaniach od 1. do 5. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

5 Kontenery

„Głupiec zastanawia się, mądry człowiek pyta”. — Benjamin Disraeli

W rozdziale 3. opisałem, jak można zapisywać obiekty w zmiennych. W tym przedstawię, jak można zapisywać obiekty w kontenerach. Kontenery są jak szafki na akta — pozwalają organizować dane. W tym rozdziale opisane zostaną trzy najczęściej używane spośród dostępnych kontenerów: listy, krotki (ang. tuples) oraz słowniki.

Metody Rozdział 4. był poświęcony między innymi funkcjom. W języku Python stosowane jest jeszcze jedno pojęcie zbliżone do funkcji — metody. Metody są funkcjami ściśle powiązanymi z konkretnym typem danych. Metody, podobnie jak funkcje, wykonują kod i zwracają wynik. Jednak w odróżnieniu od funkcji metody są wywoływane na rzecz konkretnego obiektu. Można także przekazywać do nich parametry. Poniżej przedstawione zostały przykłady użycia dwóch metod, upper oraz replace, operujących na obiektach string:

69

a9c8a50d59776690f264c310a5514dcc a

70

Rozdział 5



Kontenery

1 # python_lst121.py 2 3 "Witaj".upper()

>> WITAJ 1 # python_lst122.py 2 3 "Witaj".replace("j", "m")

>> Witam

Metody zostaną szczegółowo opisane w drugiej części książki.

Listy Lista jest kontenerem przechowującym obiekty w ściśle określonej kolejności, graficzną reprezentację listy przedstawia rysunek 5.1.

Rysunek 5.1. Graficzna reprezentacja listy

Listy są reprezentowane przy użyciu nawiasów kwadratowych. Można je tworzyć za pomocą dwóch różnych zapisów. Pustą listę można opracować z wykorzystaniem funkcji list, tak jak pokazano na poniższym przykładzie: 1 # python_lst123.py 2 3 fruit = list() 4 fruit

>> []

Pustą listę można także utworzyć przy użyciu pary nawiasów kwadratowych: 1 # python_lst124.py 2 3 fruit = [] 4 fruit

>> []

Stosując ten drugi zapis, można tworzyć listy, które już będą zawierać podane elementy — wystarczy umieścić je pomiędzy nawiasami kwadratowymi i oddzielić przecinkami: 1 # python_lst125.py 2 3 fruit = ["mango", "banan", "gruszka"] 4 fruit

>> ['mango', 'banan', 'gruszka']

a9c8a50d59776690f264c310a5514dcc a

71

Listy

Na tak utworzonej liście znajdują się trzy elementy: "mango", "banan" oraz "gruszka". Listy przechowują swoje elementy w określonej kolejności. Jeśli kolejność elementów listy nie zostanie jawnie zmieniona, element "mango" zawsze będzie pierwszy, "banan" — drugi, a "gruszka" — trzecia. Element "mango" znajduje się na początku listy, a "gruszka" na jej końcu. Nowe elementy można dodawać do listy, używając metody append: 1 2 3 4 5 6

# python_lst126.py fruit = ["mango", "banan", "gruszka"] fruit.append("pomelo") fruit.append("figa") fruit

>> ['mango', 'banan', 'gruszka', 'pomelo', 'figa']

Każdy obiekt przekazany do metody append stał się teraz elementem listy. Metoda append zawsze dodaje obiekty na końcu listy. Możliwości list nie ograniczają się do przechowywania łańcuchów znaków — można na nich zapisywać dane dowolnego typu: 1 2 3 4 5 6 7 8

# python_lst127.py random = [] random.append(True) random.append(100) random.append(1.1) random.append("Witaj") random

>> [True, 100, 1.1, 'Witaj']

Łańcuchy znaków, listy oraz kroki są tak zwanymi obiektami iterowalnymi. Obiekt jest interowalny, jeśli każdy jego element można kolejno pobrać w pętli. Każdy element takiego iterowalnego obiektu ma swój indeks — numer reprezentujący jego położenie w obiekcie. Pierwszy element listy ma zawsze indeks o wartości 0, a nie 1. W poniższym przykładzie łańcuch "mango" ma indeks 0, "banan" — 1, a "gruszka" — 2: 1 # python_lst128.py 2 3 fruit = ["mango", "banan", "gruszka"]

Element o konkretnym indeksie można pobrać, używając zapisu [nazwa_listy][[indeks]]: 1 2 3 4 5 6

# python_lst129.py fruit = ["mango", "banan", "gruszka"] fruit[0] fruit[1] fruit[2]

>> 'mango' >> 'banan' >> 'gruszka'

a9c8a50d59776690f264c310a5514dcc a

72

Rozdział 5



Kontenery

W przypadku próby odwołania się do elementu o indeksie, który nie istnieje, Python zgłosi wyjątek: 1 # python_lst130.py 2 3 colors = ["niebieski","zielony","czerwony"] 4 colors[4]

>> IndexError: list index out of range

Listy są elementami modyfikowalnymi (ang. mutable). Kiedy kontener jest modyfikowalny, można do niego dodawać nowe elementy oraz usuwać z niego elementy już istniejące. Element listy można zmienić, przypisując jego indeks nowemu obiektowi, co pokazano na poniższym przykładzie: 1 2 3 4 5 6

# python_lst131.py colors = ["niebieski","zielony","czerwony"] colors colors[2] = "filetowy" colors

>> ['niebieski', 'zielony', czerwony] >> ['niebieski', 'zielony', 'filetowy']

Element można usunąć z listy, używając metody pop: 1 2 3 4 5 6 7

# python_lst132.py colors = ["niebieski","zielony","czerwony"] colors item = colors.pop() item colors

>> ['niebieski', 'zielony', 'czerwony'] >> 'czerwony' >> ['niebieski', 'zielony']

Nie można używać metody pop do pobierania elementów z pustej listy. W takim przypadku Python zgłosi wyjątek. Dwie listy można połączyć przy użyciu operatora dodawania: 1 2 3 4 5

# python_lst133.py colors1 = ["niebieski","zielony","czerwony"] colors2 = ["fioletowy", "czarny", "purpurowy"] colors1 + colors2

>> ['niebieski', 'zielony', 'czerwony', 'fioletowy', 'czarny', 'purpurowy']

Z pomocą słowa kluczowego in można sprawdzać, czy element jest dostępny na liście: 1 # python_lst134.py 2 3 colors = ["niebieski","zielony","czerwony"] 4 "zielony" in colors

>> True

a9c8a50d59776690f264c310a5514dcc a

73

Krotki

Natomiast do sprawdzania, czy elementu nie ma na liście, można użyć słowa kluczowego in poprzedzonego operatorem not: 1 # python_lst135.py 2 3 colors = ["niebieski","zielony","czerwony"] 4 "czarny" not in colors

>> True

Wielkość listy (czyli liczbę jej elementów) można określić przy użyciu funkcji len: 1 # python_lst136.py 2 3 len(colors)

>> 3

A oto przykład prezentujący nieco bardziej praktyczne wykorzystanie list: 1 2 3 4 5 6 7 8 9 10

# python_lst137.py colors = ["niebieski","zielony","czerwony"] guess = input("Zgadnij, jaki kolor wybrałem? ") if guess in colors: print("Zgadłeś!") else: print("Źle. Spróbuj jeszcze raz.")

>> Zgadnij, jaki kolor wybrałem?

Lista colors zawiera różne łańcuchy znaków reprezentujące kolory. Program używa wbudowanej funkcji input, by poprosić użytkownika o odgadnięcie koloru, i zapisuje wpisaną wartość w zmiennej guess. Jeśli podany kolor znajduje się na liście colors, program wyświetla komunikat informujący, że użytkownikowi udało się odgadnąć prawidłowo. W przeciwnym razie wyświetlany jest komunikat informujący, że użytkownikowi nie udało się odgadnąć koloru.

Krotki Krotka jest kontenerem przechowującym obiekty w określonej kolejności. W odróżnieniu od listy, krotki są niezmienne, co oznacza, że ich zawartości nie można modyfikować. Po utworzeniu krotki nie można zmieniać wartości żadnego z zapisanych w niej elementów, dodawać nowych elementów ani usuwać tych, które się w krotce znajdują. Krotki są reprezentowane przy użyciu nawiasów. Poszczególne elementy krotki muszą być od siebie oddzielone przecinkami. Krotki można tworzyć na dwa sposoby: 1 # python_lst138.py 2 3 my_tuple = tuple() 4 my_tuple

>> ()

a9c8a50d59776690f264c310a5514dcc a

74

Rozdział 5



Kontenery

oraz: 1 # python_lst139.py 2 3 my_tuple = () 4 my_tuple

>> ()

Aby utworzyć krotkę z jakimiś obiektami, należy użyć drugiego rozwiązania i umieścić poszczególne obiekty wewnątrz nawiasów oraz oddzielić je od siebie przecinkami: 1 # python_lst140.py 2 3 rndm = ("M. Jackson", 1958, True) 4 rndm

>> ('M. Jackson', 1958, True)

Jeśli nawet tworzona krotka ma zawierać tylko jeden element, to i tak należy po nim zapisać przecinek. W ten sposób Python będzie w stanie rozróżnić, że chodzi nam o utworzenie kroki, a nie że podajemy na przykład liczby zapisanej w nawiasach w celu określenia kolejności wykonywania działań: 1 2 3 4 5 6 7 8

# python_lst141.py # to jest krotka ("programista_samouk",) # to nie jest krotka (9) + 1

>> ('programista_samouk',) >> 10

Do utworzonej krotki nie można dodawać nowych elementów ani zmieniać elementów, które są w niej zapisane. Próba wykonania takiej operacji spowoduje zgłoszenie wyjątku: 1 # python_lst142.py 2 3 dys = ("1984", 4 "Nowy wspaniały świat", 5 "Fahrenheit 451") 6 7 dys[1] = "Folwark zwierzęcy"

>> TypeError: 'tuple' object does not support item assignment

Elementy kroki można pobierać dokładnie tak samo jak elementy listy — odwołując się do nich przy użyciu indeksu: 1 # python_lst143.py 2 3 dys = ("1984", 4 "Nowy wspaniały świat",

a9c8a50d59776690f264c310a5514dcc a

75

Słowniki

5 6 7 dys[2]

"Fahrenheit 451")

>> Fahrenheit 451

Używając słowa kluczowego in, można sprawdzić, czy dany element jest w krotce: 1 # python_lst144.py 2 3 dys = ("1984", 4 "Nowy wspaniały świat", 5 "Fahrenheit 451") 6 7 "1984" in dys

>> True

Aby sprawdzić, czy danego elementu nie ma w krotce, wystarczy poprzedzić in operatorem not: 1 # python_lst145.py 2 3 dys = ("1984", 4 "Nowy wspaniały świat", 5 "Fahrenheit 451") 6 7 "Folwark zwierzęcy" not in dys

>> True

Można by się zastanawiać, dlaczego używać struktur danych, które najwidoczniej są mniej elastyczne od list. Krotki są przydatne, gdy operujemy na wartościach, które nigdy nie będą się zmieniać i kiedy nie chcemy, by dane te były modyfikowane przez inne fragmenty programu. Przykładem informacji, które z powodzeniem nadają się do umieszczania w krotkach, są współrzędne geograficzne. Szerokość i długość geograficzną miast należy zapisywać w krotce, gdyż nigdy się nie zmienią, a użycie kroki gwarantuje, że dane te nie ulegną zmianie w innych miejscach programu. W odróżnieniu od list, krotki mogą być używane jako klucze w słownikach, które zostaną przedstawione w następnym podrozdziale.

Słowniki Kolejnym wbudowanym kontenerem do przechowywania obiektów są słowniki. Służą one do kojarzenia jednego obiektu, nazywanego kluczem, z innym obiektem, zwanym wartością. Takie połączenie jednego obiektu z drugim nazywane jest odwzorowywaniem. W efekcie odwzorowania powstaje para klucz-wartość. Właśnie takie pary klucz-wartość są dodawane do słownika. Następnie można wyszukać w słowniku klucz i na jego podstawie pobrać wartość. Nie można natomiast użyć wartości do odszukania klucza. Słowniki są kontenerami modyfikowalnymi, a zatem można do nich dodawać nowe pary klucz-wartość. W odróżnieniu od list i krotek, słowniki nie przechowują obiektów w żadnej konkretnej kolejności. Ich przydatność bazuje na powiązaniach tworzonych pomiędzy kluczami i wartościami, a istnieje

a9c8a50d59776690f264c310a5514dcc a

76

Rozdział 5



Kontenery

wiele sytuacji, w których takie sposoby przechowywania danych w parach są bardzo przydatne. Przykładowo w słowniku można by przechowywać informacje o osobach. W takim przypadku można skojarzyć klucz o nazwie height z wartością reprezentującą wzrost danej osoby, klucz eyecolor z łańcuchem określającym jej kolor oczu czy też klucz nationality z wartością reprezentującą jej narodowość. Graficzną reprezentację słownika przedstawia rysunek 5.2.

Rysunek 5.2. Graficzna reprezentacja słownika

Słowniki są reprezentowane przy użyciu nawiasów klamrowych. Można je tworzyć na dwa sposoby, przedstawione na poniższych przykładach. Oto pierwszy z nich: 1 # python_lst146.py 2 3 my_dict = dict() 4 my_dict

>> {}

I drugi sposób: 1 # python_lst147.py 2 3 my_dict = {} 4 my_dict

>> {}

Pary klucz-wartość można dodawać do słownika już podczas jego tworzenia. W obu sposobach opracowywania słowników klucze są oddzielane od wartości znakiem dwukropka; z kolei poszczególne pary należy oddzielać od siebie przecinkami. W odróżnieniu do krotek, jeśli słownik ma zawierać tylko jedną parę, nie trzeba zapisywać za nią znaku przecinka. Poniższy przykład pokazuje, jak można dodawać pary do słownika podczas tworzenia: 1 # python_lst148.py 2 3 fruits = {"agrest": 4 "zielony", 5 "porzeczka": 6 "czerwony"} 7 fruits

>> {'agrest': 'zielony', 'porzeczka': 'czerwony'}

a9c8a50d59776690f264c310a5514dcc a

77

Słowniki

Na wynikach wyświetlonych w oknie interaktywnej powłoki poszczególne pary mogą być zapisane w innej kolejności niż na powyższym przykładzie. Wynika to z faktu, że słowniki nie zachowują uporządkowania par, a Python wyświetla je w dowolnej kolejności (to samo dotyczy wszystkich przykładów ze słownikami prezentowanych w tym podrozdziale). Słowniki są kontenerami modyfikowalnymi. Do słownika po utworzeniu można dodawać kolejne pary, używając składni [nazwa_słownika][[klucz]] = [wartość]; z kolei do pobierania wartości ze słownika służy następujący zapis: [nazwa_słownika][[klucz]]: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# python_lst149.py facts = dict() # dodanie wartości facts["programowanie"] = "zabawa" # pobranie wartości facts["programowanie"] # dodanie wartości facts["Bill"] = "Gates" # pobranie wartości facts["Bill"] # dodanie wartości facts["Grunwald"] = 1410 # pobranie wartości facts["Grunwald"]

>> 'zabawa' >> 'Gates' >> '1410'

Każdy obiekt może zostać zapisany w słowniku jako wartość. W powyższym przykładzie pierwszymi dwiema wartościami są łańcuchy znaków, a ostatnią liczba całkowita — 1410. Jednak w odróżnieniu od wartości, klucze słownika muszą być obiektami niezmiennymi. A zatem kluczem słownika mogą być łańcuchy znaków lub krotki, lecz nie listy ani inne słowniki. Aby sprawdzić, czy w słowniku jest dostępny klucz, należy użyć słowa kluczowego in. Nie można go jednak używać do sprawdzania, czy w słowniku jest zapisana konkretna wartość: 1 # python_lst150.py 2 3 bill = dict({"Bill Gates": 4 "charytatywność"}) 5 6 "Bill Gates" in bill

>> True

Próba odwołania się do klucza, którego nie ma w słowniku, spowoduje zgłoszenie wyjątku.

a9c8a50d59776690f264c310a5514dcc a

78

Rozdział 5



Kontenery

Aby sprawdzić, czy klucza nie ma w słowniku, in należy poprzedzić operatorem not: 1 # python_lst151.py 2 3 bill = dict({"Bill Gates": 4 "charytatywność"}) 5 6 7 "Bill Doors" not in bill

>> True

Konkretne pary można usuwać ze słownika, używając słowa kluczowego del: 1 2 3 4 5 6 7 8 9 10

# python_lst152.py books = {"Dracula": "Stoker", "1984": "Orwell", "Proces": "Kafka"} del books["Proces"] books

>> {'Dracula': 'Stoker', '1984': 'Orwell'}

Poniżej zamieszczony został program przedstawiający zastosowanie słownika: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# python_lst153.py rhymes = {"1": "2": "3": "4": "5": }

"niebem", "kwa kwa", "śni", "ordery", "cięć"

n = input("Wpisz cyfrę: ") if n in rhymes: rhyme = rhymes[n] print(rhyme) else: print("Nie znaleziono.")

>> Wpisz cyfrę:

Słownik (rhymes) zawiera pięć cyfr (kluczy) skojarzonych ze słowami (wartościami). Program prosi użytkownika o wpisanie cyfry (jako łańcucha znaków) i zapisuje ją w zmiennej. Następnie, przed wyświetleniem słowa rymującego się z daną cyfrą program sprawdza, czy klucz istnieje w słowniku, używając w tym celu słowa kluczowego in. Jeśli klucz istnieje, program odczytuje ze słownika powiązane z nim słowo i wyświetla je na ekranie. W przeciwnym razie, jeśli klucz nie zostanie odnaleziony w słowniku, wyświetlany jest stosowny komunikat.

a9c8a50d59776690f264c310a5514dcc a

79

Kontenery w kontenerach

Kontenery w kontenerach Istnieje możliwość zapisywania jednych kontenerów w innych. Przykładowo elementami jednej listy mogą być inne listy: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

# python_lst154.py

lists = [] rap = ["Kanye West", "Jay Z", "Eminem", "Nas"] rock = ["Bob Dylan", "The Beatles", "Led Zeppelin"] djs = ["Zeds Dead", "Tiesto"] lists.append(rap) lists.append(rock) lists.append(djs) print(lists)

>> [['Kanye West', 'Jay Z', 'Eminem', 'Nas'], ['Bob Dylan', 'The Beatles', 'Led Zeppelin'], ['Zeds Dead', 'Tiesto']]

W tym przykładzie lista lists ma trzy indeksy. Każdy z nich jest kolejną listą: pierwsza zawiera pseudonimy raperów, druga — nazwy zespołów rockowych, a trzecia — pseudonimy DJ-ów. Do tych list można się odwoływać za pomocą ich indeksów: 1 2 3 4 5 6 7

# python_lst155.py # kontynuacja poprzedniego # przykładu python_lst154.py rap = lists[0] print(rap)

>> ['Kanye West', 'Jay Z', 'Eminem', 'Nas']

Kiedy dodamy nowy element do listy rap, wprowadzone w niej zmiany będą widoczne po wyświetleniu listy lists: 1 # python_lst156.py 2 3 # kontynuacja poprzedniego 4 # przykładu: python_lst154.py

a9c8a50d59776690f264c310a5514dcc a

80

Rozdział 5

5 6 7 8 9 10



Kontenery

rap = lists[0] rap.append("Kendrick Lamar") print(rap) print(lists)

>> ['Kanye West', 'Jay Z', 'Eminem', 'Nas', 'Kendrick Lamar'] >> [['Kanye West', 'Jay Z', 'Eminem', 'Nas', 'Kendrick Lamar'], ['Bob Dylan', 'The Beatles', 'Led Zeppelin'], ['Zeds Dead', 'Tiesto']]

Można także zapisywać krotki na listach, listy w krotkach oraz słowniki zarówno na listach, jak i w krotkach: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# python_lst157.py

locations = [] la = (34.0522, 188.2437) chicago = (41.8781, 87.6298) locations.append(la) locations.append(chicago) print(locations)

>> [(34.0522, 188.2437), (41.8781, 87.6298)] 1 2 3 4 5 6 7 8 9 10 11 12 13

# python_lst158.py eights = ["Edgar Allan Poe", "Charles Dickens"] nines = ["Hemingway", "Fitzgerald", "Orwell"] authors = (eights, nines) print(authors)

>> (['Edgar Allan Poe', 'Charles Dickens'], ['Hemingway', 'Fitzgerald', 'Orwell']) 1 2 3 4 5 6 7 8 9 10

# python_lst159.py bday = {"Hemingway": "7.21.1899", "Fitzgerald": "9.24.1896"} my_list = [bday] print(my_list)

a9c8a50d59776690f264c310a5514dcc a

81

Słownictwo

11 my_tuple = (bday,) 12 print(my_tuple)

>> [{'Hemingway': '7.21.1899', 'Fitzgerald': '9.24.1896'}] >> ({'Hemingway': '7.21.1899', 'Fitzgerald': '9.24.1896'},)

Zarówno lista, krotka, jak i słownik mogą być wartością w innym słowniku: 1 # python_lst160.py 2 3 ny = {"lokalizacja": 4 (40.7128, 5 74.0059), 6 7 8 "celebryci": 9 ["W. Allen", 10 "Jay Z", 11 "K. Bacon"], 12 13 "fakty": 14 {"stan": 15 "NY", 16 "kraj": 17 "Ameryka"} 18 }

Słownik utworzony w tym przykładzie ma trzy klucze: "lokalizacja", "celebryci" oraz "fakty". Wartość powiązana z pierwszym z tych kluczy jest krotką, gdyż współrzędne geograficzne nigdy się nie zmieniają. Wartością drugiego klucza jest lista celebrytów mieszkających w Nowym Jorku i jest to lista, ponieważ jej zawartość może się zmieniać. I w końcu wartością trzeciego klucza jest słownik, gdyż pary klucz-wartość najlepiej nadają się do reprezentacji informacji o Nowym Jorku.

Słownictwo Klucz. Wartość używana do odszukiwania innej wartości w słowniku. Lista. Kontener przechowujący obiekty w określonej kolejności. Metoda. Funkcja ściśle powiązana z konkretnym typem danych. Modyfikowalny. Kiedy kontener jest modyfikowalny, jego zawartość może się zmieniać. Niezmienny. Kiedy kontener jest niezmienny, jego zawartość nie może się zmieniać. Obiekt iterowalny. Obiekt, do którego elementów można odwoływać się kolejno w pętli. Obiekty iterowalne. Obiekty, takie jak łańcuchy znaków, listy i krotki. Para klucz-wartość. Klucz powiązany z wartością w słowniku.

a9c8a50d59776690f264c310a5514dcc a

82

Rozdział 5



Kontenery

Powiązanie. Połączenie jednego obiektu z drugim. Słownik. Wbudowany kontener do przechowywania obiektów, używany do kojarzenia jednego obiektu — nazywanego kluczem — z innym obiektem — nazywanym wartością. Wartość. Wartość skojarzona z kluczem w słowniku.

Wyzwania 1. Utwórz listę swoich ulubionych muzyków. 2. Utwórz listę krotek, z których każda będzie zawierać współrzędne geograficzne jakiegoś miejsca, w którym mieszkałeś lub które odwiedziłeś. 3. Utwórz słownik zawierający różne informacje dotyczące Ciebie: wzrost, ulubiony kolor, ulubiony autor i tak dalej. 4. Napisz program, który pozwoli użytkownikowi pytać o różne informacje dotyczące Ciebie, takie jak wzrost, ulubiony kolor, ulubiony autor, a następnie będzie je zwracać ze słownika utworzonego w poprzednim wyzwaniu. 5. Utwórz słownik kojarzący Twoich ulubionych muzyków z listami ich utworów. 6. Listy, krotki oraz słowniki to jedynie niektóre spośród wbudowanych kontenerów dostępnych w języku Python. Zobacz, czym w Pythonie są zbiory (ang. set; to typ kontenera). Do czego można by ich używać? Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

6 Operacje na łańcuchach znaków

„W teorii nie ma żadnej różnicy pomiędzy teorią i praktyką. Jednak w praktyce taka różnica jest”. — Jan L.A. van de Snepscheut

Python dysponuje wbudowanymi możliwościami funkcjonalnymi służącymi do wykonywania operacji na łańcuchach znaków, takich jak dzielenie łańcuchów na części w miejscu wystąpienia określonego znaku lub zmiana wielkości liter w łańcuchu. Jeśli na przykład dysponujemy łańcuchem znaków zapisanym WIELKIMI LITERAMI, a chcemy, by wyświetlić go małymi literami, możemy to zrobić przy użyciu funkcji dostarczanych przez Python. W tym rozdziale dowiesz się znacznie więcej na temat łańcuchów znaków i poznasz najbardziej użyteczne narzędzia do manipulowania nimi w języku Python.

83

a9c8a50d59776690f264c310a5514dcc a

84

Rozdział 6



Operacje na łańcuchach znaków

Potrójne łańcuchy Jeśli łańcuch znaków zajmuje więcej niż jeden wiersz kodu, należy go zapisywać, używając sekwencji trzech znaków cudzysłowu lub apostrofu: 1 # python_lst161.py 2 3 """ wiersz pierwszy 4 wiersz drugi 5 wiersz trzeci 6 """

Jeśli taki łańcuch, zajmujący więcej niż jeden wiersz, spróbujemy zapisać pomiędzy pojedynczymi lub podwójnymi cudzysłowami lub apostrofami, Python zgłosi błąd składniowy.

Indeksy Łańcuchy znaków, podobnie jak listy i krotki, są iterowalne. Oznacza to, że każdy ze znaków łańcucha można odczytać, używając jego indeksu. Podobnie jak inne dane iterowalne, pierwszy znak łańcucha zawsze ma indeks 0, a indeksy kolejnych znaków są większe o 1: 1 2 3 4 5 6 7 8

# python_lst162.py author = "Kafka" author[0] author[1] author[2] author[3] author[4]

>> >> >> >> >>

'K' 'a' 'f' 'k' 'a'

W tym przykładzie zastosowaliśmy indeksy 0, 1, 2, 3 oraz 4, aby pobrać poszczególne znaki z łańcucha "Kafka". Próba zastosowania indeksu o wartości przekraczającej długość łańcucha spowoduje zgłoszenie wyjątku: 1 # python_lst163.py 2 3 author = "Kafka" 4 author[5]

>> IndexError: string index out of range

Python pozwala także na odwoływanie się do elementów listy przy użyciu indeksów o wartościach ujemnych: są to indeksy (które muszą być liczbami ujemnymi) pozwalające na odwoływanie się do elementów danej iterowalnej w kierunku od prawej do lewej, a nie do lewej do prawej. Innymi słowy, indeksu -1 można użyć do pobrania ostatniego elementu danej iterowalnej:

a9c8a50d59776690f264c310a5514dcc a

85

Łańcuchy znaków są niezmienne

1 # python_lst164.py 2 3 author = "Kafka" 4 author[-1]

>> a

Indeks ujemny o wartości -2 pozwala pobrać drugi element od końca, indeks -3 — trzeci element od końca i tak dalej: 1 2 3 4 5

# python_lst165.py author = "Kafka" author[-2] author[-3]

>> k >> f

Łańcuchy znaków są niezmienne Łańcuchy znaków, podobnie jak krotki, są niezmienne. Nie można zmienić znaków w łańcuchu. Jeśli chcemy wprowadzić w łańcuchu jakieś zmiany, w efekcie musimy utworzyć nowy łańcuch: 1 2 3 4 5

# python_lst166.py ff = "F. Fitzgerald" ff = "F. Scott Fitzgerald" ff

>> 'F. Scott Fitzgerald'

Python udostępnia kilka metod pozwalających na tworzenie nowych łańcuchów na podstawie łańcuchów już istniejących; zostaną one przedstawione dalej w tym rozdziale.

Konkatencja Dwa łańcuchy znaków (lub więcej łańcuchów) można ze sobą połączyć, używając operatora dodawania. Łańcuch uzyskany w efekcie takiej operacji będzie zawierał wszystkie znaki pierwszego łańcucha, następnie wszystkie znaki drugiego łańcucha (i tak dalej). Taka operacja łączenia łańcuchów znaków jest nazywana konkatenacją: 1 # python_lst167.py 2 3 "kot" + "w" + "butach"

>> 'kotwbutach' 1 # python_lst168.py 2 3 "Kot " + " w" + " butach"

>> Kot w butach

a9c8a50d59776690f264c310a5514dcc a

86

Rozdział 6



Operacje na łańcuchach znaków

Powielanie łańcuchów znaków Łańcuch znaków można pomnożyć przez liczbę, używając w tym celu operatora mnożenia: 1 # python_lst169.py 2 3 "Puchatek" * 3

>> 'PuchatekPuchatekPuchatek'

Zmiana wielkości liter Wszystkie litery w łańcuchu znaków można zmienić na wielkie, używając metody upper: 1 # python_lst170.py 2 3 "Nie pytaj, komu bije dzwon...".upper()

>> 'NIE PYTAJ, KOMU BIJE DZWON...'

I podobnie, używając metody lower można zmienić każdą literę w łańcuchu na małą: 1 # python_lst171.py 2 3 "TAK TO JEST.".lower()

>> tak to jest

Przy użyciu metody capitalize można zmienić w łańcuchu pierwszą literę zdania na wielką: 1 # python_lst172.py 2 3 "historyczny wynik naszej...".capitalize()

>> 'Historyczny wynik...'

Formatowanie Metoda format pozwala na tworzenie nowych łańcuchów znaków. Jej działanie polega na wyszukiwaniu par nawiasów klamrowych ({}) i zastępowaniu ich podanymi parametrami: 1 # python_lst173.py 2 3 "William {}".format("Faulkner")

>> 'William Faulkner'

Parametrem wywołania metody format może być także zmienna:

a9c8a50d59776690f264c310a5514dcc a

87

Dzielenie łańcuchów

1 # python_lst174.py 2 3 last = "Faulkner" 4 "William {}".format(last)

>> 'William Faulkner'

Nasze możliwości nie ograniczają się do jednokrotnego użycia nawiasów klamrowych — ich par można umieścić w łańcuchu znaków dowolnie wiele: 1 2 3 4 5 6 7 8 9

# python_lst175.py

author = "William Faulkner" year_born = "1897" """{} urodził się w {} roku."""\ .format(author, year_born)

>> 'William Faulkner urodził się w 1897 roku.'

Metoda format jest przydatna, kiedy musimy utworzyć łańcuch znaków na podstawie danych wpisanych przez użytkownika: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

# python_lst176.py n1 = input("Wpisz rzeczownik: ") v = input("Wpisz czasownik: ") adj = input("Wpisz przymiotnik: ") n2 = input("Wpisz rzeczownik: ") r = """The {} {} the {} {} """.format(n1, v, adj, n2) print(r)

>> Wpisz rzeczownik:

Ten program prosi użytkownika o podanie dwóch rzeczowników, czasownika i przymiotnika, a następnie łączy je w zdanie, używając do tego celu funkcji format.

Dzielenie łańcuchów Łańcuchy znaków udostępniają metodę split, z której można skorzystać do dzielenia łańcucha na dwie lub więcej części. Parametrem przekazywanym w wywołaniu tej metody jest inny łańcuch znaków, który zostanie użyty do podzielenia na części łańcucha, na rzecz którego metoda została wywołana. Przykładowo łańcuch znaków "Przeskoczyłem przez kałużę. Miała 3 metry szerokości!" można podzielić na dwa zdania, wywołując na jego rzecz metodę split i przekazując do niej jako parametr łańcuch znaków zawierający kropkę (".").

a9c8a50d59776690f264c310a5514dcc a

88

Rozdział 6



Operacje na łańcuchach znaków

1 # python_lst177.py 2 3 """Hej.Siema!""".split(".")

>> ['Hej', 'Siema!']

Wynikiem wywołania funkcji split przedstawionej na powyższym przykładzie jest lista zawierająca dwa elementy, czyli łańcuch znaków zawierający wszystkie znaki umieszczone przed kropką oraz drugi łańcuch zawierający wszystkie znaki po kropce.

Metoda join Metoda join pozwala dodawać określone znaki pomiędzy wszystkimi znakami przekazanego łańcucha: 1 2 3 4 5

# python_lst178.py first_three = "abc" result = "+".join(first_three) result

>> 'a+b+c'

Przy użyciu metody join można także połączyć listę łańcuchów w jeden długi łańcuch znaków — wystarczy przekazać tę listę jako parametr metody join wywołanej na rzecz pustego łańcucha znaków: 1 2 3 4 5 6 7 8 9 10 11

# python_lst179.py words = ["Zwinny", "lis", "przeskoczy", "nad", "leniwym", "psem", "."] one = "".join(words) one

>> 'Zwinnylisprzeskoczynadleniwympsem.'

Nowy łańcuch znaków zawierający poszczególne słowa oddzielone od siebie odstępami można utworzyć, wywołując metodę join na rzecz łańcucha zawierającego jeden znak odstępu: 1 2 3 4 5 6 7 8 9 10 11

# python_lst180.py words = ["Zwinny", "lis", "przeskoczy", "nad", "leniwym", "psem", "."] one_string = " ".join(words) one_string

>> 'Zwinny lis przeskoczy nad leniwym psem .'

a9c8a50d59776690f264c310a5514dcc a

89

Usuwanie odstępów

Usuwanie odstępów Przy użyciu metody strip można usuwać znaki odstępu umieszczone na początku oraz na końcu łańcucha znaków: 1 2 3 4 5

# python_lst181.py s = " To s = s.strip() s

"

>> 'To'

Zastępowanie Metoda replace zastępuje każde wystąpienie określonego łańcucha znaków innym. Pierwszym parametrem tej metody jest łańcuch, który należy zastępować, a drugim — jego zamiennik: 1 2 3 4 5

# python_lst182.py equ = "Ostry wicher mrozi." equ = equ.replace("r", "@") print(equ)

>> Ost@y wiche@ m@ozi.

Znajdowanie indeksu Indeks pierwszego wystąpienia znaku w łańcuchu można określić przy użyciu metody index. W jej wywołaniu należy przekazać poszukiwany znak, a metoda zwróci indeks jego pierwszego wystąpienia w łańcuchu: 1 # python_lst183.py 2 3 "imperialny".index("r")

>> 4

Jeśli metoda index nie znajdzie przekazanego znaku, zgłosi wyjątek: 1 # python_lst184.py 2 3 "imperialny".index("z")

>> ValueError: substring not found

Jeśli nie mamy pewności, że w łańcuchu znajdziemy poszukiwany znak, najlepiej skorzystać z techniki obsługi wyjątków:

a9c8a50d59776690f264c310a5514dcc a

90

Rozdział 6



Operacje na łańcuchach znaków

1 # python_lst185.py 2 3 try: 4 "imperialny".index("z") 5 except: 6 print("Nie znaleziono znaku.")

>> Nie znaleziono znaku.

Metoda in Metoda in pozwala sprawdzić, czy jeden łańcuch znaków występuje w innym; metoda ta zwraca wartości logiczne True lub False: 1 # python_lst186.py 2 3 "Kot" in "Kot w butach."

>> True 1 # python_lst187.py 2 3 "Kat" in "Kot w butach."

>> False

Umieszczenie operatora not przed in pozwoli sprawdzić, czy jeden łańcuch znaków nie znajduje się w drugim: 1 # python_lst188.py 2 3 "Potter" not in "Harry"

>> True

Zabezpieczanie znaków specjalnych Jeśli spróbujemy umieścić cudzysłowy wewnątrz łańcucha znaków, Python zgłosi błąd składniowy: 1 # python_lst189.py 2 3 # Ten kod nie działa! 4 5 "Odpowiedziała mu: "Owszem.""

>> SyntaxError: invalid syntax

Ten błąd można rozwiązać, poprzedzając cudzysłowy wewnątrz łańcucha znakami odwrotnego ukośnika: 1 # python_lst190.py 2 3 "Odpowiedziała mu: \"Owszem.\""

>> 'Odpowiedziała mu: "Owszem."'

a9c8a50d59776690f264c310a5514dcc a

91

Znak nowego wiersza

1 # python_lst191.py 2 3 'Odpowiedziała mu: \"Owszem.\"'

>> 'Odpowiedziała mu: "Owszem."'

Zabezpieczanie polega na umieszczeniu symbolu przed znakiem, który normalnie ma w języku Python specjalne znaczenie (w powyższym przykładzie takim znakiem jest cudzysłów). Informuje ono język, że znak w danym przypadku należy potraktować jak normalny znak, bez żadnego specjalnego znaczenia. W języku Python do takiego zabezpieczania używane są znaki odwrotnego ukośnika. Nie trzeba zabezpieczać znaków apostrofu umieszczonych w łańcuchu zapisanym w cudzysłowach: 1 # python_lst192.py 2 3 "Odpowiedziała mu: 'Owszem.'"

>> "Odpowiedziała mu: 'Owszem.'"

Analogicznie cudzysłowy można umieszczać w łańcuchu zapisanym w apostrofach, co jest znacznie łatwiejsze niż ich zabezpieczanie: 1 # python_lst193.py 2 3 'Odpowiedziała mu: "Owszem."'

>> 'Odpowiedziała mu: "Owszem."'

Znak nowego wiersza Symbol \n umieszczony wewnątrz łańcucha znaków reprezentuje znak nowego wiersza: 1 # python_lst194.py 2 3 print("wiersz1\nwiersz2\nwiersz3")

>> wiersz1 >> wiersz2 >> wiersz3

Wycinki Tworzenie wycinka to proces polegający na utworzeniu danej iterowalnej stanowiącej podzbiór elementów danej pierwotnej. Taki wycinek można uzyskać przy użyciu następującego zapisu [dana_iterowalna][[indeks_początkowy]:[indeks_końcowy]]. Indeks początkowy to indeks pierwszego elementu tworzonego wycinka, a indeks końcowy to indeks jego ostatniego elementu.

a9c8a50d59776690f264c310a5514dcc a

92

Rozdział 6



Operacje na łańcuchach znaków

Poniższy przykład pokazuje sposób tworzenia wycinków: 1 # python_lst195.py 2 3 fict = ["Clavel", 4 "Camus", 5 "Orwell", 6 "Huxley", 7 "Austin"] 8 fict[0:3]

>> ['Clavel', 'Camus', 'Orwell']

Indeks początkowy obejmuje wskazany element, natomiast indeks końcowy wskazuje pierwszy element, który nie znajdzie się w tworzonym wycinku. Właśnie z tego powodu, aby utworzyć wycinek obejmujący elementy od "Clavel" (indeks 0) do "Orwell" (indeks 2), należy użyć indeksów 0 i 3. Poniżej przedstawiony został przykład tworzenia wycinków na podstawie łańcucha znaków: 1 2 3 4 5 6 7

# python_lst196.py ivan = """A żyć tak na skraju zguby \ trzeba samotnie""" ivan[0:10] ivan[26:41]

>> 'A żyć tak ' >> 'trzeba samotnie'

Jeśli początkowym indeksem wycinka jest 0, można je pominąć: 1 2 3 4 5 6

# python_lst197.py ivan = """A żyć tak na skraju zguby \ trzeba samotnie""" ivan[:17]

>> 'A żyć tak '

I podobnie, jeśli drugim indeksem jest indeks ostatniego elementu danej iterowalnej, także ten indeks można pominąć: 1 2 3 4 5 6

# python_lst198.py ivan = """A żyć tak na skraju zguby \ trzeba samotnie""" ivan[26:]

>> 'trzeba samotnie'

a9c8a50d59776690f264c310a5514dcc a

93

Słownictwo

Jeśli pominiemy oba indeksy, zwrócona zostanie cała pierwotna dana: 1 2 3 4 5 6

# python_lst199.py ivan = """A żyć tak na skraju zguby \ trzeba samotnie""" ivan[:]

>> "A żyć tak na skraju zguby trzeba samotnie"

Słownictwo Indeks końcowy. Indeks końca wycinka. Indeks początkowy. Indeks początku wycinka. Indeks ujemny. Indeks (musi być liczbą ujemną), którego można używać do pobierania elementów z danej iterowalnej w kolejności do prawej do lewej, a nie od lewej do prawej. Tworzenie wycinka. Sposób tworzenia nowej danej iterowalnej obejmującej podzbiór elementów danej pierwotnej. Zabezpieczanie. Umieszczanie określonego symbolu przed znakiem, który normalnie ma w języku Python specjalne znaczenie, po to, by znak ten został potraktowany jak zwyczajny znak.

Wyzwania 1. Wyświetl każdy znak łańcucha "Camus". 2. Napisz program, który będzie pobierać od użytkownika dwa łańcuchy znaków, a następnie wstawiać je do łańcucha "Wczoraj napisałem [łańcuch1] i wysłałem do [łańcuch2]." oraz wyświetlać. 3. Użyj odpowiedniej metody, by poprawić zdanie "aldous Huxley urodził się w 1894 roku." — ma ono zaczynać się od wielkiej litery. 4. Dysponując łańcuchem "Gdzie teraz? Kto teraz? Kiedy, teraz?", wywołaj metodę, która zwróci następujący wynik: ["Gdzie teraz?", "Kto teraz?", "Kiedy, teraz?"]. 5. Dysponując listą ["Zwinny", "lis", "przeskoczył", "nad", "leniwym", "psem", "."], przekształć ją w poprawne gramatycznie zdanie. Pomiędzy poszczególnymi słowami zdania mają być umieszczone odstępy, lecz nie ma być odstępu pomiędzy ostatnim słowem i kropką. (Nie zapomnij, że poznałeś metodę, która łączy elementy listy w jeden łańcuch znaków). 6. Zastąp każdą literę "s" w zdaniu: "W czasie suszy szosa sucha." znakiem dolara ($). 7. Użyj odpowiedniej metody, by zwrócić indeks wystąpienia litery "m" w słowie "Hemingway".

a9c8a50d59776690f264c310a5514dcc a

94

Rozdział 6



Operacje na łańcuchach znaków

8. W swojej ulubionej książce wyszukaj jakiś dialog (zawierający cudzysłowy) i zapisz go w kodzie programu jako łańcuch znaków. 9. Utwórz łańcuch znaków "trzy trzy trzy", używając najpierw konkatenacji, a następnie techniki powielania łańcuchów. 10. Dysponując łańcuchem znaków: "Długo na szturm i szaniec poglądał w milczeniu. Na koniec rzekł: 'Stracona'.", zwróć wycinek obejmujący znaki od początku do pierwszej kropki. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

7 Pętle

„Osiemdziesiąt procent sukcesu to pokazać się”. — Woody Alen

Drugi program, który przedstawiłem na początku tej książki, wyświetlał słowa Witaj, świecie! sto razy. Ten efekt uzyskałem dzięki zastosowaniu pętli, czyli fragmentu kodu, który cyklicznie wykonuje zadane instrukcje tak długo, jak długo będzie spełniony określony warunek. W tym rozdziale dowiesz się, czym są pętle i jak należy ich używać.

Pętle for W tym podrozdziale opisane zostanie stosowanie pętli for — pętli używanej do operowania na danych iterowalnych. Taki proces nazywany jest iterowaniem. Pętli for można używać do definiowania instrukcji, które zostaną wykonane dokładnie jeden raz dla każdego elementu danej iterowalnej, przy czym instrukcje te mogą odwoływać się do tego elementu oraz przeprowadzać na nim różnego rodzaju operacje. Przykładowo pętli for można użyć do przeglądnięcia listy łańcuchów znaków oraz zastosowania metody upper, by zmienić litery w tych łańcuchach na wielkie.

95

a9c8a50d59776690f264c310a5514dcc a

96

Rozdział 7



Pętle

Pętle for tworzy się za pomocą następującej składni: for [nazwa_zmiennej] in [nazwa_danej_iterowalnej]: [instrukcje]; gdzie [nazwa_zmiennej] to dowolna nazwa zmiennej, która zostanie przypisana do wartości każdego elementu danej iterowalnej, natomiast [instrukcje] to kod, który będzie wykonywany dla każdego z tych elementów. Poniżej przedstawiony został przykład pętli for zastosowanej do wyświetlenia wszystkich znaków w łańcuchu: 1 # python_lst200.py 2 3 name = "Ted" 4 for character in name: 5 print(character)

>> T >> e >> d

Podczas każdego wykonania kodu wewnątrz pętli zmienna character zostaje przypisana innemu elementowi danej iterowalnej name. Podczas pierwszej iteracji pętli zostanie wyświetlona litera T, gdyż zmienna character zostanie przypisana pierwszemu elementowi danej iterowalnej name. Podczas drugiej iteracji będzie wyświetlona litera e, gdyż zmienna character zostanie przypisana drugiemu elementowi danej iterowalnej name. Ten proces będzie kontynuowany aż do momentu, gdy wszystkie elementy danej iterowalnej zostaną przypisane zmiennej character. Kolejny przykład przedstawia pętlę for operującą na liście łańcuchów znaków: 1 # python_lst201.py 2 3 shows = ["Jaka to melodia", 4 "Spadkobiercy", 5 "Familiada"] 6 for show in shows: 7 print(show)

>> Jaka to melodia >> Spadkobiercy >> Familiada

Kolejny przykład przedstawia użycie pętli for do wyświetlenia elementów krotki: 1 # python_lst202.py 2 3 coms = ("Programowanie", 4 "Znajomi", 5 "Chillout") 6 for show in coms: 7 print(show)

>> Programowanie >> Znajomi >> Chillout

a9c8a50d59776690f264c310a5514dcc a

97

Pętle for

W kolejnym przykładzie pętla for została zastosowana do wyświetlenia wszystkich kluczy słownika: 1 # python_lst203.py 2 3 people = {"Fowler": 4 "Wzorce projektowe", 5 "Knuth": 6 "Algorytny", 7 "Stroustrup": 8 "C++" 9 } 10 11 for character in people: 12 print(character)

>> Knuth >> Stroustrup >> Fowler

Pętli for można także używać do wprowadzania zmian w modyfikowalnej danej iterowalnej, co pokazano na poniższym przykładzie: 1 2 3 4 5 6 7 8 9 10 11 12 13

# python_lst204.py tv = ["Jaka to melodia", "Spadkobiercy", "Familiada"] i = 0 for show in tv: new = tv[i] new = new.upper() tv[i] = new i += 1 print(tv)

>> ['JAKA TO MELODIA', 'SPADKOBIERCY', 'FAMILIADA']

W tym przykładzie użyliśmy pętli for do przejrzenia listy tv. Do określania aktualnie przetwarzanego elementu listy zastosowaliśmy zmienną indeksową — zmienną przechowującą liczbę całkowitą reprezentującą indeks przetwarzanego elementu danej iterowalnej. Początkową wartością zmiennej i jest 0, następnie wartość ta jest inkrementowana podczas każdej kolejnej iteracji pętli. Tej zmiennej indeksowej używamy do pobrania bieżącego elementu listy, który następnie zapisujemy w zmiennej new. Następnie wywołujemy metodę upper na rzecz zmiennej new, zapisujemy zwrócony wynik i używamy zmiennej indeksowej, by zastąpić tym wynikiem dotychczasową wartość elementu danej iterowalnej. W końcu inkrementujemy zmienną i; zatem podczas kolejnej iteracji pętli możemy przetworzyć kolejny element listy. Ponieważ odwoływanie się do poszczególnych elementów danych iterowalnych i korzystanie z ich indeksów jest bardzo częste, język Python udostępnia jeszcze inną składnię pętli for, którą można do tego celu zastosować:

a9c8a50d59776690f264c310a5514dcc a

98

Rozdział 7

1 2 3 4 5 6 7 8 9 10 11



Pętle

# python_lst205.py tv = ["Jaka to melodia", "Spadkobiercy", "Familiada"] for i, show in enumerate(tv): new = tv[i] new = new.upper() tv[i] = new print(tv)

>> ['JAKA TO MELODIA', 'SPADKOBIERCY', 'FAMILIADA']

Zamiast operować bezpośrednio na liście tv, przekazaliśmy ją w wywołaniu funkcji enumerate i w pętli operujemy na zwróconych przez nią wynikach — w ten sposób mogliśmy dodać do pętli nową zmienną i, która przechowuje indeks aktualnie przetwarzanego elementu listy. Pętli for można także używać do przenoszenia elementów pomiędzy modyfikowalnymi danymi iterowalnymi. Można na przykład użyć dwóch pętli for, by pobrać wszystkie łańcuchy znaków z dwóch list, zmienić w nich wszystkie litery na wielkie, po czym zapisać w nowej liście: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

# python_lst206.py

tv = ["Jaka to melodia", "Spadkobiercy", "Familiada"] coms = ["Programowanie", "Znajomi", "Chillout"] all_shows = [] for show in tv: show = show.upper() all_shows.append(show) for show in coms: show = show.upper() all_shows.append(show) print(all_shows)

>> ['JAKA TO MELODIA', 'SPADKOBIERCY', 'FAMILIADA', 'PROGRAMOWANIE', 'ZNAJOMI', 'CHILLOUT']

W tym przykładzie są używane trzy listy: tv, coms oraz all_shows. W pierwszej pętli przeglądamy wszystkie elementy listy tv, zapisujemy łańcuchy wielkimi literami, używając do tego metody upper, a następnie dodajemy je do listy all_shows za pomocą metody append. W drugiej pętli w taki sam sposób przetwarzamy elementy listy coms. Na końcu programu, kiedy wyświetlamy listę all_shows, zawiera ona wszystkie elementy z obu list zapisane wielkimi literami.

a9c8a50d59776690f264c310a5514dcc a

99

Funkcja range

Funkcja range Można skorzystać z funkcji range, by tworzyć sekwencje liczb całkowitych, a następnie operować na nich w pętlach for. Funkcja range posiada dwa parametry — liczbę, od której sekwencja się zaczyna, oraz liczbę, na której się kończy. Sekwencja liczb całkowitych zwróconych przez funkcję range zawiera wartość określoną pierwszym parametrem (wartość początkową zakresu), lecz nie zawiera wartości określonej drugim parametrem (wartości końcowej zakresu). Poniżej przedstawiony został przykład użycia funkcji range do utworzenia zakresu, a następnie zastosowanie go w pętli for: 1 # python_lst207.py 2 3 for i in range(1, 11): 4 print(i)

>> 1 ... >> 9 >> 10

W tym przykładzie użyliśmy pętli for do wyświetlenia wszystkich wartości z danej iterowalnej zwróconej przez funkcję range. Programiści bardzo często nadają zmiennym używanym w pętlach do przeglądania list liczb całkowitych nazwę i.

Pętle while W tym podrozdziale zostanie przedstawiona pętla while: pętla, która wykonuje kod tak długo, jak długo jej warunek będzie mieć wartość True. Pętla while ma następującą składnię: while [wyrażenie]: [kod_do_wykonania]; gdzie [wyrażenie] to warunek determinujący, czy pętla będzie dalej wykonywana, czy nie, a [kod_do_wykonania] reprezentuje kod, który pętla ma wykonywać, jeśli warunek będzie spełniony. Oto przykład pętli while: 1 2 3 4 5 6 7

# python_lst208.py x = 10 while x > 0: print('{}'.format(x)) x -= 1 print("Szczęśliwego Nowego Roku!")

>> >> >> >> >> >> >> >> >> >> >>

10 9 8 7 6 5 4 3 2 1 Szczęśliwego Nowego Roku!

a9c8a50d59776690f264c310a5514dcc a

100

Rozdział 7



Pętle

Pętle while wykonują umieszczony w nich kod tak długo, jak długo wyrażenie warunkowe zdefiniowane w ich nagłówku (w powyższym przykładzie jest to x > 0) przyjmuje wartość True. Podczas pierwszego wykonania pętli x ma wartość 10, zatem wyrażenie x > 10 ma wartość True. Kod umieszczony wewnątrz pętli wyświetla wartość x, a następnie pomniejsza ją o 1. A zatem, po wykonaniu kodu wewnątrz pętli x będzie mieć wartość 9. Podczas kolejnej iteracji pętli zmienna x jest ponownie wyświetlana i pomniejszana o 1, czyli w efekcie przyjmie wartość 8. Ten proces jest kontynuowany tak długo, aż wartość zmiennej x zostanie pomniejszona do 0, gdyż w tym momencie warunek x > 0 przyjmie wartość False i wykonywanie pętli się zakończy. Kiedy to nastąpi, Python wykona kolejny wiersz kodu umieszczony poniżej pętli, który wyświetla komunikat Szczęśliwego Nowego Roku!. Jeśli zdefiniujemy pętlę while zawierającą warunek, który zawsze będzie mieć wartość True, pętla będzie wykonywana w nieskończoność. Takie pętle są nazywane pętlami nieskończonymi. Poniżej został przedstawiony przykład pętli nieskończonej (pamiętaj, że ten kod po uruchomieniu trzeba będzie przerwać, naciskając w interaktywnej powłoce kombinację klawiszy Ctrl+C): 1 # python_lst209.py 2 3 while True: 4 print("Witaj, świecie!")

>> Witaj, świecie!

Pętla while działa tak długo, jak długo wyrażenie warunkowe umieszczone w jej nagłówku przyjmuje wartość True; a ponieważ True zawsze będzie mieć wartość True, zatem powyższa pętla będzie działać w nieskończoność.

Instrukcja break Instrukcja break służy do przerywania działania pętli. Pętla przedstawiona na kolejnym przykładzie zostanie wykonana sto razy: 1 # python_lst210.py 2 3 for i in range(0, 100): 4 print(i)

>> 0 >> 1 ...

Jeśli do takiej pętli dodamy instrukcję break, kod wewnątrz pętli zostanie wykonany tylko raz: 1 # python_lst211.py 2 3 for i in range(0, 100): 4 print(i) 5 break

>> 0

a9c8a50d59776690f264c310a5514dcc a

101

Instrukcja continue

Jak tylko Python dotrze do instrukcji break, wykonywanie pętli zostanie przerwane. Pętli while oraz instrukcji break można użyć do napisania programu, który będzie prosił użytkownika o wpisywanie danych aż do chwili, gdy użytkownik wpisze na przykład pojedynczą literę q: 1 2 3 4 5 6 7 8 9 10 11 12

# python_lst212.py qs = ["Jak masz na imię?", "Jaki jest twój ulubiony kolor?", "Jakie masz zadanie?"] n = 0 while True: print("Wpisz q, aby zakończyć") a = input(qs[n]) if a == "q": break n = (n + 1) % 3

Wpisz q, aby zakończyć Jak masz na imię?

Podczas każdego wykonania pętli program zada użytkownikowi jedno z pytań zapisanych na liście qs. W powyższym przykładzie n jest zmienną indeksową. Podczas każdej iteracji pętli zmienna n jest przypisywana do wyniku wyrażenia (n + 1) % 3, co pozwala nieskończenie długo, cyklicznie zadawać pytania z listy qs. Podczas pierwszej iteracji pętli zmienna n ma wartość 0. Następnie zmiennej tej jest przypisywana wartość (0 + 1) % 3, czyli 1. Podczas kolejnej iteracji pętli zmienna ta jest przypisywana wynikowi wyrażenia (1 + 1) % 3, czyli liczbie 2, gdyż zawsze, kiedy pierwsza wartość w wyrażeniu z operatorem modulo jest mniejsza od drugiej, wynikiem wyrażenia jest pierwsza wartość. W końcu n jest przypisywana wartości wyrażenia (2 + 1) % 3, czyli liczbie 0.

Instrukcja continue Instrukcji continue można używać do przerwania wykonywania bieżącej iteracji pętli i rozpoczęcia kolejnej. Załóżmy, że chcemy wyświetlić wszystkie liczby z zakresu od 1 do 5 z wyjątkiem liczby 3. Możemy to łatwo zrobić, używając pętli for oraz instrukcji continue: 1 # python_lst213.py 2 3 for i in range(1, 6): 4 if i == 3: 5 continue 6 print(i)

>> >> >> >>

1 2 4 5

Kiedy w tej pętli zmienna i przyjmie wartość 3, zostanie wykonana instrukcja continue — nie powoduje ona jednak całkowitego zakończenia pętli, tak jak instrukcja break, lecz przejście do następnej iteracji z pominięciem pozostałego kodu pętli. A zatem, kiedy zmienna i przyjmie

a9c8a50d59776690f264c310a5514dcc a

102

Rozdział 7



Pętle

wartość 3, zostanie wykonana instrukcja continue, a w efekcie pętla nie wyświetli bieżącej wartości zmiennej iteracyjnej. Dokładnie ten sam efekt można uzyskać przy użyciu pętli while oraz instrukcji continue: 1 # python_lst214.py 2 3 i = 1 4 while i > >> >> >>

1 2 4 5

Pętle zagnieżdżone Pętle można ze sobą łączyć na różne sposoby. Można na przykład umieścić pętlę w pętli lub pętlę w pętli i w pętli. Nie ma żadnego limitu określającego, jak wiele razy można w taki sposób zagnieżdżać pętle, choć bez wątpienia będziemy chcieli to ograniczyć. Kiedy jedna pętla jest wewnątrz drugiej, ta zewnętrzna jest nazywana pętlą zewnętrzną, a ta zagnieżdżona — pętlą wewnętrzną. W pętlach zagnieżdżonych pętla wewnętrzna przegląda całą swoją zmienną w ramach pojedynczej iteracji pętli zewnętrznej: 1 # python_215.py 2 3 for i in range(1, 3): 4 print(i) 5 for letter in ["a", "b", "c"]: 6 print(letter)

>> >> >> >> >> >> >> >>

1 a b c 2 a b c

Pętla zagnieżdżona operuje na liście ["a", "b", "c"] i przejrzy ją w całości dokładnie tyle razy, ile razy zostanie wykonana pętla zewnętrzna — czyli w tym przypadku dwa. Gdybyśmy zmienili zewnętrzną pętlę, aby wykonywała trzy iteracje, wewnętrzna pętla przeglądnęłaby swoją listę trzy razy.

a9c8a50d59776690f264c310a5514dcc a

103

Pętle zagnieżdżone

Dwóch pętli for można użyć na przykład po to, by dodać każdą z liczb z jednej listy do wszystkich liczb umieszczonych na innej liście: 1 2 3 4 5 6 7 8 9 10

# python_lst216.py list1 = [1, 2, 3, 4] list2 = [5, 6, 7, 8] added = [] for i in list1: for j in list2: added.append(i + j) print(added)

>> [6, 7, 8, 9, 7, 8, 9, 10, 8, 9, 10, 11, 9, 10, 11, 12]

Pierwsza pętla operuje na wszystkich liczbach zapisanych na liście list1. Dla każdej z tych liczb wewnętrzna pętla przegląda wszystkie liczby z listy list2, dodaje bieżące liczby z obu list, a następnie zapisuje ich sumę na trzeciej liście — added. Zmiennej indeksowej używanej w drugiej pętli for nadałem nazwę j, gdyż zmienna o nazwie i jest już wykorzystywana w pętli zewnętrznej. Można także zagnieżdżać pętlę for wewnątrz pętli while i na odwrót: 1 # python_lst217.py 2 3 while input('t czy n?') != 'n': 4 for i in range(1, 6): 5 print(i)

>> >> >> >> >> >> >> >> >> >> >> >> >>

t czy n?t 1 2 3 4 5 t czy n?t 1 2 3 4 5 t czy n?n

Ten program będzie prosił użytkownika o wpisanie znaku i wyświetlał liczby od 1 do 5 tak długo, aż użytkownik wpisze n.

a9c8a50d59776690f264c310a5514dcc a

104

Rozdział 7



Pętle

Słownictwo Instrukcja break. Instrukcja ze słowem kluczowym break, używana do zakończenia pętli. Instrukcja continue. Instrukcja ze słowem kluczowym continue, używana do przerywania bieżącej iteracji pętli oraz do rozpoczęcia kolejnej iteracji. Iterowanie. Stosowanie pętli do przeglądnięcia wszystkich elementów danej iterowalnej. Pętla for. Pętla używana do przeglądania danej iterowalnej, takiej jak łańcuch znaków, lista, krotka czy słownik. Pętla nieskończona. Pętla, która nigdy się nie kończy. Pętla wewnętrzna. Pętla umieszczona wewnątrz innej pętli. Pętla while. Pętla wykonująca instrukcje tak długo, jak długo warunek przyjmuje wartość True. Pętla zewnętrzna. Pętla, wewnątrz której jest umieszczona inna pętla. Pętla. Fragment kodu, który bezustannie wykonuje zadane instrukcje tak długo, jak długo spełniony jest warunek podany w jego nagłówku. Zmienna indeksowa. Zmienna przechowująca liczbę całkowitą reprezentującą indeks aktualnie przeglądanego elementu danej iteracyjnej.

Wyzwania 1. Wyświetl każdy element listy ["Noc żywych trupów", "Ekipa", "Rodzina Soprano", "Pamiętniki wampirów"]. 2. Wyświetl wszystkie liczby z zakresu od 25 do 50. 3. Wyświetl wszystkie elementy listy z wyzwania pierwszego wraz z ich indeksami. 4. Napisz program zawierający pętlę nieskończoną (z opcją naciśnięcia klawisza q w celu jej przerwania) oraz listę liczb. Podczas każdej iteracji tej pętli proś użytkownika o odgadnięcie liczby z listy, a następnie wyświetlaj informację, czy udało mu się poprawnie trafić, czy nie. 5. Pomnóż wszystkie liczby z listy [8, 19, 148, 4] przez wszystkie liczby z listy [9, 1, 33, 83], a uzyskane wyniki zapisz w trzeciej liście. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

8 Moduły

„Wytrwałość i hart ducha zawsze czyniły cuda”. — George Washington

Wyobraź sobie, że napisałeś program liczący 10 tysięcy wierszy kodu. Gdybyś zapisał cały ten kod w jednym pliku, przeglądanie go byłoby bardzo utrudnione. Za każdym razem gdy w kodzie pojawiłby się wyjątek lub błąd, musiałbyś przeglądać całe 10 tysięcy wierszy kodu, by odnaleźć ten, w którym występuje problem. Programiści rozwiązują problemy tego typu, dzieląc duże programy na mniejsze części nazywane modułami — to inna nazwa pliku zawierającego część kodu programu. Python pozwala na używanie kodu pochodzącego z jednego modułu w innym module. Język ten udostępnia także moduły wbudowane, zawierające ważne możliwości funkcjonalne. W tym rozdziale zostaną opisane moduły oraz sposoby ich tworzenia i stosowania.

Moduły wbudowane Aby użyć modułu, należy go zaimportować, czyli napisać kod, dzięki któremu Python będzie wiedzieć, gdzie należy szukać danego modułu. Do importowania modułów jest używana składnia import [nazwa_modułu]. Przy czym fragment [nazwa_modułu] należy zastąpić nazwą modułu, który chcemy zaimportować. Po zaimportowaniu modułu można używać dostępnych w nim funkcji i zmiennych. 105

a9c8a50d59776690f264c310a5514dcc a

106

Rozdział 8



Moduły

Python udostępnia wiele wbudowanych modułów, takich jak math, który zawiera możliwości funkcjonalne związane z obliczeniami matematycznymi. Listę wszystkich wbudowanych modułów Pythona można znaleźć na stronie https://docs.python.org/3/py-modindex.html. Poniższy przykład pokazuje, jak zaimportować wbudowany moduł math: 1 # python_lst218.py 2 3 import math

Po zaimportowaniu modułu można używać udostępnianego przez niego kodu, posługując się składnią [nazwa_modułu].[kod]; przy czym fragment [nazwa_modułu] należy zastąpić nazwą zaimportowanego modułu, a fragment [kod] — funkcją lub zmienną, której chcemy użyć. Poniższy przykład importuje z modułu math funkcję pow, która ma dwa parametry, x oraz y, i zwraca wynik będący wartością x podniesioną do potęgi y: 1 # python_lst219.py 2 3 import math 4 5 math.pow(2, 3)

>> 8.0

W tym przykładzie najpierw importujemy moduł math. Instrukcje importu należy umieszczać na samym początku pliku, tak by łatwo było zorientować się, jakie moduły są używane w kodzie. Następnie wywołujemy funkcję pow, używając zapisu math.pow(2, 3). To wywołanie zwraca wartość 8.0. Kolejnym z wbudowanych modułów języka Python jest random. Udostępnianej przez niego funkcji randint można używać do wygenerowania losowej liczby całkowitej — w jej wywołaniu należy przekazać dwie liczby całkowite, a funkcja zwróci wartość z określonego przez nie zakresu. 1 2 3 4 5 6 7 8

# python_lst220.py # Wynik może być inny niż 52, # w końcu to ma być liczba losowa! import random random.randint(0,100)

>> 52

Wbudowanego modułu statistics można używać do obliczeń statystycznych, takich jak mediana, średnia czy dominanta, wykonywanych na danej iteracyjnej zawierającej liczby: 1 2 3 4 5 6 7 8 9

# python_lst221.py import statistics # średnia nums = [1, 5, 33, 12, 46, 33, 2] statistics.mean(nums)

a9c8a50d59776690f264c310a5514dcc a

107

Importowanie innych modułów

10 11 12 13 14 15

# mediana statistics.median(nums) # dominanta statistics.mode(nums)

>> 18.857142857142858 >> 12 >> 33

Wbudowanego modułu keyword można używać, by sprawdzać, czy podane słowo jest słowem kluczowym języka Python: 1 2 3 4 5 6

# python_lst222.py import keyword keyword.iskeyword("for") keyword.iskeyword("football")

>> True >> False

Importowanie innych modułów W tym podrozdziale napiszemy własny moduł, zaimportujemy go i użyjemy kodu, który udostępnia. Najpierw utworzymy katalog o nazwie tstp. Wewnątrz tego katalogu musimy utworzyć plik o nazwie hello.py, a w nim umieścić kod przedstawiony na poniższym listingu: 1 # python_lst223.py 2 3 def print_hello(): 4 print("Witaj")

W tym samym katalogu tstp utworzymy także plik project.py, w którym umieścimy następujący kod: 1 # python_lst224.py 2 3 import hello 4 5 hello.print_hello()

>> Witaj

W tym przykładzie zastosowaliśmy słowo kluczowe import, by użyć kodu z jednego modułu w innym module. Podczas importowania modułu wykonywany jest cały umieszczony w nim kod. Utwórzmy kolejny plik, module1.py, zawierający poniższy kod: 1 # python_lst225.py 2 3 # kod modułu module1 4 print("Witaj!");

a9c8a50d59776690f264c310a5514dcc a

108

Rozdział 8



Moduły

Kod modułu module1.py zostanie wykonany, kiedy zaimportujemy ten moduł w module module2.py: 1 # python_lst226.py 2 3 # kod modułu module2 4 import module1

>> Witaj!

Takie działanie może być niepożądane. Przykładowo w module może być umieszczony kod testowy, który nie powinien być wykonywany podczas importowania modułu. Taki problem można rozwiązać, umieszczając cały taki kod w instrukcji warunkowej if o następującej postaci: if __name__ == "__main__". I tak kod w pliku module1.py z wcześniejszego przykładu można by zmienić w następujący sposób: 1 # python_lst227.py 2 3 # kod modułu module1 4 if __name__ == "__main__": 5 print("Witaj!")

>> Witaj!

Po uruchomieniu tego pliku bezpośrednio generowane przez niego wyniki pozostaną takie same. Kiedy jednak zaimportujemy go do modułu module2.py, tekst Witaj! nie zostanie już wyświetlony: 1 # python_lst228.py 2 3 # kod modułu module2 4 import module1

Słownictwo Moduł. Inna nazwa pliku z kodem w języku Python. Moduł wbudowany. Moduły dostarczane wraz z językiem Python i zawierające ważne możliwości funkcjonalne. Importowanie. Pisanie kodu, który informuje język Python, gdzie należy szukać modułu, którego chcemy używać w kodzie.

Wyzwania 1. Wywołaj kilka innych funkcji z modułu statistics. 2. Utwórz moduł o nazwie cubed definiujący funkcję pobierającą jako parametr liczbę i zwracającą tę liczbę podniesioną do potęgi trzeciej. Zaimportuj ten moduł w innym. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

9 Pliki

„Samodzielna nauka, mocno w to wierzę, jest jedyną istniejącą formą edukacji”. — Isaac Asimov

Języka Python można używać do pracy z plikami. Można na przykład napisać w Pythonie kod, który będzie odczytywał dane z pliku lub zapisywał coś w pliku. Odczytywanie danych z pliku oznacza uzyskiwanie dostępu do danych zapisanych w tym pliku. Zapisywanie danych w pliku oznacza dodawanie nowych informacji do pliku bądź też zmianę tych, które są w nim zapisane. W tym rozdziale przedstawione zostaną podstawowe informacje związane z operowaniem na plikach.

Zapisywanie danych w pliku Pierwszym etapem wszelkich operacji na plikach w języku Python jest otworzenie pliku przy użyciu metody open. Funkcja ta ma dwa parametry — pierwszym jest łańcuch znaków określający ścieżkę dostępu do otwieranego pliku, a drugim — łańcuch znaków określający tryb, w jakim plik ma zostać otworzony. Ścieżka dostępu do pliku, nazywana także ścieżką pliku, określa położenie pliku na komputerze. Przykładowo C:\Users\bob\st.txt to ścieżka pliku o nazwie st.txt. Każde słowo oddzielone znakiem odwrotnego ukośnika (w systemie Windows, w systemach Linux jest do tego celu używany znak 109

a9c8a50d59776690f264c310a5514dcc a

110

Rozdział 9



Pliki

ukośnika) jest nazwą katalogu. Połączenie tych wszystkich katalogów i nazwy pliku określa lokalizację tego pliku. Jeśli ścieżka pliku składa się tylko z jego nazwy (nie ma w niej żadnych katalogów), Python będzie szukać pliku w tym samym katalogu, w którym został uruchomiony aktualnie wykonywany program. Ścieżki dostępu do pliku należy pisać samodzielnie. Systemy bazujące na Linuksie oraz Windowsie używają innych znaków oraz innych liczb tych znaków w ścieżkach dostępu. Aby rozwiązać ten problem i zapewnić możliwość używania programu w różnych systemach operacyjnych, ścieżki zawsze należy tworzyć przy użyciu wbudowanego modułu języka Python o nazwie os. Udostępniana przez niego funkcja path pozwala na przekazanie, w formie parametrów, nazw wszystkich katalogów w ścieżce dostępu i zwraca łańcuch znaków zawierający tę ścieżkę: 1 # python_lst229.py 2 3 import os 4 5 os.path.join("Users", 6 "bob", 7 "st.txt")

>> 'Users\bob\st.txt'

Tworzenie ścieżek przy użyciu funkcji path zapewnia, że będą one działać w każdym systemie operacyjnym. Praca ze ścieżkami dostępu może być trudna. W razie problemów pomocy i dodatkowych informacji na ten temat można poszukać na stronie http://theselftaughtprogrammer.io/ filepaths. Tryb przekazywany w wywołaniu funkcji open określa operacje, które będzie można wykonywać na otwieranym pliku. Mamy do wyboru kilka trybów: 

"r" otwiera plik tylko do odczytu;



"w" otwiera plik tylko do zapisu, jeśli plik już istnieje, zostanie nadpisany, jeśli pliku jeszcze nie ma, zostanie utworzony pusty;



"w+" otwiera plik w trybie do odczytu i zapisu. Jeśli plik już istnieje, zostanie nadpisany. Jeśli pliku jeszcze nie ma, zostanie utworzony nowy1.

Funkcja open zwraca obiekt nazywany obiektem pliku, którego następnie można używać do odczytywania bądź zapisywania danych w pliku. W przypadku zastosowania trybu "w" funkcja open tworzy nowy plik (o ile plik jeszcze nie istnieje) w katalogu, w którym program został uruchomiony. Po otworzeniu pliku można używać metody write, wywoływanej na rzecz obiektu pliku, by zapisywać dane w pliku, oraz metody close, by zamknąć plik. Jeśli użyjemy funkcji open, by otworzyć w programie wiele plików, a następnie zapomnimy je zamknąć, możemy doprowadzić do wystąpienia problemów. Poniżej przedstawiony został przykład programu, który otwiera plik, zapisuje w nim łańcuch znaków, a następnie zamyka plik.

1

https://www.tutorialspoint.com/python/python_files_io.htm

a9c8a50d59776690f264c310a5514dcc a

111

Automatyczne zamykanie plików

1 2 3 4 5

# python_lst230.py st = open("st.txt", "w") st.write("Cześć... zapisano z Pythona!") st.close()

W tym przykładzie używamy funkcji open do otworzenia pliku i zapisujemy zwrócony obiekt w zmiennej st. Następnie wywołujemy metodę write obiektu st, przekazując do niej jako parametr łańcuch znaków, który chcemy zapisać w pliku. W efekcie Python utworzy nowy plik i zapisze w nim przekazany łańcuch. Na samym końcu programu zamykamy plik, wywołując metodę close.

Automatyczne zamykanie plików Istnieje także drugi, preferowany sposób otwierania plików, który pozwala uniknąć konieczności samodzielnego ich zamykania. Wymaga on umieszczenia całego kodu operującego na pliku wewnątrz tak zwanej instrukcji with; jest to instrukcja złożona, która po zakończeniu wykonywania automatycznie zamknie plik. Instrukcja ta ma postać with open([ścieżka_pliku], [tryb]) as [nazwa_zmiennej]: [kod]; przy czym [ścieżka_pliku] to ścieżka dostępu do otwieranego pliku, [tryb] to tryb dostępu, [nazwa_zmiennej] to nazwa zmiennej, w której zostanie zapisany obiekt pliku zwrócony przez funkcję open, i w końcu [kod] to kod, w którym będzie używana zmienna z obiektem pliku. W razie użycia do otwierania pliku tego zapisu plik zostanie automatycznie zamknięty po wykonaniu instrukcji określonych jako [kod]. Poniżej przedstawiony został dokładnie ten sam przykład, co wcześniej, przy czym w tym przypadku zastosowana została instrukcja with: 1 # python_lst231.py 2 3 with open("st.txt", "w") as f: 4 f.write("Cześć... zapisano z Pythona!")

Tak długo, jak długo będziemy się znajdować wewnątrz instrukcji with, będziemy także mogli odwoływać się do obiektu pliku — w tym przypadku będzie to zmienna f. Jednak zaraz po tym, gdy Python zakończy wykonywanie kodu umieszczonego wewnątrz instrukcji with, plik zostanie zamknięty.

Odczyt z plików Jeśli chcemy odczytywać dane z pliku, należy przekazać łańcuch "r" jako drugi parametr wywołania funkcji open. Następnie można używać metody read uzyskanego obiektu pliku, która zwraca daną iterowalną zawierającą każdy wiersz tekstu odczytany ze wskazanego pliku: 1 # python_lst232.p 2 3 # upewnij się, że został wykonany 4 # poprzedni przykład, kiedy chcesz

a9c8a50d59776690f264c310a5514dcc a

112

Rozdział 9



Pliki

5 # utworzyć używany tu plik 6 7 with open("st.txt", "r") as f: 8 print(f.read())

>> Cześć... zapisano z Pythona!

Metodę read można wywołać na danym pliku tylko jeden raz — aby można było to zrobić ponownie, trzeba by plik zamknąć i ponownie otworzyć. Jeśli zatem chcemy używać danych odczytanych z pliku w dalszej części programu, musimy zapisać całą zawartość pliku w kontenerze lub zmiennej. Poniżej pokazałem, jak można zapisać zawartość utworzonego wcześniej pliku w liście: 1 2 3 4 5 6 7 8

# python_lst233.py my_list = list() with open("st.txt", "r") as f: my_list.append(f.read()) print(my_list)

>> ['Cześć... zapisano z Pythona!']

Teraz już będzie można używać tych danych w dalszej części programu.

Pliki CSV Python jest dostarczany z wbudowanym modułem pozwalającym na korzystanie z plików CSV. Pliki CSV mają rozszerzenie .csv i zawierają zwyczajne dane tekstowe rozdzielone przecinkami (CSV to skrót od angielskich słów Comma Separated Values, czyli właśnie wartości rozdzielone przecinkami). Programy zarządzające arkuszami kalkulacyjnymi, takie jak Excel, bardzo często obsługują także format CSV. W pliku CSV każda informacja oddzielona od pozostałych przecinkami reprezentuje zawartość jednej komórki arkusza kalkulacyjnego; natomiast każdy wiersz pliku reprezentuje jeden wiersz arkusza. Separator to symbol, taki jak przecinek lub pionowa kreska (|), który jest używany do rozdzielania poszczególnych danych w pliku CSV. Poniżej przedstawiona została zawartość pliku CSV o nazwie self_taught.csv: jeden,dwa,trzy cztery,pięć,sześć

Taki plik można wczytać do Excela i wartości jeden, dwa i trzy zostaną zapisane w komórkach pierwszego wiersza arkusza, a wartości cztery, pięć i sześć w komórkach drugiego wiersza. Do otwierania plików CSV można używać instrukcji with, jednak wewnątrz niej trzeba skorzystać z modułu csv, aby skonwertować obiekt pliku na obiekt csv. Moduł csv udostępnia metodę writer, która wymaga przekazania obiektu pliku oraz separatora. Metoda ta zwraca obiekt csv, udostępniający metodę writerow. Metoda ta ma parametr pozwalający na przekazanie listy wartości, które zostaną zapisane w pliku CSV. Każdy element tej listy zostanie zapisany w jednym wierszu pliku CSV, przy

a9c8a50d59776690f264c310a5514dcc a

113

Pliki CSV

czym poszczególne elementy będą od siebie oddzielone separatorem przekazanym w wywołaniu metody writer. Metoda writerow tworzy tylko jeden wiersz, a zatem, by utworzyć dwa wiersze, trzeba ją wywołać dwukrotnie: 1 # python_lst234.py 2 3 import csv 4 5 with open("st.csv", "w", newline='') as f: 6 write = csv.writer(f, delimiter=",") 7 write.writerow(["jeden", 8 "dwa", 9 "trzy"]) 10 write.writerow(["cztery", 11 "pięć", 12 "sześć"])

Ten program tworzy nowy plik o nazwie st.csv, który po otworzeniu w edytorze będzie wyglądał następująco: jeden,dwa,trzy cztery,pięć,sześć

Jeśli otworzymy ten plik w programie Excel (lub programie Arkusze Google — darmowej alternatywie Excela), przecinki znikną, wartości jeden, dwa i trzy zostaną umieszczone w pierwszym wierszu, a wartości cztery, pięć i sześć — w drugim. Modułu csv można także używać do odczytywania zawartości plików. Aby odczytać dane z pliku CSV, należy zacząć od wywołania metody open i przekazania do niej jako drugiego parametru łańcucha "r" — spowoduje to, że plik zostanie otworzony w trybie do odczytu. Następnie, wewnątrz instrukcji with, można używać metody reader, przekazując do niej obiekt pliku oraz przecinek (jak separator) — metoda ta zwróci daną iterowalną pozwalającą na odczytanie poszczególnych wierszy pliku. 1 2 3 4 5 6 7 8 9 10 11 12

# python_lst235.py import csv # upewnij się, że istnieje # plik utworzony # w poprzednim przykładzie with open("st.csv", "r") as f: r = csv.reader(f, delimiter=",") for row in r: print(",".join(row))

>> jeden,dwa,trzy >> cztery,pięć,sześć

W tym przykładzie otwieramy plik st.csv w trybie do odczytu, a następnie odczytujemy całą jego zawartość, wywołując metodę reader modułu csv i zapisując zwrócony wynik w zmiennej r.

a9c8a50d59776690f264c310a5514dcc a

114

Rozdział 9



Pliki

Następnie możemy już w pętli przejrzeć zwrócone dane. Podczas każdej iteracji pętli używamy metody join, by dodać przecinki pomiędzy poszczególnymi elementami odczytanego wiersza i wyświetlić wynik w takiej samej postaci, w jakiej elementy są zapisane w pliku (czyli rozdzielone przecinkami).

Słownictwo Instrukcja with. Instrukcja złożona dysponująca akcją, która jest automatycznie wykonywana po zakończeniu instrukcji. Obiekt pliku. Obiekt, którego można używać do odczytu i zapisu plików. Odczyt. Dostęp do zawartości pliku. Plik CSV. Plik z rozszerzeniem .csv, w którym dane są rozdzielone przecinkami (stąd też pochodzi nazwa pliku, Comma Separated Values — wartości rozdzielone przecinkami). Pliki w tym formacie są często używane przez programy obsługujące arkusze kalkulacyjne, takie jak Excel. Separator. Symbol, taki jak przecinek, używany do rozdzielania poszczególnych danych zapisanych w pliku CSV. Ścieżka pliku. Lokalizacja pliku na komputerze. Zapis. Dodawanie lub zmiana danych w pliku.

Wyzwania 1. Znajdź na swoim komputerze jakiś plik i wyświetl jego zawartość. 2. Napisz program, który zada użytkownikowi pytanie i zapisze odpowiedź w pliku. 3. Napisz program, który zapisze w pliku CSV zawartość następującej listy list: [["Top Gun", "Ocean's Eleven", "Raport mniejszości"], ["Titanic", "Ostatni Jedi", "Incepcja"],["Pulp Fiction", "Człowiek w ogniu", "Seksmisja"]]. Dane z każdej z list

powinny zostać zapisane w osobnych wierszach i rozdzielone przecinkami. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

10 Łączenie wszystkiego w całość

„Wszystko, czego się nauczyłem, nauczyłem się z książek”. — Abraham Lincoln

W tym rozdziale połączymy wszystkie pojęcia przedstawione do tej pory i napiszemy tekstową grę — klasycznego „wisielca”. Jeśli jednak nigdy w wisielca nie grałeś, poniżej przedstawiam jego zasady. 1. Pierwszy gracz wymyśla sobie jakieś tajemne słowo i dla każdej litery, z której się ono składa, rysuje poziomą kreskę (do tego celu użyjemy znaku podkreślenia). 2. Drugi gracz próbuje odgadnąć słowo litera po literze. 3. Jeśli drugi gracz odgadnie literę, gracz pierwszy zapisuje ją w odpowiednim miejscu słowa zamiast znaku podkreślenia. W tej wersji gry, jeśli litera występuje dwa razy, każde z jej wystąpień trzeba będzie odgadnąć osobno. ALBO Jeśli drugiemu graczowi nie uda się odgadnąć litery, pierwszy gracz rysuje fragment figurki wisielca (zaczynając od głowy; patrz rysunek 10.1). 4. Jeśli drugi gracz odgadnie całe słowo, zanim figurka wisielca zostanie w całości narysowana, wygrywa. W przeciwnym razie przegrywa. 115

a9c8a50d59776690f264c310a5514dcc a

116

Rozdział 10



Łączenie wszystkiego w całość

Rysunek 10.1. Gra w wisielca

W naszym programie rolę pierwszego gracza będzie pełnił komputer, natomiast użytkownik programu to drugi gracz. A zatem, czy jesteś gotów wziąć się za pisanie gry w wisielca?

Wisielec Oto początkowy fragment kodu gry w wisielca: 1 # python_lst236.py 2 3 def hangman(word): 4 wrong = 0 5 stages = ["", 6 "________ ", 7 "| ", 8 "| | ", 9 "| 0 ", 10 "| /|\ ", 11 "| / \ ", 12 "| " 13 ] 14 rletters = list(word) 15 board = ["__"] * len(word) 16 win = False 17 print("Gra w wisielca")

Zaczniemy od napisania funkcji o nazwie hangman, która będzie główną funkcją gry. Funkcja ta ma jeden parametr o nazwie word, który będzie określał słowo, jakie drugi gracz ma odgadnąć. Kolejna zmienna, o nazwie wrong, będzie przechowywać liczbę określającą, ile razy gracz pomylił się podczas odgadywania liter. Zmienna stages to lista łańcuchów znaków, które gra będzie wyświetlać w celu narysowania figurki wisielca. Kiedy program wyświetli wszystkie łańcuchy z tej listy w kolejnych wierszach, powstanie kompletna figurka wisielca. Zmienna rletters to lista zawierająca wszystkie litery słowa word; listy będziemy używali do śledzenia, ile liter pozostało jeszcze do odgadnięcia.

a9c8a50d59776690f264c310a5514dcc a

117

Wisielec

Zmienna board to lista łańcuchów znaków, której będziemy używali do wyświetlania podpowiedzi dla użytkownika, takich jak k_t, jeśli słowem do odgadnięcia jest kot (a gracz prawidłowo odgadł litery k i t). Do określenia początkowej zawartości tej listy używamy wyrażenia ["_"] * len(word), czyli zapisujemy w niej znak podkreślenia dla każdej litery zgadywanego słowa. Przykładowo dla słowa kot początkową zawartością zmiennej board będzie ["_", "_", "_"]. Oprócz tego w programie będziemy używali zmiennej win, której początkową wartością będzie False; to ona będzie śledzić, czy gracz już wygrał grę, czy jeszcze nie. Na samym końcu funkcji wyświetlamy komunikat: Gra w wisielca. Kolejnym fragmentem kodu jest pętla obsługująca główny przebieg gry: 1 # python_lst237.py 2 3 while wrong < len(stages) - 1: 4 print("\n") 5 msg = "Odgadnij literę: " 6 char = input(msg) 7 if char in rletters: 8 cind = rletters.index(char) 9 board[cind] = char 10 rletters[cind] = '$' 11 else: 12 wrong += 1 13 print((" ".join(board))) 14 e = wrong + 1 15 print("\n".join(stages[0: e])) 16 if "__" not in board: 17 print("Wygrałeś!") 18 print(" ".join(board)) 19 win = True 20 break

Nasza pętla (a zatem także i gra) jest wykonywana tak długo, jak długo wartość zmiennej wrong jest mniejsza od wyrażenia len(stages) - 1. Zmienna wrong przechowuje liczbę nieudanych prób odgadnięcia litery przez gracza; a zatem gra zostanie zakończona, jak tylko liczba tych nieudanych prób przewyższy liczbę łańcuchów używanych do wyświetlenia kompletnej postaci wisielca (czyli liczbę łańcuchów na liście stages). Długość listy stages jest pomniejszana o 1, aby uwzględnić fakt, że elementy listy są indeksowane od 0, a zliczanie nieudanych prób zaczyna się od 1. Wewnątrz pętli zaczynamy od wyświetlenia znaku odstępu, aby gra wyglądała ładnie w wierszu poleceń. Następnie prosimy gracza o podanie litery (używając do tego celu funkcji input) i zapisujemy ją w zmiennej guess. Jeśli litera zapisana w zmiennej quess znajduje się także w zmiennej rletters (czyli na liście zawierającej wszystkie litery słowa, które gracz ma jeszcze odgadnąć), oznacza to, że gracz odgadł daną literę. W takim przypadku musimy zaktualizować listę board, której później użyjemy do wyświetlenia liter pozostających do odgadnięcia. Gdyby gracz odgadł literę c, musielibyśmy zmienić zawartość listy board na ["c", "_", "_"].

a9c8a50d59776690f264c310a5514dcc a

118

Rozdział 10



Łączenie wszystkiego w całość

W tym celu wywołujemy metodę index listy rletters, aby pobrać indeks pierwszego wystąpienia litery wpisanej przez gracza. Następnie używamy tego indeksu, by w zmiennej board zastąpić podkreślenie odgadniętą literą. Takie rozwiązanie zawiera jednak pewien problem. Otóż metoda index zwraca jedynie pierwsze wystąpienie litery w łańcuchu, a z tego względu nasz kod nie będzie działać, jeśli odgadnięta litera będzie występować w zmiennej word więcej niż jeden raz. Aby rozwiązać ten problem, modyfikujemy zmienną rletters, zastępując prawidłowo odgadniętą literę znakiem dolara ($), dzięki czemu podczas następnej iteracji pętli metoda index będzie mogła prawidłowo odnaleźć kolejne wystąpienie tej samej litery (jeśli takie jest), a nie zatrzyma się na pierwszym. Z drugiej strony, jeśli graczowi nie udało się odgadnąć litery, inkrementujemy wartość zmiennej wrong. Następnie, korzystając z list board oraz stages, wyświetlamy planszę gry oraz figurkę wisielca. Kod odpowiedzialny za wyświetlenie planszy do gry ma postać " ".join(board). Nieco trudniejsze jest wyświetlenie figurki wisielca. Zostanie ona wyświetlona prawidłowo, jeśli każdy z łańcuchów zapisanych na liście stages wyświetlimy w nowym wierszu. Całą figurkę można zatem wyświetlić, używając wyrażenia "\n".join(stages), które do każdego łańcucha z listy stages dodaje znak nowego wiersza, dzięki czemu każdy z nich zostanie pokazany w osobnym wierszu. Aby wyświetlić figurkę wisielca na dowolnym etapie gry, używamy wycinka — zaczynamy od indeksu 0, po czym wyświetlamy kolejne wiersze aż do bieżącego etapu (określonego przez wartość zmiennej wrong powiększoną o 1). To powiększenie o 1 jest konieczne, gdyż podczas tworzenia wycinka element wskazany przez drugi z podanych indeksów nie wchodzi w skład zwracanego wycinka. Tak utworzony wycinek będzie zawierał wyłącznie te łańcuchy, które powinny się znaleźć na wyświetlanej figurce wisielca, odpowiadającej bieżącemu stanowi gry. I w końcu sprawdzamy, czy gracz wygrał grę. Można to określić, sprawdzając liczbę znaków podkreślenia dostępnych na liście board — jeśli ich nie będzie, znaczy to, że gracz odgadł wszystkie litery słowa. Jeśli gracz wygrał, wyświetlamy komunikat Wygrałeś! i odgadnięte słowo, następnie przypisujemy zmiennej win wartość True, co kończy działanie pętli. Jeśli pętla została zakończona i gracz wygrał, nie musimy już robić nic więcej — program może się zakończyć. Gdyby jednak gracz przegrał, zmienna win będzie mieć wartość False. W takim przypadku wyświetlamy kompletną figurkę wisielca, komunikat Przegrałeś! oraz słowo, które trzeba było odgadnąć: 1 # python_lst238.py 2 3 if not win: 4 print("\n".join(stages[0: wrong])) 5 print("Przegrałeś! Miałeś odgadnąć: {}.".format(word))

Poniżej przedstawiłem kompletny kod gry w wisielca: 1 # python_lst239.py 2 3 def hangman(word): 4 wrong = 0

a9c8a50d59776690f264c310a5514dcc a

119

Wyzwania

5 stages = ["", 6 "________ ", 7 "| ", 8 "| | ", 9 "| 0 ", 10 "| /|\ ", 11 "| / \ ", 12 "| " 13 ] 14 rletters = list(word) 15 board = ["__"] * len(word) 16 win = False 17 print("Gra w wisielca") 18 while wrong < len(stages) - 1: 19 print("\n") 20 msg = "Odgadnij literę: " 21 char = input(msg) 22 if char in rletters: 23 cind = rletters.index(char) 24 board[cind] = char 25 rletters[cind] = '$' 26 else: 27 wrong += 1 28 print((" ".join(board))) 29 e = wrong + 1 30 print("\n".join(stages[0: e])) 31 if "__" not in board: 32 print("Wygrałeś!") 33 print(" ".join(board)) 34 win = True 35 break 36 if not win: 37 print("\n".join(stages[0: wrong])) 38 print("Przegrałeś! Miałeś odgadnąć: {}.".format(word)) 39 40 hangman("kot")

Wyzwania 1. Zmodyfikuj grę w taki sposób, by słowo było losowo wybierane z listy. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

120

Rozdział 10

a9c8a50d59776690f264c310a5514dcc



Łączenie wszystkiego w całość

a

Rozdział

11 Praktyka

„Praktyka nie czyni doskonałym. Praktyka rozwija mózg, a to czyni doskonałym”. — Daniel Coyle

Jeśli to Twoja pierwsza książka o programowaniu, radziłbym, żebyś teraz, przed rozpoczęciem dalszej lektury, poświęcił nieco czasu na ćwiczenia praktyczne. Niżej podałem kilka stron wartych odwiedzenia i przeczytania wraz z radami, gdzie szukać informacji w razie problemów.

Do przeczytania 1. https://softwareengineering.stackexchange.com/questions/44177/what-is-the-single-mosteffective-thing-you-did-to-improve-your-programming-skil1.

Inne zasoby Listę godnych uwagi zasobów dotyczących programowania w Pythonie przygotowałem i opublikowałem na stronie https://www.theselftaughtprogrammer.io/resources2. 1

Materiały w języku ang. — przyp. red.

2

Ibid. 121

a9c8a50d59776690f264c310a5514dcc a

122

Rozdział 11



Praktyka

Poszukiwanie pomocy Na wypadek gdybyś utknął, mam kilka rad. Przede wszystkim zadaj pytanie na grupie czytelników tej książki na stronie http://forum.theselftaughtprogrammer.io/. To grupa przyjaznych programistów (oraz osób aspirujących do zostania programistami), którzy mogą pomóc w znalezieniu odpowiedzi na każde pytanie. Warto także sprawdzić witrynę https://stackoverflow.com/, na której można zadawać pytania dotyczące programowania i otrzymywać porady od społeczności. Nauczenie się korzystania z pomocy innych osób było dla mnie bardzo cenną lekcją. Podejmowanie wysiłków, by coś zrozumieć, jest jednym z głównych aspektów procesu nauki, jednak z drugiej strony jest ono całkowicie nieproduktywne. W przeszłości, kiedy pracowałem nad projektami, starałem się wszystko zrozumieć, wykraczając poza granice produktywności. Jednak obecnie szukam odpowiedzi w internecie, a jeśli nie mogę jej szybko znaleźć, zadaję pytanie. Za każdym razem kiedy zadałem jakieś pytanie, ktoś na nie odpowiedział. Pod tym względem społeczność programistów jest niezwykle przyjacielska i pomocna.

a9c8a50d59776690f264c310a5514dcc a

Część

II Wprowadzenie do programowania obiektowego

a9c8a50d59776690f264c310a5514dcc a

a9c8a50d59776690f264c310a5514dcc a

Rozdział

12 Paradygmaty programowania

„Istnieją tylko dwa rodzaje języków: te, na które ludzie się uskarżają, oraz te, których nikt nie używa”. — Bjarne Stroustrup

Paradygmat programowania to styl programowania. Istnieje wiele paradygmatów. Aby profesjonalnie zajmować się programowaniem, należy poznać bądź to paradygmat programowania funkcyjnego, bądź paradygmat programowania obiektowego. W tym rozdziale przedstawione zostaną paradygmaty programowania proceduralnego, funkcyjnego oraz obiektowego, przy czym skoncentrujemy się głównie na tym ostatnim.

Stan Jedną z podstawowych różnic pomiędzy paradygmatami programowania jest sposób obsługi stanu. Stan to wartości zmiennych używanych w programie podczas jego działania. Stan globalny to wartości zmiennych globalnych przyjmowane podczas wykonywania programu.

125

a9c8a50d59776690f264c310a5514dcc a

126

Rozdział 12



Paradygmaty programowania

Programowanie proceduralne W pierwszej części książki pisaliśmy programy, korzystając z paradygmatu programowania proceduralnego; to styl programowania, w którym program składa się z sekwencji kroków wykonywanych w celu uzyskania rozwiązania problemu, przy czym każdy z tych kroków zmienia stan programu. Kiedy stosujemy programowanie proceduralne, piszemy kod, który „najpierw robi to, a następnie coś innego”: 1 2 3 4 5 6 7

# python_lst240.py x = y = z = xyz xyz

2 4 8 = x + y + z

>> 14

Każdy wiersz kodu w tym przykładzie zmienia stan programu. Najpierw definiujemy zmienną x, potem y, a potem z. W końcu definiujemy zmienną xyz. W przypadku programowania proceduralnego dane są przechowywane w zmiennych globalnych, a operacje na nich wykonywane przy użyciu funkcji: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

# python_lst241.py rock = [] country = [] def collect_songs(): song = "Wpisz tytuł piosenki." ask = "Naciśnij r lub c albo q, by wyjść." while True: genre = input(ask) if genre == "q": break if genre == "r": rk = input(song) rock.append(rk) elif genre ==("c"): cy = input(song) country.append(cy) else: print("Nieprawidłowe polecenie.") print(rock) print(country) collect_songs()

>> Naciśnij r lub c albo q, by wyjść.

a9c8a50d59776690f264c310a5514dcc a

127

Paradygmat programowania funkcyjnego

Programowanie proceduralne można stosować podczas pisania niewielkich programów, takich jak prezentowane w tej książce, jednak w czasie pisania dużych programów zaczynają pojawiać się problemy ze względu na to, że stan programu jest przechowywany w zmiennych globalnych,. Problem ze stosowaniem zmiennych globalnych polega na tym, że mogą one powodować występowanie nieoczekiwanych błędów. Wraz z powiększaniem się programu zaczynamy używać zmiennych globalnych w coraz to większej liczbie funkcji w całym kodzie programu i niemal niemożliwe staje się śledzenie wszystkich miejsc, w których zmienne te mogą być modyfikowane. Przykładowo wartość zmiennej globalnej może być modyfikowana w jednej funkcji, a następnie, dalej w kodzie programu ta sama zmienna może być modyfikowana przez inną funkcję, gdyż programista zapomniał, że została ona zmieniona już wcześniej. Takie sytuacje występują bardzo często i mogą prowadzić do uszkodzenia danych programu. Wraz ze wzrostem złożoności programu rośnie także liczba używanych w nim danych globalnych. Jeśli połączymy to ze zwiększaniem się ilości funkcji, niezbędnych do zapewniania nowych możliwości programu, z których wszystkie modyfikują jakieś zmienne globalne, to okaże się, że kod programu bardzo szybko stanie się niezwykle trudny do zarządzania i modyfikacji. Co więcej, ten styl programowania bazuje na efektach ubocznych. Efekt uboczny to zmiana stanu zmiennej globalnej. W programowaniu proceduralnym efekty uboczne, takie jak niezamierzona dwukrotna inkrementacja jakiejś zmiennej, mogą występować całkiem często. Wszystkie problemy związane z programowaniem proceduralnym doprowadziły do opracowania paradygmatów programowania obiektowego oraz funkcyjnego, a każdy z nich stara się rozwiązać te problemy w inny sposób.

Paradygmat programowania funkcyjnego Programowanie funkcyjne wywodzi się od rachunku lambda: najmniejszego, uniwersalnego języka programowania na świecie (opracowanego przez matematyka Alonzo Churcha). Programowanie funkcyjne rozwiązuje problemy występujące w programowaniu proceduralnym poprzez wyeliminowanie stanu globalnego. Programowanie funkcyjne bazuje na wykorzystaniu funkcji, które bądź to w ogóle nie używają, bądź też nie modyfikują stanu globalnego — jedynym stanem, z którego korzystają, jest stan przekazany do nich w formie parametrów. Wynik zwracany przez funkcję jest zazwyczaj przekazywany do innej funkcji. Dzięki temu programiści korzystający z paradygmatu programowania funkcyjnego mogą unikać stosowania stanu globalnego poprzez przekazywanie go z jednej funkcji do drugiej. Wyeliminowanie stosowania stanu globalnego wyklucza występowanie efektów ubocznych oraz wszystkich związanych z nimi problemów. Programowanie funkcyjne cechuje się swoistym żargonem, lecz Mary Rose Cook odrzuca go całkowicie w swojej krótkiej definicji: „Kod funkcyjny charakteryzuje się jedną podstawową cechą: brakiem efektów ubocznych. W żadnym stopniu nie jest on zależy od jakichkolwiek danych spoza

a9c8a50d59776690f264c310a5514dcc a

128

Rozdział 12



Paradygmaty programowania

aktualnie wykonywanej funkcji, jak również nie modyfikuje żadnych danych spoza niej”1. Definicja ta jest ilustrowana przykładem funkcji mającej efekty uboczne: 1 # python_lst242.py 2 3 a = 0 4 5 def increment(): 6 global a 7 a += 1

A to funkcja, w której efekty uboczne nie będą występować: 1 # python_lst243.py 2 3 def increment(a): 4 return a + 1

W pierwszej z tych funkcji efekty uboczne występują, gdyż bazuje ona na danych spoza niej samej, jak również zmienia dane spoza swojego kodu — konkretnie rzecz biorąc, inkrementuje wartość zmiennej globalnej. Natomiast w przypadku drugiej funkcji efekty uboczne nie występują, gdyż ani nie używa, ani nie modyfikuje danych spoza swojego kodu. Ogromną zaletą programowania funkcyjnego jest to, że umożliwia ono wyeliminowanie całej kategorii błędów powodowanych przez stan globalny (w przypadku programowania funkcyjnego stan globalny nie istnieje). Jednak z drugiej strony, programowanie funkcyjne ma także swoją wadę: otóż okazuje się, że niektóre problemy łatwiej można sobie wyobrazić, posługując się stanem. Łatwiej na przykład można sobie wyobrazić projektowanie interfejsu użytkownika posiadającego stan globalny niż interfejsu, który będzie takiego stanu pozbawiony. Gdybyśmy mieli sobie wyobrazić program z przyciskiem, który na przemian wyświetla i ukrywa obrazek, znacznie łatwiej będzie nam wyobrazić sobie utworzenie takiego przycisku, gdy napiszemy program mający stan globalny. W takim przypadku moglibyśmy utworzyć zmienną globalną przyjmującą wartości True lub False, która — w zależności od swojej bieżącej wartości — będzie wyświetlać lub ukrywać obrazek. Wyobrażenie sobie przycisku bez takiego stanu globalnego jest znacznie trudniejsze.

Paradygmat programowania obiektowego Także paradygmat programowania obiektowego eliminuje problemy występujące w programowaniu proceduralnym poprzez wyeliminowanie stanu globalnego, jednak zamiast przenoszenia tego stanu do funkcji zapisuje go w obiektach. W programowaniu obiektowym stosowane jest pojęcie klas, które definiują zestaw obiektów, a te z kolei mogą prowadzić ze sobą interakcje. Klasy są mechanizmem pozwalającym programistom na klasyfikację i grupowanie obiektów o podobnych cechach. Wyobraźmy sobie torbę pomarańczy. Każda z tych pomarańczy jest obiektem. Wszystkie pomarańcze mają te same atrybuty, takie jak kolor oraz waga, jednak wartości tych atrybutów mogą się zmieniać w zależności od konkretnego owocu. Możemy użyć klasy do opracowania 1

https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming

a9c8a50d59776690f264c310a5514dcc a

129

Paradygmat programowania obiektowego

ogólnego modelu pomarańczy, a następnie utworzyć obiekty tej klasy różniące się od siebie konkretnymi wartościami atrybutów. Możemy na przykład zdefiniować klasę, a następnie użyć jej do utworzenia obiektu pomarańczy limety o wadze 280 g, jak również obiektu pomarańczy olbrzymiej o wadzie 340 g. Każdy obiekt jest instancją klasy. Jeśli zdefiniujemy klasę Orange i utworzymy dwa obiekty Orange, każdy z nich będzie instancją klasy Orange — oba będą miały ten sam typ, czyli Orange. Terminów obiekt oraz instancja można używać zamiennie. Po zdefiniowaniu klasy wszystkie jej instancje będą identyczne. Wszystkie będą mieć te same atrybuty zdefiniowane w klasie, na przykład dla klasy reprezentującej pomarańcze mogą to być kolor i waga — jednak w każdym z obiektów wartości tych atrybutów mogą być inne. W języku Python klasa jest instrukcją złożoną, składającą się z nagłówka oraz zestawów. Do definiowania klas jest używana składnia class [nazwa]: [zestawy]; gdzie [nazwa] to nazwa klasy, a [zestawy] to definiowane przez nas zestawy klasy. Konwencja stosowana w języku Python nakazuje, by nazwy wszystkich klas zaczynały się od wielkiej litery, a w przypadku nazw składających się z kilku słów należy stosować notację CamelCase, czyli pierwsze litery poszczególnych słów trzeba zapisywać wielką literą (tak jak OtoPrzykład), a nie rozdzielać znakiem podkreślenia (jak nakazuje konwencja określająca postać nazw funkcji). Zestaw wchodzący w skład definicji klasy może być zwyczajną instrukcją bądź też instrukcją złożoną nazywaną metodą. Metody przypominają funkcje, jednak są definiowane wewnątrz klas i można je wywoływać wyłącznie na rzecz obiektów utworzonych przy użyciu danej klasy (jak robiliśmy wcześniej w tej książce, na przykład wywołując "Witaj".upper() na rzecz obiektu łańcucha znaków). Nazwy metod, podobnie jak nazwy funkcji, należy zapisywać wyłącznie małymi literami, a poszczególne słowa oddzielać od siebie znakami podkreślenia. Metody są definiowane przy wykorzystaniu dokładnie takiej samej składni, jakiej używa się do tworzenia funkcji, choć występują pomiędzy nimi dwie różnice. Po pierwsze, metody mogą być definiowane wyłącznie jako zestawy w klasie, a po drugie, każda metoda musi mieć przynajmniej jeden parametr (choć i od tej reguły istnieją wyjątki). Zwyczajowo, pierwszy parametr metody zawsze ma nazwę self. To definiowanie przynajmniej jednego parametru w metodach wynika z faktu, że podczas ich wywoływania na rzecz jakiegoś obiektu Python automatycznie przekazuje ten obiekt do metody jako jej parametr: 1 # python_lst244.py 2 3 class Orange: 4 def __init__(self): 5 print("Utworzono!")

Parametru self można użyć do definiowania zmiennych instancyjnych, czyli zmiennych należących do obiektu. Jeśli utworzymy więcej obiektów, każdy z nich może mieć inne wartości zmiennych instancyjnych. Zmienne instancyjne można tworzyć, używając składni self.[nazwa_zmiennej] = [wartość_zmiennej]. Zmienne instancyjne zazwyczaj definiuje się wewnątrz specjalnej metody o nazwie __init__ (co stanowi skrót od słowa „inicjalizacja”), którą Python wywołuje podczas tworzenia obiektu.

a9c8a50d59776690f264c310a5514dcc a

130

Rozdział 12



Paradygmaty programowania

Poniżej przedstawiony został przykład klasy reprezentującej pomarańczę: 1 # python_lst245.py 2 3 class Orange: 4 def __init__(self, w, c): 5 self.weight = w 6 self.color = c 7 print("Utworzono!")

Kod metody __init__ jest wykonywany podczas opracowania obiektu Orange (co nie następuje w tym przykładzie) i tworzy dwie zmienne instancyjne, takie jak weight oraz color. Tych zmiennych można używać jak zwyczajnych zmiennych we wszystkich metodach danej klasy. Podczas tworzenia obiektu Orange kod umieszczony w metodzie __init__ wyświetla jeszcze komunikat Utworzono!. Wszystkie metody, których nazwy są umieszczone pomiędzy podwójnym znakami podkreślenia, tak jak __init__, są nazywane metodami magicznymi; są to metody, których Python używa w specjalnych celach, takich jak tworzenie obiektów. Obiekt Orange można utworzyć, posługując się tym samym zapisem, który jest używany do wywoływania funkcji — [nazwa_klasy]([parametry]), przy czym wyrażenie [nazwa_klasy] należy zastąpić nazwą klasy, której obiekt chcemy utworzyć, a [parametry] — parametrami wymaganymi przez metodę __init__. Do tej metody nie trzeba przekazywać parametru self — Python zrobi to automatycznie. Tworzenie obiektu nazywamy także tworzeniem instancji klasy: 1 2 3 4 5 6 7 8 9 10

# python_lst246.py class Orange: def __init__(self, w, c): self.weight = w self.color = c print("Utworzono!") or1 = Orange(280, "ciemnopomarańczowy") print(or1)

>> Utworzono! >>

W tym przykładzie poniżej definicji klasy tworzymy instancję klasy Orange, używając wywołania w postaci Orange(410, "pomarańcza limeta"). W efekcie utworzenia instancji i wywołania metody __init__ zostaje wyświetlony komunikat Utworzono!. Następnie wyświetlamy sam utworzony obiekt — spowoduje to, że Python wyświetli informację, iż jest to obiekt Orange, oraz jego lokalizację w pamięci (jak można się spodziewać, przedstawiona w książce będzie inna od tej, którą będziesz mógł zobaczyć na ekranie swojego komputera). Po utworzeniu obiektu możemy pobrać wartości jego zmiennych instancyjnych; do tego celu służy składnia w postaci [nazwa_obiektu].[nazwa_zmiennej]: 1 # python_lst247.py 2 3 class Orange: 4 def __init__(self, w, c): 5 self.weight = w

a9c8a50d59776690f264c310a5514dcc a

131

Paradygmat programowania obiektowego

6 self.color = c 7 print("Utworzono!") 8 9 or1 = Orange(280, "ciemnopomarańczowy") 10 print(or1.weight) 11 print(or1.color)

>> Utworzono! >> 280 >> ciemnopomarańczowy

Wartość zmiennej instancyjnej można zmienić, posługując się zapisem [nazwa_obiektu].[nazwa_zmiennej] = [nowa_wartość]: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

# python_lst248.py class Orange: def __init__(self, w, c): self.weight = w self.color = c print("Utworzono!") or1 = Orange(280, "ciemnopomarańczowy") or1.weight = 650 or1.color = "jasnopomarańczowy" print(or1.weight) print(or1.color)

>> Utworzono! >> 650 >> jasnopomarańczowy

Choć zmienne instancyjne color i weight miały początkowo wartości "ciemnopomarańczowy" i 280, to jednak zmieniliśmy je w programie na "jasnopomarańczowy" i 390. Klasy Orange można użyć do utworzenia wielu pomarańczy: 1 2 3 4 5 6 7 8 9 10 11

# python_lst249.py class Orange: def __init__(self, w, c): self.weight = w self.color = c print("Utworzono!") or1 = Orange(120, "jasnopomarańczowy") or2 = Orange(240, "ciemnopomarańczowy") or3 = Orange(390, "żółty")

>> Utworzono! >> Utworzono! >> Utworzono!

Jednak pomarańcza to nie tylko jej cechy fizyczne, takie jak kolor i waga. Pomarańcze wykonują także pewne czynności, które można zamodelować przy użyciu metod; przykładem takiej

a9c8a50d59776690f264c310a5514dcc a

132

Rozdział 12



Paradygmaty programowania

czynności może być psucie się. Poniższy przykład pokazuje, w jaki sposób możemy zapewnić pomarańczy możliwość zepsucia się: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

# python_lst250.py class Orange(): def __init__(self, w, c): """waga jest podawana w gramach""" self.weight = w self.color = c self.mold = 0 print("Utworzono!") def rot(self, days, temp): self.mold = days * temp orange = Orange(170, "pomarańczowy") print(orange.mold) orange.rot(10, 29) print(orange.mold)

>> Utworzono! >> 0 >> 290

Metoda rot pobiera dwa parametry: liczbę dni od momentu zerwania owocu oraz średnią temperaturę w ciągu dnia. Po wywołaniu metoda ta używa określonego wzoru matematycznego, by zmienić wartość zmiennej instancyjnej mold, co jest możliwe, gdyż w dowolnej metodzie klasy można zmienić wartość dowolnej zmiennej instancyjnej. Teraz już pomarańcza może się psuć. W klasie można definiować dowolnie wiele metod. Poniżej przedstawiony został przykład klasy reprezentującej prostokąt wraz z metodą obliczającą jego powierzchnię oraz drugą — do zmiany jego wielkości: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# python_lst251.py class Rectangle(): def __init__(self, w, l): self.width = w self.len = l def area(self): return self.width * self.len def change_size(self, w, l): self.width = w self.len = l rectangle = Rectangle(10, 20) print(rectangle.area()) rectangle.change_size(20, 40) print(rectangle.area())

>> 200 >> 800

a9c8a50d59776690f264c310a5514dcc a

133

Słownictwo

Zastosowany w tym przykładzie obiekt Rectangle ma dwie zmienne instancyjne — len i width. Metoda area zwraca pole danego prostokąta, mnożąc jego długość przez szerokość; z kolei metoda change_size zmienia wartości tych zmiennych instancyjnych, przypisując im nowe wartości przekazane jako parametry. Programowanie obiektowe ma kilka zalet. Wspomaga i zachęca do wielokrotnego stosowania kodu, a co za tym idzie, ogranicza czas poświęcany na pisanie i pielęgnację kodu. Dodatkowo ułatwia dzielenie dużych problemów na mniejsze, co także ułatwia późniejszą pielęgnację kodu. Z kolei wadą programowania obiektowego jest to, że pisanie programów wymaga dodatkowego wysiłku, który zazwyczaj należy włożyć w etap projektowania programów pisanych obiektowo.

Słownictwo Efekt uboczny. Zmiana stanu zmiennej globalnej. Instancja. Każdy obiekt jest instancją klasy. Wszystkie instancje danej klasy są tego samego typu. Klasy. Mechanizm pozwalający programistom na klasyfikowanie i grupowanie podobnych obiektów. Metody magiczne. Metody, których Python używa w różnych sytuacjach, takich jak inicjalizacja obiektu. Metody. Metody są zestawami w klasach. Przypominają nieco funkcje, jednak definiujemy je wewnątrz klas i możemy je wywoływać wyłącznie na rzecz obiektów danej klasy. Paradygmat programowania. Styl programowania. Programowanie funkcyjne. Programowanie funkcyjne rozwiązuje problemy występujące w programowaniu proceduralnym, gdyż eliminuje stan globalny — w programowaniu funkcyjnym stan jest przekazywany z funkcji do funkcji. Programowanie obiektowe. Paradygmat programowania polegający na tworzeniu obiektów, które prowadzą ze sobą interakcje. Programowanie proceduralne. Styl programowania, w którym programista pisze sekwencję czynności wykonywanych w celu dotarcia do rozwiązania — każda z tych czynności zmienia stan programu. Stan globalny. Wartości zmiennych globalnych programu podczas jego działania. Stan. Wartości zmiennych programu w trakcie jego działania. Tworzenie instancji. Proces tworzenia nowego obiektu danej klasy. Zmienne instancyjne. Zmienne należące do obiektu.

a9c8a50d59776690f264c310a5514dcc a

134

Rozdział 12



Paradygmaty programowania

Wyzwania 1. Zdefiniuj klasę Apple, dysponującą czterema zmiennymi instancyjnymi reprezentującymi cztery różne cechy jabłek. 2. Zdefiniuj klasę Circle dysponującą metodą area, która oblicza i zwraca pole koła. Następnie utwórz obiekt Circle, wywołaj jego metodę area i wyświetl uzyskany wynik. Skorzystaj przy tym z funkcji pi dostępnej we wbudowanym module math. 3. Zdefiniuj klasę Triangle dysponującą metodą area, która oblicza i zwraca pole trójkąta. Następnie utwórz obiekt Triangle, wywołaj jego metodę area i wyświetl uzyskany wynik. 4. Zdefiniuj klasę Hexagon dysponującą metodą o nazwie calculate_perimeter, która oblicza i zwraca obwód sześciokąta. Następnie utwórz obiekt tej klasy, wywołaj jego metodę calculate_perimeter i wyświetl zwrócony przez nią wynik. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

13 Cztery filary programowania obiektowego

„Dobry projekt zapewnia korzyści szybciej niż zwiększa koszty”. — Thomas C. Gale

Istnieją cztery podstawowe pojęcia związane z programowaniem obiektowym; są to hermetyzacja, abstrahowanie, polimorfizm oraz dziedziczenie. Razem stanowią one cztery filary programowania obiektowego. Aby język programowania mógł być uznany za prawdziwie obiektowy, musi obsługiwać każde z tych czterech pojęć. Takimi w pełni obiektowymi językami są na przykład Python, Java oraz Ruby. W tym rozdziale opisane zostanie każde z czterech podstawowych pojęć programowania obiektowego.

Hermetyzacja Hermetyzacja to w zasadzie określenie odnoszące się do dwóch pojęć. Pierwsze z nich jest związane z tym, że w językach programowania obiektowego obiekty grupują zmienne (stan) oraz metody (operacje zmieniające ten stan lub służące do wykonywania obliczeń korzystających z tego stanu) w jednym elemencie — obiekcie: 135

a9c8a50d59776690f264c310a5514dcc a

136

Rozdział 13



Cztery filary programowania obiektowego

1 # python_lst252.py 2 3 class Rectangle(): 4 def __init__(self, w, l): 5 self.width = w 6 self.len = l 7 8 9 def area(self): 10 return self.width * self.len

W tym przykładzie zmienne instancyjne len i width przechowują stan obiektu. Ten stan obiektu jest zgrupowany w tym samym elemencie (obiekcie), w którym jest dostępna metoda area. Metoda ta z kolei używa stanu obiektu, by wyliczyć i zwrócić pole powierzchni prostokąta. Drugim pojęciem związanym z hermetyzacją jest ukrywanie wewnętrznych danych klasy, tak by uniemożliwić klientom, czyli kodowi, w którym dana klasa jest używana, bezpośrednie korzystanie z tych danych: 1 # python_lst253.py 2 3 class Data: 4 def __init__(self): 5 self.nums = [1, 2, 3, 4, 5] 6 7 def change_data(self, index, n): 8 self.nums[index] = n

Klasa Data definiuje zmienną instancyjną o nazwie nums, zawierającą listę liczb całkowitych. Po utworzeniu obiektu klasy Data elementy listy nums można zmieniać na dwa sposoby: przy użyciu metody change_data bądź też bezpośrednio odwołać się do zmiennej instancyjnej num za pomocą obiektu Data: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# python_lst254.py class Data: def __init__(self): self.nums = [1, 2, 3, 4, 5] def change_data(self, index, n): self.nums[index] = n data_one = Data() data_one.nums[0] = 100 print(data_one.nums) data_two = Data() data_two.change_data(0, 100) print(data_two.nums)

>> [100, 2, 3, 4, 5] >> [100, 2, 3, 4, 5]

a9c8a50d59776690f264c310a5514dcc a

137

Hermetyzacja

Oba sposoby modyfikowania wartości zmiennej instancyjnej num działają prawidłowo; zastanówmy się jednak, co by się stało, gdybyśmy zmienili zmienną nums z listy na krotkę? W takim przypadku każdy kod, który próbowałby zmienić jakiś element zmiennej nums, na przykład wykonując instrukcję nums[0] = 100, przestałby działać, gdyż krotki są danymi niezmiennymi. Wiele języków programowania rozwiązuje ten problem, pozwalając definiować zmienne prywatne oraz metody prywatne, czyli zmienne instancyjne oraz metody, które są dostępne wyłącznie w kodzie danej klasy, lecz nie poza nią. Takie zmienne i metody prywatne są bardzo przydatne w sytuacjach, kiedy w klasie występują jakieś dane lub operacje używane jedynie wewnątrz niej, oraz kiedy wiemy, że implementacja klasy może w przyszłości ulec zmianie (bądź też chcemy zagwarantować sobie taką możliwość na przyszłość), a co za tym idzie, nie chcemy, by ktokolwiek korzystał ze zmiennych lub metod, które mogą ulec zmianie (potencjalnie doprowadzając do problemów w kodzie korzystającym z danej klasy). Zmienne prywatne są przykładem drugiego pojęcia, do którego odnosi się hermetyzacja — ukrywają one prywatne dane klasy, uniemożliwiając bezpośredni dostęp do nich kodowi spoza klasy. Z drugiej strony, zmienne publiczne to zmienne, do których kod spoza klasy ma dostęp. W języku Python nie ma możliwości tworzenia zmiennych prywatnych. W Pythonie wszystkie zmienne są publiczne. Natomiast problem, na który odpowiedź stanowią zmienne prywatne, jest w tym języku rozwiązywany w inny sposób — przy wykorzystaniu konwencji nazewniczych. Jeśli w Pythonie jakaś zmienna lub metoda nie ma być używana w kodzie spoza danej klasy, jej nazwę należy poprzedzić znakiem podkreślenia. Programiści używający Pythona doskonale wiedzą, że jeśli nazwa metody lub zmiennej zaczyna się od podkreślenia, nie powinni jej używać (choć na własne ryzyko mogą to robić): 1 # python_lst255.py 2 3 class PublicPrivateExample: 4 def __init__(self): 5 self.public = "bezpieczny" 6 self._unsafe = "niebezpieczny" 7 8 9 def public_method(self): 10 # klient może używać tej metody 11 pass 12 13 14 def _unsafe_method(self): 15 # ta metoda nie powinna być 16 # używana poza klasą 17 pass

Programiści, którzy w swoich programach chcą używać tej klasy, czytając jej kod, będą wiedzieć, że zmiennej self.public mogą bezpiecznie używać, jednak nie powinni korzystać ze zmiennej self._unsafe, gdyż jej nazwa zaczyna się od znaku podkreślenia; jeśli jednak chcą z niej skorzystać, zrobią to na własną odpowiedzialność. Osoba odpowiadająca za utrzymanie kodu nie ma żadnego obowiązku zostawiać w nim tej zmiennej, gdyż inni programiści korzystający z tej klasy nie powinni tej zmiennej w ogóle używać. Programiści ci wiedzą, że bezpiecznie mogą używać metody

a9c8a50d59776690f264c310a5514dcc a

138

Rozdział 13



Cztery filary programowania obiektowego

public_method oraz że nie powinni używać metody _unsafe_method, gdyż jej nazwa zaczyna się od

znaku podkreślenia.

Abstrahowanie Abstrahowanie to proces „przenoszenia lub usuwania cech charakterystycznych obiektów w celu ograniczenia ich do zestawu cech, które mają dla nich kluczowe znaczenie”1. Proces abstrahowania jest stosowany w programowaniu obiektowym podczas modelowania obiektów w formie klas, kiedy to usuwane są z nich niepotrzebne szczegóły. Załóżmy, że chcemy zaprojektować klasę reprezentującą osobę. Osoba to złożone zagadnienie: każdy człowiek ma określony kolor włosów, oczu, wzrost, wagę, pochodzenie, płeć i tak dalej. Podczas tworzenia klasy reprezentującej osobę niektóre z tych cech nie będą miały znaczenia dla rozwiązywanego problemu. Przykładem abstrahowania może być tworzenie klasy Person i pomijanie w niej niektórych atrybutów, takich jak kolor oczu lub wzrost. Obiekty Person budowane na podstawie takiej klasy są abstrakcjami osób. To reprezentacje osób okrojone wyłącznie do tych cech, które są niezbędne dla rozwiązywanego problemu.

Polimorfizm Polimorfizm to „możliwość (programowego) prezentowania tego samego interfejsu dla różnych form (typów danych)”2. Interfejs jest funkcją lub metodą. Poniżej przedstawiony został przykład udostępniania tego samego interfejsu dla różnych typów danych: 1 2 3 4 5

# python_lst256.py print("Witaj, świecie!") print(200) print(200.1)

>> Witaj, świecie! >> 200 >> 200.1

W tym przykładzie prezentujemy ten sam interfejs — funkcję print — dla trzech różnych typów danych: łańcucha znaków, liczby całkowitej oraz liczby zmiennoprzecinkowej. W celu wyświetlenia danych tych trzech typów nie musieliśmy wcale definiować i wywoływać trzech różnych funkcji (takich jak print_string do wyświetlania łańcuchów znaków, print_int do wyświetlania liczb całkowitych oraz print_float do wyświetlania liczb zmiennoprzecinkowych). Zamiast tego mogliśmy użyć tylko jednej funkcji print, aby zapewnić interfejs pozwalający na wyświetlenie wartości każdego z trzech typów. 1

http://whatis.techtarget.com/definition/abstraction

2

http://stackoverflow.com/questions/1031273/what-is-polymorphism-what-is-it-forand-how-is-it-used

a9c8a50d59776690f264c310a5514dcc a

139

Polimorfizm

Wbudowana funkcja type zwraca typ danych obiektu: 1 2 3 4 5

# brakujący_typ_lst.py type("Witaj, świecie!") type(200) type(200.1)

>> >> >>

Załóżmy, że chcemy napisać program pozwalający na tworzenie trzech rodzajów obiektów, które można narysować: trójkątów, kwadratów i okręgów. Można to zrobić całkiem prosto, definiując trzy klasy: Triangle, Square oraz Circle, z których każda będzie posiadać metodę draw. Metoda Triangle.draw będzie rysować trójkąt, Square.draw — kwadrat, a Circle.draw — okrąg. W takim razie możemy powiedzieć, że każdy obiekt dysponuje interfejsem draw, który „wie”, jak należy dany obiekt narysować. Innymi słowy, utworzyliśmy ten sam interfejs w trzech różnych typach danych. Gdyby Python nie obsługiwał polimorfizmu, potrzebowalibyśmy metody służącej do rysowania każdej figury, na przykład: draw_triangle do rysowania obiektów Triangle, draw_square do rysowania obiektów Square oraz draw_circle do rysowania obiektów Circle. Co więcej, gdybyśmy dysponowali listą obiektów tych klas i chcieli narysować każdy z nich, musielibyśmy sprawdzać każdy obiekt, aby określić jego typ, a następnie w zależności od niego wywołać odpowiednią metodę; a to sprawiłoby, że program byłby znacznie dłuższy, trudniejszy do przeanalizowania, do napisania i znacznie bardziej narażony na wystąpienie błędów. Dodatkowo utrudniłoby to ewentualne rozszerzanie programu, gdyż za każdym razem, gdybyśmy chcieli dodać do niego nową figurę, musielibyśmy znaleźć w kodzie wszystkie miejsca, w których figury są rysowane, i w każdym dodać sprawdzenie typu figury (w celu określenia, jaką metodę należy wywołać) oraz wywołanie odpowiedniej funkcji rysującej. Poniżej przedstawiony został przykład rysowania figur z wykorzystaniem polimorfizmu oraz bez niego: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

# python_lst257.py # Nie uruchamiać! # Rysowanie figur # bez polimorfizmu shapes = [tr1, sq1, cr1] for a_shape in shapes: if type(a_shape) == "Triangle": a_shape.draw_triangle() if type(a_shape) == "Square": a_shape.draw_square() if type(a_shape) == "Circle": a_shape.draw_circle() # Rysowanie figur # z wykorzystaniem polimorfizmu

a9c8a50d59776690f264c310a5514dcc a

140

Rozdział 13



Cztery filary programowania obiektowego

18 shapes = [tr1, 19 sw1, 20 cr1] 21 for a_shape in shapes: 22 a_shape.draw()

Bez wykorzystania polimorfizmu dodanie do listy shapes figury nowego typu wymagałoby zmodyfikowania pętli for w celu sprawdzenia typu a_shape i wywołania odpowiedniej metody rysującej. Jednak korzystając z jednolitego, polimorficznego interfejsu można dodawać do listy shapes dowolnie wiele obiektów różnych klas reprezentujących figury i rysować je bez konieczności dopisywania jakiegokolwiek nowego kodu.

Dziedziczenie Dziedziczenie w programowaniu jest podobne do dziedziczenia w genetyce. W klasycznym rozumieniu tego słowa dziedziczymy pewne cechy, takie jak kolor oczu, po swoich rodzicach. I analogicznie, kiedy w programie tworzymy jakąś klasę, może ona dziedziczyć zmienne i metody po innej klasie. Klasa, po której są dziedziczone zmienne i metody, jest nazywana klasą bazową, natomiast klasa dziedzicząca klasą pochodną. W tym podrozdziale zaimplementujemy klasy różnych figur, wykorzystując mechanizm dziedziczenia. Oto klasa, która reprezentuje figurę: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

# python_lst258.py class Shape(): def __init__(self, w, l): self.width = w self.len = l def print_size(self): print("""{} na {} """.format(self.width, self.len)) my_shape = Square(20,20) my_shape.print_size()

>> 20 na 20

Ta klasa pozwala na tworzenie obiektów Shape dysponujących dwiema zmiennymi instancyjnymi — width oraz len. Klasa ta definiuje także metodę print_size, która wyświetla wartości zmiennych instancyjnych width i len. Klasę pochodną, dziedziczącą po klasie bazowej można utworzyć, przekazując nazwę klasy bazowej w nawiasach umieszczonych za nazwą definiowanej klasy pochodnej. Kolejny przykład definiuje klasę Square dziedziczącą po klasie Shape: 1 # python_lst259.py 2 3 class Shape(): 4 def __init__(self, w, l):

a9c8a50d59776690f264c310a5514dcc a

141

Dziedziczenie

5 self.width = w 6 self.len = l 7 8 def print_size(self): 9 print("""{} na {} 10 """.format(self.width, 11 self.len)) 12 13 class Square(Shape): 14 pass 15 16 a_square = Square(20,20) 17 a_square.print_size()

>> 20 na 20

W definicji klasy Square przekazaliśmy nazwę klasy Shape jako parametr, a to oznacza, że klasa Square odziedziczy wszystkie zmienne i metody po klasie Shape. Jedynym zestawem zdefiniowanym w klasie Square jest słowo kluczowe pass, które informuje Python o tym, że nic nie trzeba robić. Dzięki wykorzystaniu mechanizmu dziedziczenia możemy utworzyć obiekt Square, określić jego długość i szerokość, a następnie wywołać metodę print_size bez pisania wewnątrz klasy Square żadnego kodu (z wyjątkiem słowa kluczowego pass). Ta redukcja ilości kodu jest bardzo ważna, gdyż unikanie niepotrzebnego powielania kodu pozwala zmniejszyć wielkość programów i ułatwić zarządzanie bazą ich kodu. Klasa pochodna jest zwyczajną klasą języka Python — można w niej definiować metody i zmienne bez wpływu na klasę bazową: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# python_lst260.py class Shape(): def __init__(self, w, l): self.width = w self.len = l def print_size(self): print("""{} na {} """.format(self.width, self.len)) class Square(Shape): def area(self): return self.width * self.len a_square = Square(20, 20) print(a_square.area())

>> 400

Kiedy klasa pochodna dziedziczy metodę po klasie bazowej, może ją przesłonić — w tym celu wystarczy zdefiniować w klasie pochodnej nową metodę o nazwie takiej samej jak nazwa metody dziedziczonej. Możliwość zmiany implementacji metody w klasie pochodnej nazywamy przesłanianiem metod.

a9c8a50d59776690f264c310a5514dcc a

142

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

Rozdział 13



Cztery filary programowania obiektowego

# python_lst261.py class Shape(): def __init__(self, w, l): self.width = w self.len = l def print_size(self): print("""{} na {} """.format(self.width, self.len)) class Square(Shape): def area(self): return self.width * self.len def print_size(self): print("""Moje wymiary to: {} na {} """.format(self.width, self.len)) a_square = Square(20, 20) a_square.print_size()

>> Moje wymiary to: 20 na 20

Ponieważ w tym przypadku w klasie Square zdefiniowaliśmy metodę o nazwie print_size, przesłania ona metodę klasy bazowej o tej samej nazwie, a jej wywołanie spowoduje wyświetlenie nowego komunikatu.

Kompozycja Skoro poznałeś już cztery podstawowe pojęcia programowania obiektowego, chciałbym przedstawić jeszcze jedno, także bardzo ważne, czyli kompozycję. Kompozycja odpowiada relacji „ma”, pozwalając na przechowywanie obiektów jako zmiennych w innych obiektach. Można na przykład użyć kompozycji do odwzorowania relacji posiadania pomiędzy psem i jego właścicielem (pies ma swojego pana). Aby zamodelować tę relację, najpierw zdefiniujemy klasy reprezentujące psa i człowieka: 1 # python_lst262.py 2 3 class Dog(): 4 def __init__(self, 5 name, 6 breed, 7 owner): 8 self.name = name 9 self.breed = breed 10 self.owner = owner 11 12

a9c8a50d59776690f264c310a5514dcc a

143

Słownictwo

13 class Person(): 14 def __init__(self, name): 15 self.name = name

Tworząc obiekt Dog, możemy przekazać obiekt Person jako parametr określający właściciela (owner): 1 2 3 4 5 6 7 8 9 10

# python_lst263.py # dalszy ciąg # poprzedniego przykładu mick = Person("Mick Jagger") stan = Dog("Stanley", "Bulldog", mick) print(stan.owner.name)

>> Mick Jagger

Teraz pies o imieniu "Stanley", zapisany w zmiennej stan, ma swojego właściciela — obiekt Person o nazwie "Mick Jagger" — który jest zapisany w jego zmiennej instancyjnej owner.

Słownictwo Abstrahowanie. Proces „przenoszenia lub usuwania cech charakterystycznych obiektów w celu ograniczenia ich do zestawu cech, które mają dla nich kluczowe znaczenie”3. Cztery filary programowania obiektowego. Kluczowe cztery pojęcia związane z programowaniem obiektowym to: dziedziczenie, polimorfizm, abstrahowanie oraz hermetyzacja. Dziedziczenie. W kontekście genetyki dziedziczymy po rodzicach takie cechy jak na przykład kolor oczu. W przypadku klas jedna klasa może dziedziczyć po innej jej zmienne instancyjne i metody. Hermetyzacja. Termin ten odnosi się do dwóch pojęć. Pierwsze z nich jest związane z tym, że w językach obiektowych zmienne (stan) oraz metody (służące do zmieniania tego stanu) są grupowane w jedną całość — nazywaną właśnie obiektem. Drugim pojęciem związanym z hermetyzacją jest ukrywanie wewnętrznych danych klasy, tak by nie można ich było używać poza jej kodem. Klasa bazowa. Klasa, po której dziedziczy inna klasa. Klasa pochodna. Klasa dziedzicząca. Klient. Kod spoza klasy, który używa jej instancji. Kompozycja. Odwzorowanie relacji „ma” poprzez zapisywanie jednego obiektu w zmiennej instancyjnej drugiego.

3

http://whatis.techtarget.com/definition/abstraction

a9c8a50d59776690f264c310a5514dcc a

144

Rozdział 13



Cztery filary programowania obiektowego

Polimorfizm. „Możliwość (programowego) prezentowania tego samego interfejsu dla różnych form (typów danych)”4. Przesłanianie metod. Możliwość zmiany w klasie pochodnej implementacji metody odziedziczonej po klasie bazowej.

Wyzwania 1. Zdefiniuj klasy Rectangle i Square dysponujące metodą calculate_perimeter obliczającą obwód danej figury. Utwórz obiekty Rectangle i Square, a następnie wywołaj ich metodę calculate_perimeter. 2. W klasie Square zdefiniuj metodę change_size, która pozwoli zwiększać lub zmniejszać (w razie przekazania wartości ujemnej) długość krawędzi kwadratu o podaną wartość. 3. Utwórz klasę Square. Zdefiniuj w niej metodę what_am_i, która będzie wyświetlać łańcuch znaków "Jestem figurą". Zmodyfikuj klasy Square i Rectangle z poprzednich wyzwań, tak by dziedziczyły po klasie Shape. Utwórz obiekty Square i Rectangle, a następnie wywołaj ich metodę what_am_i. 4. Zdefiniuj klasę Horse oraz Rider. Używając kompozycji, zamodeluj fakt, że koń może mieć jeźdźca. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

4

http://stackoverflow.com/questions/1031273/what-is-polymorphism-what-is-it-forand-how-is-it-used

a9c8a50d59776690f264c310a5514dcc a

Rozdział

14 Więcej o programowaniu obiektowym

„Traktuj swój kod jak poezję i skracaj do absolutnego minimum”. — Ilya Dorman

W tym rozdziale opiszę dodatkowe zagadnienia związane z programowaniem obiektowym.

Zmienne klasowe a zmienne instancyjne W języku Python klasy są obiektami. Pomysł takiego potraktowania klas pochodzi z języka Smalltalk, bardzo znaczącego języka programowania, który był jednym z pierwszych języków programowania obiektowego. W Pythonie każda klasa jest instancją klasy „typu”. 1 # python_lst264.py 2 3 class Square: 4 pass 5 6 print(Square)

>> > Kocham $

Normalnie znak dolara oznacza, że dopasowanie ma być umieszczone na końcu wiersza, jednak w tym przypadku, ponieważ znak dolara został poprzedzony odwrotnym ukośnikiem, zatem zostanie on potraktowany jak zwyczajny znak, a nie jak znak specjalny. Poniżej przedstawione zostało użycie tego samego wyrażenia regularnego w kodzie Python: 1 2 3 4 5 6 7 8 9 10 11

# python_lst286.py import re line = "Kocham $" m = re.findall("\\$", line, re.IGNORECASE) print(m)

>> ['$']

a9c8a50d59776690f264c310a5514dcc a

182

Rozdział 17



Wyrażenia regularne

Narzędzia do tworzenia wyrażeń regularnych Przygotowanie wyrażenia regularnego, które będzie prawidłowo dopasowywać określony wzorzec, może być naprawdę frustrujące. Dlatego też pracując z wyrażeniami regularnymi, warto skorzystać z narzędzi ułatwiających ich tworzenie i testowanie, takich jak dostępne na stronie http://regexr.com/. Warto też zapoznać się z doskonałym samouczkiem dotyczącym pisania wyrażeń regularnych, dostępnym na stronie https://www.tutorialspoint.com/python/python_reg_expressions.htm.

Słownictwo Jajko wielkanocne (ang. Easter egg). Wiadomość ukryta w kodzie. Niezachłanne. Niezachłanne wyrażenia regularne będą poszukiwały możliwie jak najkrótszego dopasowania. Wyrażenie regularne. „Sekwencja znaków definiująca wzorzec wyszukiwania”3. Zachłanne. Zachłanne wyrażenia regularne będą starały się dopasować możliwie jak najwięcej analizowanego tekstu.

Wyzwania 1. Napisz wyrażenie regularne, które zostanie dopasowanie do słowa Holendrem z tekstu w pliku zen.txt. 2. Napisz wyrażenie regularne, które zwróci wszystkie cyfry z łańcucha znaków Arizona 479, 501, 870. California 209, 213, 650. 3. Napisz wyrażenie regularne, które będzie odnajdywać słowa zaczynające się od dowolnej litery, po której będą umieszczone dwie litery o. Następnie użyj modułu re Pythona, by odszukać takie słowa w łańcuchu Bieg na orientację dookoła miejskiego zoo. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

3

https://pl.wikipedia.org/wiki/Wyrażenie_regularne

a9c8a50d59776690f264c310a5514dcc a

Rozdział

18 Menedżery pakietów

„Każdy programista jest autorem”. — Sercan Leylek

Menedżer pakietów to program służący do instalowania innych programów i zarządzania nimi. Menedżery te są bardzo użyteczne, gdyż często zdarza się, że inne programy są konieczne do tworzenia nowego oprogramowania. Przykładowo programiści aplikacji internetowych często używają frameworków internetowych, czyli programów ułatwiających pisanie witryn WWW. Programiści korzystają z menedżerów pakietów do instalowania takich frameworków i wielu innych programów. W tym rozdziale dowiesz się, jak używać menedżera pakietów o nazwie pip.

Pakiety Pakiet to oprogramowanie „spakowane” w celu rozpowszechniania — zawiera on pliki tworzące sam program, jak również metadane, czyli informacje o danych, takie jak nazwa oprogramowania, numer jego wersji, oraz zależności, to znaczy programy, których dany program wymaga do prawidłowego działania. Menedżera pakietu możemy użyć do pobrania pakietu i zainstalowania go jako programu na swoim komputerze. Menedżer zadba o zaspokojenie wszelkich zależności, jakich dany pakiet wymaga. 183

a9c8a50d59776690f264c310a5514dcc a

184

Rozdział 18



Menedżery pakietów

Pip W tym podrozdziale opiszę, w jaki sposób można pobierać pakiety przy użyciu programu pip — menedżera pakietów dla Pythona. Pakiet po pobraniu można importować jako moduł we własnych programach pisanych w Pythonie. Najpierw musimy się upewnić, że pip jest zainstalowany na komputerze. W tym celu należy otworzyć powłokę Bash lub wiersz poleceń (w systemie Windows) i wpisać polecenie pip: # bash_lst036.sh $ pip

>> Usage: pip [options] Commands: install Install packages. download Download packages. ...

Po wpisaniu tego polecenia na ekranie powinna zostać wyświetlona lista opcji. Menedżer pakietów pip jest obecnie dołączany do środowiska języka Python, więc powinien już być zainstalowany, jednak wcześniej trzeba było pobierać go osobno. Jeśli po wpisaniu tego polecenia nic się nie stanie, będzie to oznaczać, że program pip nie jest dostępny na komputerze. Informacje, jak go zainstalować, można znaleźć na stronie https://pip.pypa.io/en/stable/installing/. Nowy pakiet można zainstalować, wykonując polecenie pip install [nazwa_pakietu]. Pip instaluje nowe pakiety do podkatalogu site-packages w katalogu Pythona. Lista wszystkich pakietów, które można w ten sposób zainstalować, jest dostępna na stronie https://pypi.python.org/pypi. Pakiety, które chcemy zainstalować, można określać na dwa sposoby — podając nazwę pakietu bądź też nazwę pakietu, a za nią dwa znaki równości (==) oraz numer wersji pakietu, która ma zostać pobrana. Jeśli podamy jedynie nazwę pakietu, pip pobierze jego najnowszą wersję. Drugi sposób pozwala na pobranie konkretnej wersji pakietu, a nie jedynie najnowszej. Poniżej pokazałem, jak można zainstalować pakiet Flask służący do tworzenia witryn WWW w systemach Ubuntu i Unix: # bash_lst037.sh $ sudo pip install

Flask==0.10.1

>> Password: >> Successfully installed flask-0.11.1

W systemie Windows trzeba uruchomić wiersz poleceń w trybie administratora. W tym celu należy kliknąć jego ikonę prawym przyciskiem myszy i z wyświetlonego menu podręcznego wybrać opcję Uruchom jako administrator. W oknie wiersza poleceń należy wpisać: # bash_lst038.sh $ pip install Flask==0.10.1

>> Successfully installed flask-0.11.1

a9c8a50d59776690f264c310a5514dcc a

185

Pip

Wykonanie tego polecenia sprawi, że pip zainstaluje pakiet Flask na komputerze, w katalogu site-packages. Teraz można już będzie zaimportować moduł Flask w programie. Utwórz zatem nowy plik Pythona i umieść w nim następujący kod: 1 2 3 4 5 6 7 8 9 10 11

>>

# python_lst287.py from flask import Flask app = Flask(__name__) @app.route('/') def index(): return "Witaj, świecie!" app.run(port='8000')

*

Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)

Teraz można uruchomić przeglądarkę i wyświetlić w niej stronę o adresie http://127.0.0.1:8000/, a zostanie wyświetlona prosta strona z tekstem Witaj, świecie!, taka jak przedstawiona na rysunku 18.1.

Rysunek 18.1. Prosta strona internetowa utworzona przy użyciu modułu Flask

Moduł Flask pozwala na łatwe i szybkie tworzenie serwera WWW oraz stron WWW. Więcej informacji na temat tego, jak działa powyższy przykład, można znaleźć na stronie http://flask.pocoo.org/docs/0.11/tutorial/. Listę zainstalowanych pakietów można wyświetlić, używając polecenia pip freez: # bash_lst039.sh pip freeze

>> Flask==0.11.11

I w końcu, można usunąć instalację pakietu, używając polecenia pip uninstall [nazwa_pakietu]. Usuń teraz instalację pakietu Flask, wykonując następujące polecenie: # bash_lst040.sh pip uninstall Flask.

>> Proceed (y/n)? y ...

a9c8a50d59776690f264c310a5514dcc a

186

Rozdział 18



Menedżery pakietów

W ten sposób moduł Flask zostanie usunięty, o czym możesz się przekonać, wykonując polecenie pip freeze.

Środowiska wirtualne Wcześniej czy później i tak zechcesz instalować pakiety Pythona nie w katalogu site-packages, lecz w środowiskach wirtualnych. Środowiska wirtualne pozwalają na rozdzielanie pakietów instalowanych dla różnych projektów. Więcej informacji na ich temat można znaleźć na stronie http://docs.python-guide.org/en/latest/dev/virtualenvs/.

Słownictwo $PYTHONPATH. Python szuka modułów w katalogach, których nazwy zostały zapisane w zmiennej środowiskowej $PYTHONPATH. Apt-get. Menedżer pakietów używany w systemie Ubuntu. Framework internetowy. Program pomagający w budowaniu witryn WWW. Menedżer pakietów. Program do instalowania innych programów i zarządzania nimi. Metadane. Informacje o danych. Pakiet. Oprogramowanie „spakowane” do dystrybucji. Pip. Menedżer pakietów używany w języku Python. PyPI. Witryna WWW, na której są udostępniane pakiety dla języka Python. Site-packages. Katalog wymieniony w zmiennej środowiskowej $PYTHONPATH. Menedżer pip instaluje w nim pakiety. Środowisko wirtualne. Środowiska wirtualne są używane do rozdzielania i przechowywania pakietów Pythona używanych w różnych projektach. Zależności. Inne programy, od których zależy poprawne działanie danego programu.

Wyzwania 1. Odszukaj jakiś pakiet na witrynie PyPI (https://pypi.python.org/pypi) i pobierz go, używając menedżera pakietów pip. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

19 Kontrola wersji

„Nie zgadzam się na robienie rzeczy, które mogą zrobić komputery”. — Olin Shivers

Pisanie oprogramowania to gra zespołowa. Pracując nad projektem z jakąś inną osobą (lub z całym zespołem), musimy mieć możliwość wprowadzania zmian w bazie kodu — katalogach i plikach tworzonego programowania — i odpowiedniego ich synchronizowania. Oczywiście, można by od czasu do czasu wysyłać zmodyfikowane pliki pocztą elektroniczną i samodzielnie łączyć ich różne wersje, jednak byłoby to bardzo męczące. A co by się stało, gdyby dwie osoby jednocześnie wprowadzały zmiany w tej samej części projektu? W jaki sposób można by zdecydować, które zmiany należy ostatecznie zastosować? Wszystkie problemy tego typu są rozwiązywane przez systemy kontroli wersji. Systemy kontroli wersji to programy zaprojektowane w celu ułatwienia wspólnej pracy nad projektami grupom programistów. Dwoma popularnymi systemami kontroli wersji są Git oraz SVN. Zazwyczaj taki system kontroli wersji jest używany wraz z internetowym serwisem przechowującym kody projektu w chmurze. W tym rozdziale nauczysz się korzystać z systemu Git oraz serwisu GitHub — witryny WWW umożliwiającej przechowywanie kodów w chmurze.

187

a9c8a50d59776690f264c310a5514dcc a

188

Rozdział 19



Kontrola wersji

Repozytoria Repozytorium to struktura danych tworzona przez system kontroli wersji, taki jak Git, która przechowuje informacje o wszelkich danych wprowadzanych w bazie kodu projektu. Struktura danych to sposób organizowania i przechowywania informacji; strukturami danych są na przykład listy i słowniki (które zostaną dokładniej przedstawione w czwartej części książki). Kiedy przyjrzymy się repozytorium, wygląda ono jak katalog z plikami. Będziemy używać programu Git do interakcji ze strukturą danych zawierającą wszelkie informacje o zmianach wprowadzanych w kodzie projektu. Podczas pracy nad projektem zarządzanym przy użyciu systemu kontroli wersji Git takich repozytoriów będzie wiele (zazwyczaj po jednym na każdą osobę pracującą nad projektem). Niemal zawsze każda osoba pracująca nad danym projektem dysponuje takim repozytorium na swoim komputerze; jest ono nazywane repozytorium lokalnym. Repozytorium lokalne jest używane do gromadzenia wszelkich modyfikacji wprowadzanych w projekcie. Istnieje także repozytorium centralne, przechowywane w internecie, na przykład w serwisie GitHub, którego wszystkie repozytoria lokalne używają do synchronizacji kodu projektu (gdyż każde z nich jest całkowicie niezależne od pozostałych). Programiści pracujący nad projektem mogą aktualizować repozytorium centralne, przesyłając do niego zmiany zapisane w ich repozytoriach lokalnych, a oprócz tego mogą modyfikować swoje repozytoria lokalne, pobierając najnowsze zmiany wprowadzone przez innych programistów i zapisane w repozytorium centralnym. Jeśli będziesz pracować nad projektem z jakimś innym, jednym programistą, wasza konfiguracja repozytoriów będzie wyglądała tak, jak na rysunku 19.1.

Rysunek 19.1. Konfiguracja repozytoriów dla dwóch programistów

Nowe repozytorium centralne można utworzyć w serwisie GitHub (lub z poziomu wiersza poleceń). Po utworzeniu repozytorium centralnego można natomiast użyć programu Git do zbudowania repozytorium lokalnego, które będzie komunikować się z danym repozytorium centralnym.

a9c8a50d59776690f264c310a5514dcc a

189

Rozpoczynanie projektu

Rozpoczynanie projektu Jeśli zmieni się interfejs użytkownika serwisu GitHub, instrukcje zamieszczone w tym rozdziale też będą musiały się zmienić. W takim przypadku podam wszelkie niezbędne, aktualne informacje na swojej witrynie, na stronie https://theselftaughtprogrammer.io/git. Aby rozpocząć korzystanie z systemu kontroli wersji, należy utworzyć konto w serwisie GitHub (https://github.com/join). Aby utworzyć nowe repozytorium, zaloguj się w serwisie GitHub (oczywiście, wcześniej musisz mieć w nim konto), a następnie kliknij przycisk ze znakiem „+” umieszczony w prawym, górnym rogu ekranu i z wyświetlonej listy wybierz opcję New repository. Nadaj repozytorium nazwę wisielec. Następnie zaznacz przycisk opcji Public oraz pole wyboru Initialize the repository with a README, kiedy to zrobisz — kliknij przycisk Create repository. Kliknij przycisk umieszczony w prawym, górnym rogu strony, a następnie z wyświetlonego menu wybierz opcję Your profile (patrz rysunek 19.2).

Rysunek 19.2. Menu z opcją przejścia do profilu użytkownika

Na wyświetlonej stronie będzie widoczna nazwa utworzonego repozytorium — wisielec. Kliknij ją. Ta część witryny jest nazywana repozytorium centralnym. W głównej części strony pod menu będzie widoczne pole tekstowe z adresem URL; upewnij się, że przed adresem jest zaznaczony przycisk HTTPS, a następnie skopiuj i zapisz ten adres. Przed rozpoczęciem dalszej lektury należy zainstalować Git. Informacje na temat jego instalacji można znaleźć na stronie https://www.git-scm.com/book/en/v2/Getting-Started-Installing-Git. Programu Git po zainstalowaniu można użyć w wierszu poleceń. Wykonaj zatem polecenie git: # bash_lst041.sh $ git

>> usage: git [--version] [--help] [-C ] [-c name=value] [--exec-path[=]] [--html-path] [--man-path] [--info-path]

a9c8a50d59776690f264c310a5514dcc a

190

Rozdział 19



Kontrola wersji

Jeśli wyniki, które zostały wyświetlone w oknie konsoli, przypominają przedstawione powyżej, to znaczy, że Git został poprawnie zainstalowany. Teraz możesz użyć skopiowanego adresu URL, aby pobrać repozytorium z serwisu GitHub na swój lokalny komputer. Służy do tego polecenie git clone [adres_url_repozytorium]. Repozytorium zostanie pobrane do katalogu, w którym wykonano polecenie. Skopiuj odnośnik lub kliknij przycisk służący do kopiowania odnośnika do schowka, a następnie wykonaj poniższe polecenie git clone: # bash_lst042.sh $ git clone [url_repozytorium]

>> Cloning into 'wisielec'... warning: You appear to have cloned an empty repository.

Użyj polecenia ls, aby przekonać się, czy faktycznie repozytorium zostało pobrane z serwisu GitHub: # bash_lst043.sh $ ls

>> wisielec

Powinien zostać wyświetlony katalog o nazwie wisielec. Ten katalog jest Twoim lokalnym repozytorium.

Wypychanie i wciąganie zmian Istnieją dwie podstawowe operacje wykonywane przy użyciu systemu Git. Pierwszą z nich jest aktualizacja repozytorium centralnego — czyli przesłanie do niego zmian wprowadzonych w repozytorium lokalnym. Ta operacja jest nazywana wypychaniem (ang. push). Drugą jest aktualizowanie repozytorium lokalnego — pobieranie na nie zmian zarejestrowanych w repozytorium centralnym. Ta operacja jest z kolei nazywana wciąganiem (ang. pull). Polecenie git remote -v („v” to popularna flaga, której użycie zazwyczaj powoduje wyświetlanie dodatkowych informacji o wykonywanych operacjach i stanowi skrót od angielskiego słowa verbose — rozwlekły, przegadany) wyświetla adres URL repozytorium centralnego, na który pliki są wypychane oraz z którego są wciągane. Przejdź do katalogu wisielec i wykonaj polecenie git remote: # bash_lst044.sh $ cd wisielec $ git remote -v

>> origin >> origin

[url_repozytorium]/wisielec.git (fetch) [url_repozytorium]/wisielec.git (push)

a9c8a50d59776690f264c310a5514dcc a

191

Przykład wypychania

Pierwszy wiersz wyświetlonych wyników zawiera adres URL centralnego repozytorium, z którego dane będą wciągane; natomiast drugi to adres URL centralnego repozytorium, do którego dane będą wypychane. W przeważającej większości przypadków dane będą wciągane z tego samego repozytorium centralnego i wypychane do niego, zatem oba te adresy URL będą identyczne.

Przykład wypychania W tym podrozdziale zmodyfikujemy lokalne repozytorium, które utworzyłeś wcześniej i sklonowałeś na swój komputer, a następnie wypchniemy te zmiany do repozytorium centralnego. Do katalogu wisielec skopuj plik wisielec.py zawierający kod wykonanego wyzwania, kończącego pierwszą część książki. Oznacza to, że teraz Twoje lokalne repozytorium zawiera plik, którego nie ma w repozytorium centralnym — innymi słowy, repozytorium lokalne nie jest zsynchronizowane z repozytorium centralnym. Zmiany są wypychane z repozytorium lokalnego do centralnego w trzech etapach. Pierwszym jest dodanie zmian do poczekalni (ang. stage) — w ramach tego etapu informujemy Git o tym, które ze zmodyfikowanych plików chcemy wypchnąć do repozytorium centralnego. Polecenie git status wyświetla porównanie bieżącego stanu projektu ze stanem repozytorium, można zatem zorientować się, które pliki należy dodać do poczekalni. Polecenie git status wyświetla te pliki z katalogu lokalnego, które różnią się od plików przechowywanych w repozytorium centralnym. Kiedy plik nie jest w poczekalni, będzie wyświetlony na czerwono. Kiedy będzie w poczekalni — zostanie wyświetlony na zielono. Upewnij się, że jesteś w katalogu wisielec i wykonaj polecenie git status: # bash_lst045.sh $ git status >> git status

On branch master Initial commit Untracked files: (use "git add ..." to include in what will be committed) wisielec.py nothing added to commit but untracked files present (use "git add" to track)

Nazwa pliku wisielec.py powinna być wyświetlona na czerwono. Wybrany plik można dodać do poczekalni, używając polecenia git add [plik]: # bash_lst46.py $ git add wisielec.py

Sprawdź, czy plik został dodany do poczekalni, wykonując w tym celu polecenie git status:

a9c8a50d59776690f264c310a5514dcc a

192

Rozdział 19



Kontrola wersji

# bash_lst047.py $ git status

>> On branch master Initial commit Changes to be committed: (use "git rm --cached ..." to unstage) new file: wisielec.py

Jak widać, po dodaniu do przechowalni plik wisielec.py będzie wyświetlany na zielono. Plik można usunąć z poczekalni bez wprowadzania zmian w repozytorium centralnym, używając składni git reset [ścieżka_pliku]. Spróbuj zatem usunąć z poczekalni dodany do niej wcześniej plik wisielec.py: # bash_lst048.py $ git reset wisielec.py

Upewnij się, że faktycznie został usunięty z poczekalni, ponownie wykonując polecenie git status: >> ... Untracked files: (use "git add ..." to include in what will be committed) wisielec.py

Teraz ponownie dodaj ten plik do poczekalni: # bash_lst049.sh $ git add wisielec.py $ git status

>> On branch master Initial commit Changes to be committed: (use "git rm --cached ..." to unstage) new file: wisielec.py

Po dodaniu do przechowalni plików, które chcemy przekazać do repozytorium centralnego, będziemy gotowi do wykonania kolejnego kroku, czyli zatwierdzenia plików — co oznacza wydanie systemowi Git polecenia zarejestrowania wprowadzonych zmian w repozytorium lokalnym. Do zatwierdzania zmian służy polecenie git commit -m [komunikat]. Polecenie to tworzy tak zwaną rewizję (ang. commit) — zapisaną wersję kodów projektu. Flaga -m oznacza, że do rewizji chcemy dodać wiadomość, która ułatwi ustalenie, jakie zmiany zostały wprowadzone oraz dlaczego (wiadomość taka przypomina komentarze w kodzie). Następnym krokiem będzie zatwierdzenie zarejestrowanych zmian (tych, które mają zostać przesłane do repozytorium centralnego) w serwisie GitHub, w którym podana wiadomość także będzie widoczna:

a9c8a50d59776690f264c310a5514dcc a

193

Przykład wypychania

$ bash_lst050.sh $ git commit -m "moja pierwsza rewizja"

>> [master (root-commit) fe04e21] moja pierwsza rewizja 1 file changed, 1 insertion(+) create mode 100644 wisielec.py

Po zatwierdzeniu plików możemy już przystąpić do ostatniego etapu, czyli wypchnięcia zmian z repozytorium lokalnego do repozytorium centralnego. Do tego służy polecenie git push origin master: # bash_lst051.sh $ git push origin master

>> Counting objects: 3, done. Writing objects: 100% (3/3), 229 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://github.com/prog-samouk/wisielec.git * [new branch] master -> master

Po wpisaniu nazwy użytkownika i hasła dostępu program git wypchnie wszystkie zarejestrowane zmiany do serwisu GitHub. Jeśli teraz zajrzysz do swojego repozytorium centralnego w tym serwisie, zobaczysz w nim plik wisielec.py wraz z podaną wcześniej wiadomością (patrz rysunek 19.3).

Rysunek 19.3. Kod projektu w repozytorium centralnym w serwisie GitHub

a9c8a50d59776690f264c310a5514dcc a

194

Rozdział 19



Kontrola wersji

Przykład wciągania W tym podrozdziale zaktualizujesz swoje lokalne repozytorium, wciągając do niego zmiany z repozytorium centralnego. Operację tę trzeba będzie wykonywać za każdym razem, gdy będziesz chciał zaktualizować repozytorium lokalne, dodając do niego zmiany wprowadzone przez innych programistów w repozytorium centralnym. Przejdź do repozytorium centralnego i kliknij przycisk Create new file. Utwórz plik o nazwie nowy.py, a następnie kliknij przycisk Commit new file. Ten plik nie jest jeszcze dostępny w repozytorium lokalnym, co oznacza, że nie jest ono zsynchronizowane z repozytorium centralnym. Lokalne repozytorium można zaktualizować, wciągając do niego zmiany z repozytorium centralnego przy użyciu polecenia git pull origin master: # bash_lst052.sh $ git pull origin master

>> remote: Counting objects: 3, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From https://github.com/prog-samouk/wisielec * branch master -> FETCH_HEAD eec78f2..4ed97ea master -> origin/master Updating eec78f2..4ed97ea Fast-forward nowy.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 nowy.py

W efekcie Git wprowadzi wszystkie zmiany z repozytorium centralnego w repozytorium lokalnym. Plik nowy.py, który przed chwilą utworzyłeś w repozytorium centralnym, teraz powinien być dostępny w repozytorium lokalnym na Twoim komputerze; o czym z łatwością możesz się przekonać, wykonując polecenie ls: $ ls

>> nowy.py

wisielec.py

Przywracanie wersji Git zapisuje projekt za każdym razem, gdy są zatwierdzane pliki. System ten pozwala na przywrócenie dowolnej zarejestrowanej wcześniej wersji pliku — jest to tak zwane „przewijanie” projektu. Można na przykład przywrócić projekt do rewizji zapisanej pod koniec zeszłego tygodnia. W takim przypadku wszystkie pliki i katalogi będą takie same jak w momencie zapisywania rewizji w zeszłym

a9c8a50d59776690f264c310a5514dcc a

195

diff

tygodniu. Później można błyskawicznie przewinąć projekt do ostatniej zapisanej rewizji. Każda rewizja ma swój numer — unikalną sekwencję znaków, której Git używa do identyfikowania rewizji. Historię rewizji można wyświetlić przy użyciu polecenia git log: # bash_lst053.sh $ git log

>> commit 4ed97ea4e9a225a8f228e22f7b4b47079773aae4 Author: prog-samouk Date: Mon Jul 24 21:42:39 2017 +0200 Utworzenie pliku nowy.py commit eec78f22b2ec61018cb230c1ec00408833bb2925 Author: prog-samouk Date:

Mon Jul 24 21:20:43 2017 +0200

moja pierwsza rewizja

Powinieneś zobaczyć dwie rewizje. W ramach pierwszej rewizji dodałeś plik wisielec.py, a w ramach drugiej plik nowy.py. Każda z tych rewizji ma swój numer. Projekt można przełączyć do innej rewizji, podając jej numer w poleceniu git checkout. Poniższe polecenie pozwala przełączyć projekt do stanu, w którym został do niego dodany pierwszy plik: git checkout eec78f22b2ec61018cb230c1ec00408833bb2925.

diff Polecenie git diff pokazuje różnice pomiędzy plikami w repozytoriach lokalnym i centralnym. Utwórz zatem na swoim komputerze nowy plik witaj_swiecie.py i zapisz w nim instrukcję print("Witaj, świecie!"). Kiedy to zrobisz, dodaj go do poczekalni: # bash_lst054.sh $ git add witaj_swiecie.py

Upewnij się, że wszystko jest jak należy: # bash_lst055.sh $ git status

>> ... Changes to be committed: (use "git reset HEAD ..." to unstage) new file: witaj_swiecie.py

a9c8a50d59776690f264c310a5514dcc a

196

Rozdział 19



Kontrola wersji

Teraz zarejestruj nową rewizję: # bash_lst056.sh $ git add -m "dodaję nowy plik"

>> ... 1 file changed, 1 insertion(+) create mode 100644 witaj_swiecie.py

I wypchnij zmiany do repozytorium centralnego: # bash_lst057.sh $ git push origin master

>> ... Counting objects: 3, done. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 339 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://github.com/prog-samouk/wisielec.git 4ed97ea..07b288f master -> master

Teraz dodaj do pliku witaj_swiecie.py kolejny wiersz z instrukcją print("Witaj!"). Wprowadzenie tej zmiany sprawi, że pliki w repozytoriach lokalnym i centralnym będą się różnić. Wykonaj polecenie git diff: # bash_lst058.sh $ git diff witaj_swiecie.py

>> diff --git a/witaj_swiecie.py b/witaj_swiecie.py index 0554473..1836004 100644 --- a/witaj_swiecie.py +++ b/witaj_swiecie.py @@ -1,2 +1,2 @@ print("Witaj, świecie!") +print("Witaj!")

Git wyświetli instrukcję print("Witaj!") na zielono, gdyż jest nowym fragmentem kodu. Dodatkowy znak + na początku wiersza oznacza, że został on dodany. Gdybyś usunął jakiś kod, usunięty fragment zostałby poprzedzony znakiem - i wyświetlony w kolorze czerwonym.

a9c8a50d59776690f264c310a5514dcc a

197

Dalsze kroki

Dalsze kroki W tym rozdziale opisałem jedynie podstawowe, najczęściej stosowane możliwości systemu kontroli wersji Git. Po ich opanowaniu sugeruję, byś poświęcił nieco czasu na poznanie bardziej zaawansowanych możliwości, takich jak tworzenie gałęzi oraz ich scalanie; informacje na ten temat można znaleźć na stronie https://www.codeschool.com/learn/git.

Słownictwo Baza kodu. Katalogi i pliki zawierające kody źródłowe tworzonego oprogramowania. Dodawanie do poczekalni. Wskazywanie plików (z modyfikacjami), które mają zostać wypchnięte do repozytorium centralnego. Git. Popularny system kontroli wersji. GitHub. Serwis internetowy pozwalający na przechowywanie kodów w chmurze. Numer rewizji. Unikalna sekwencja znaków przypisywana przez system Git każdej rewizji. Repozytorium centralne. Repozytorium przechowywane w serwisie internetowym, takim jak GitHub, z którym komunikują się wszystkie repozytoria lokalne w celu zachowania synchronizacji pomiędzy sobą. Repozytorium lokalne. Repozytorium umieszczone na Twoim komputerze. Repozytorium. Struktura danych tworzona przez systemy kontroli wersji, takie jak Git, przechowująca informacje o wszystkich zmianach wprowadzanych w bazie kodu projektu. Rewizja. Wersja projektu zapisana przez system Git w repozytorium. Struktura danych. Sposób organizacji i przechowywania informacji. Przykładami struktur danych są listy i słowniki. SVN. Popularny system kontroli wersji. System kontroli wersji. Program zaprojektowany w celu ułatwienia wspólnej pracy nad projektem grupie programistów. Wciąganie. Aktualizacja repozytorium lokalnego poprzez zapisywanie w nim zmian zarejestrowanych w repozytorium centralnym. Wypychanie. Aktualizacja repozytorium centralnego poprzez zapisywanie w nim zmian wprowadzonych w repozytorium lokalnym. Zatwierdzanie. Wykonywanie polecenia nakazującego systemowi Git zarejestrowanie zmian w repozytorium lokalnym.

a9c8a50d59776690f264c310a5514dcc a

198

Rozdział 19



Kontrola wersji

Wyzwania 1. Utwórz w serwisie GitHub nowe repozytorium. Zapisz w jednym katalogu wszystkie pliki z kodami Pythona, które napisałeś w ramach rozwiązywania wyzwań w poprzednich rozdziałach książki, i wypchnij je do tego repozytorium. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

20 Łączenie wszystkiego w całość

„W naszych czasach magia mitów i legend stała się prawdą. Można napisać odpowiednią inkantację na klawiaturze komputera, a jego ekran ożywa, pokazując rzeczy, których nie było lub nie mogło być…” — Frederic Brook

W tym rozdziale przekonasz się, jak ogromne możliwości daje programowanie, gdyż napiszemy w nim program gromadzący informacje na podstawie analizy danych z witryny WWW; programy tego typu są powszechnie określane jako web scrapery. Po napisaniu takiego narzędzia uzyskasz możliwość gromadzenia danych pozyskiwanych z największej istniejącej kolekcji informacji. Potęga web scrperów oraz łatwość, z jaką można je tworzyć, są jednymi z wielu powodów, dla których tak zafascynowało mnie programowanie. Mam nadzieję, że będzie to dotyczyć także Ciebie.

199

a9c8a50d59776690f264c310a5514dcc a

200

Rozdział 20



Łączenie wszystkiego w całość

Kod HTML Zanim zaczniemy pisać web scraper, trzeba odświeżyć podstawy języka HTML — języka znaczników hipertekstu. HTML to obok języków CSS i JavaScript jedna z kluczowych technologii używanych przez programistów do tworzenia witryn WWW. HTML to język używany do określania struktury stron WWW. Kod HTML składa się ze znaczników, których przeglądarka używa do ustalenia struktury i zawartości stron WWW. Z wykorzystaniem języka HTML można tworzyć całe witryny WWW. Jednak nie będą one wyglądały olśniewająco, gdyż to język JavaScript sprawia, że witryny są interaktywne, a język CSS nadaje im styl i wygląd. Poniżej przedstawiona została prosta strona WWW wyświetlająca tekst Witaj, świecie!. # bash_lst59.sh



Moja witryna

Witaj, świecie!

Kliknij tutaj.

Powyższy kod HTML należy zapisać w pliku. Ten plik możesz teraz otworzyć w przeglądarce (patrz rysunek 20.1), klikając go (być może będziesz musiał kliknąć go prawym przyciskiem myszy i zmienić domyślny program używany do otwierania danego pliku na przeglądarkę, by otworzyć go na przykład w programie Chrome). Po wyświetleniu pliku w przeglądarce zobaczysz stronę z napisem Witaj, świecie! i odnośnikiem do strony Google.

Rysunek 20.1. Prosta strona WWW z tekstem i odnośnikiem

Do wyświetlenia tej strony przeglądarka używa różnych znaczników HTML zapisanych w pliku. Znacznik HTML (czyli po prostu znacznik) jest jak instrukcja w językach programowania — każe przeglądarce coś zrobić. Większość znaczników składa się z dwóch części — znacznika otwierającego i zamykającego; pomiędzy nimi bardzo często jest umieszczony jakiś tekst. Przykładowo tekst

a9c8a50d59776690f264c310a5514dcc a

Pozyskiwanie informacji z witryny Google Wiadomości

201

umieszczony pomiędzy znacznikami oraz jest wyświetlany przez przeglądarkę jako tytuł karty. Można także umieszczać jedne znaczniki wewnątrz innych; wszystko, co znajduje się pomiędzy znacznikami i , to metadane dotyczące strony, natomiast wszystko pomiędzy znacznikami i to faktyczna zawartość strony. Znaczniki oraz wraz z zawartością definiują odnośnik. Znaczniki mogą zawierać dane. Przykładowo atrybut href="https://www.google.com" wewnątrz znacznika informuje, na jaką stronę prowadzi odnośnik. To jedynie drobna garść informacji o języku HTML, jednak w zupełności wystarczy, by napisać pierwszy web scraper.

Pozyskiwanie informacji z witryny Google Wiadomości W tym podrozdziale napiszemy web scraper, który pobiera doniesienia z witryny Google Wiadomości, odnajdując i pobierając wszystkie umieszczone na niej znaczniki . Strona Google Wiadomości używa tych znaczników, by tworzyć odwołania do innych witryn, z których zostały zaczerpnięte prezentowane treści; dlatego, oprócz pewnych dodatkowych informacji, będziemy gromadzić wszystkie adresy URL doniesień wyświetlanych na witrynie. Do analizy (inaczej do parsowania) kodu HTML strony Google Wiadomości użyjemy modułu BeautifulSoup. Parsowanie oznacza programowe przeanalizowanie danych zapisanych w jakimś formacie, takim jak HTML, w celu zrozumienia jego struktury i treści. Można to robić, by na przykład przekształcić pobrane dane na obiekt. Na początku musisz wykonać poniższe polecenie, by zainstalować moduł BeautifulSoup w systemie Ubuntu lub Unix: # bash_lst060.sh $ sudo pip install beautifulsoup4==4.4.1

>> Successfully installed beautifulsoup4-4.4.1

Analogiczne polecenie w systemie Windows będzie mieć następującą postać: # bash_lst061.sh $ pip install beautifulsoup4==4.4.1

>> Successfully installed beautifulsoup4-4.4.1

Python dysponuje wbudowanym modułem do obsługi adresów URL, o nazwie urllib. Utwórz zatem nowy plik Pythona i zapisz w nim poniższy fragment kodu: 1 2 3 4 5 6 7

# python_lst289.py import urllib.request from bs4 import BeautifulSoup class Scraper: def __init__(self,

a9c8a50d59776690f264c310a5514dcc a

202

8 9 10 11 12

Rozdział 20



Łączenie wszystkiego w całość

site): self.site = site def scrape(self): pass

Metoda __init__ pobiera parametr określający stronę WWW, którą należy przeanalizować. Nieco później przekażemy do niej adres "https://news.google.com/" jako parametr. Klasa Scraper definiuje także metodę scrape, którą będziemy wywoływać za każdym razem, gdy zechcemy pobrać dane ze strony. Następnie dodaj do metody scrape poniższy fragment kodu: 1 # python_lst290.py 2 3 def scrape(self): 4 r = urllib.request.urlopen(self.site) 5 html = r.read()

Funkcja urlopen() przesyła żądanie do podanej witryny i zwraca obiekt Response, który zawiera przesłany z witryny kod HTML. Z kolei funkcja response.read() zwraca kod HTML przechowywany w obiekcie Response. Cały kod HTML przesłany z witryny jest zapisany w zmiennej html. Teraz jesteśmy już gotowi do rozpoczęcia analizy, czyli parsowania, kodu HTML. Dodajmy do funkcji scrape kolejny wiersz kodu, który utworzy obiekt BeautifulSoup, i przekażmy do niego jako parametry zmienną html oraz łańcuch znaków "html.parser" (gdyż analizujemy kod HTML): 1 # python_lst291.py 2 3 def scrape(self): 4 r = urllib.request.urlopen(self.site) 5 html = r.read() 6 parser = "html.parser" 7 sp = BeautifulSoup(html, parser)

Obiekt BeautifulSoup wykonuje wszystkie najtrudniejsze operacje związane z analizą kodu HTML. Teraz możemy dodać do funkcji scrape kolejny fragment kodu, w którym skorzystamy z metody find_all obiektu BeautifulSoup. Kiedy przekażemy do niej parametr "a" (informujący metodę, że ma poszukiwać elementów ), metoda ta zwróci adresy URL wszystkich odnośników umieszczonych na pobranej stronie: 1 # python_lst292.py 2 3 def scrape(self): 4 r = urllib.request.urlopen(self.site) 5 html = r.read() 6 parser = "html.parser" 7 sp = BeautifulSoup(html, parser) 8 for tag in sp.find_all("a"): 9 url = tag.get("href") 10 if url is None: 11 continue 12 if "html" in url: 13 print("\n" + url)

a9c8a50d59776690f264c310a5514dcc a

Pozyskiwanie informacji z witryny Google Wiadomości

203

Metoda find_all zwraca obiekt iterowalny zawierający obiekty odszukanych znaczników (będą one zapisywane w zmiennej tag). Podczas każdej iteracji pętli zmiennej tag zostanie przypisany nowy obiekt Tag. Z kolei obiekty Tag zawierają wiele różnych zmiennych instancyjnych, jednak w tym programie interesuje nas tylko jedna z nich, czyli href, która zawiera adres URL. Adres ten możemy pobrać, wywołując metodę get i przekazując do niej łańcuch znaków "href". W końcu sprawdzamy, czy zmienna url zawiera jakieś dane, a konkretnie rzecz biorąc, czy zawiera gdzieś łańcuch "html" (gdyż nie interesują nas odnośniki wewnętrzne), a jeśli zawiera, to wyświetlamy adres. Poniżej przedstawiony został kompletny kod programu: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# python_lst293.py import urllib.request from bs4 import BeautifulSoup class Scraper: def __init__(self, site): self.site = site def scrape(self): r = urllib.request.urlopen(self.site) html = r.read() parser = "html.parser" sp = BeautifulSoup(html, parser) for tag in sp.find_all("a"): url = tag.get("href") if url is None: continue if "html" in url: print("\n" + url) news = "https://news.google.com/" Scraper(news).scrape()

Po uruchomieniu program wyświetli wyniki podobne do przedstawionych poniżej: https://www.nytimes.com/2017/07/24/opinion/chuck-schumer-employment-democrats.html https://www.washingtonpost.com/opinions/forget-a-better-deal-heres-what-wouldactually-work-for-democrats/2017/07/24/3865e556-70a9-11e7-9eac-d56bd5568db8_story.html https://www.nytimes.com/2017/07/24/us/politics/despite-brain-tumor-mccain-willreturn-for-health-care-vote.html

Teraz już możemy gromadzić odnośniki doniesień z serwisu Google Wiadomości, mamy zatem nieograniczone możliwości. Moglibyśmy na przykład napisać program analizujący słowa najczęściej używane w nagłówkach. Albo program analizujący nastawienia i sympatie zawarte w nagłówkach i określający korelację tych nastrojów z wynikami giełdowymi. Kiedy dysponujemy narzędziem typu web scraper, wszystkie informacje publikowane w internecie mamy na wyciągnięcie ręki — mam nadzieję, że to ekscytuje Cię w takim samym stopniu jak mnie.

a9c8a50d59776690f264c310a5514dcc a

204

Rozdział 20



Łączenie wszystkiego w całość

Słownictwo HTML. Język używany do określania struktury i zawartości stron WWW. Parsowanie. Oznacza programową analizę danych w formacie, takim jak HTML, w celu określenia jej struktury i zawartości. Można go używać na przykład do przetwarzania danych na obiekty. Web scraper. Program pobierający informacje ze stron WWW. Znacznik HTML. Przypomina instrukcję w językach programowania — informuje przeglądarkę, co należy zrobić.

Wyzwanie 1. Zmodyfikuj program w taki sposób, by odszukane adresy URL zapisywał w pliku. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Część

IV Wprowadzenie do informatyki

a9c8a50d59776690f264c310a5514dcc a

a9c8a50d59776690f264c310a5514dcc a

Rozdział

21 Struktury danych

„Właściwie uważam, że różnica pomiędzy złym i dobrym programistą zależy od tego, czy ważniejszy jest dla niego kod, czy struktury danych. Zły programista zwraca uwagę na kod. Dobry programista przejmuje się strukturami danych oraz ich wzajemnymi relacjami”. — Linus Torvalds

Struktury danych Struktura danych to format używany do przechowywania i organizowania informacji. Struktury danych mają kluczowe znaczenie dla programowania i większość języków programowania dysponuje kilkoma wbudowanymi ich rodzajami. Już wiemy, jak używać kilku wbudowanych struktur danych dostępnych w języku Python, takich jak listy, krotki oraz słowniki. W tym rozdziale przedstawione zostaną sposoby tworzenia i stosowania dwóch dodatkowych struktur — stosów oraz kolejek.

207

a9c8a50d59776690f264c310a5514dcc a

208

Rozdział 21



Struktury danych

Stosy Stos (ang. stack) jest strukturą danych. Podobnie jak w przypadku list, także do stosów można dodawać elementy oraz je z nich usuwać. Jednak w odróżnieniu od list, element dodawany do stosu umieszczany jest zawsze na jego końcu i usunąć ze stosu można tylko jego ostatni element. Z listy [1, 2, 3] możemy usunąć dowolny element. Kiedy jednak dysponujemy stosem o takiej samej zawartości, możemy z niego usunąć wyłącznie element 3. Jeśli tak zrobimy, stos będzie mieć postać [1, 2]. W tym momencie będziemy mogli usunąć ze stosu element 2. Po usunięciu elementu 2 ze stosu będziemy mogli usunąć element 1, co sprawi, że stos stanie się pusty. Gdy usuwamy element ze stosu, mówimy, że jest on zdejmowany. Jeśli do pustego stosu ponownie dodamy element 1, stos przyjmie postać [1]. Jeśli dodamy do niego liczbę 2, przyjmie postać [1, 2]. W przypadku dodawania elementu do stosu mówimy, że jest zapisywany na stosie lub odkładany na stos. Struktura danych tego typu, w której ostatni zapisany element jest jednocześnie pierwszym, który zostanie z niej pobrany, jest nazywana strukturą „ostatni zapisany, pierwszy obsłużony” (ang. LIFO — Last In First Out). Strukturę danych typu LIFO można porównać do stosu naczyń. Jeśli na takim stosie umieścimy pięć talerzy, to by dotrzeć do znajdującego się na samym dole, będziemy musieli zdjąć wszystkie pozostałe. Wyobraźmy sobie zatem każdą daną zapisywaną na stosie jako talerz — aby dostać się do niej, musimy zdjąć ze stosu wszystkie dane umieszczone wyżej. W tym miejscu rozdziału zajmiemy się utworzeniem stosu. Python dysponuje biblioteką udostępniającą implementacje zarówno stosu, jak i kolejki, jednak opracowanie własnej implementacji pozwoli lepiej zrozumieć, jak działa ta struktura danych. Nasz stos będzie udostępniał pięć metod: is_empty, push, pop, peek oraz size. Pierwsza metoda is_empty będzie zwracać wartość True, jeśli stos będzie pusty, oraz wartość False w przeciwnym przypadku. Metoda push będzie odkładać element na wierzchołek stosu. Z kolei metoda pop będzie usuwać i zwracać element z wierzchołka stosu. Metoda peek będzie zwracać element z wierzchołka stosu bez jego usuwania. I w końcu metoda size będzie zwracać liczbę całkowitą reprezentującą liczbę elementów zapisanych na stosie. Poniżej przedstawiona została implementacja stosu napisana w Pythonie: 1 # python_lst294.py 2 3 class Stack: 4 def __init__(self): 5 self.items = [] 6 7 def is_empty(self): 8 return self.items == [] 9 10 def push(self, item): 11 self.items.append(item) 12 13 def pop(self): 14 return self.items.pop() 15

a9c8a50d59776690f264c310a5514dcc a

209

Stosy

16 17 18 19 20 21

def peek(self): last = len(self.items)-1 return self.items[last] def size(self): return len(self.items)

Nowy stos po utworzeniu będzie pusty, a wywołanie jego metody is_empty zwróci True: 1 # python_lst295.py 2 3 stack = Stack() 4 print(stack.is_empty())

>> True

Po dodaniu do stosu nowego elementu metoda is_empty zwróci wartość False: 1 2 3 4 5

# python_lst296.py stack = Stack() stack.push(1) print(stack.is_empty())

>> False

Jeśli wywołamy metodę pop, aby usunąć element ze stosu, metoda is_empty ponownie zwróci wartość True: 1 2 3 4 5 6 7

# python_lst297.py stack = Stack() stack.push(1) item = stack.pop() print(item) print(stack.is_empty())

>> 1 >> True

I w końcu możemy skorzystać z metody peek, aby podejrzeć element znajdujący się na wierzchołku stosu bez jego zdejmowania, oraz z metody size, by sprawdzić liczbę elementów umieszczonych na stosie: 1 2 3 4 5 6 7 8 9 10 11

# python_lst298.py stack = Stack() for i in range(0, 6): stack.push(i) print(stack.peek()) print(stack.size())

>> 5 >> 6

a9c8a50d59776690f264c310a5514dcc a

210

Rozdział 21



Struktury danych

Odwracanie łańcucha znaków przy użyciu stosu Korzystając ze stosu, można odwrócić kolejność, w jakiej jest zapisana zawartość danej iterowalnej, gdyż wszystko, co zostanie zapisane na stosie, jest z niego pobierane w odwrotnej kolejności. W tym podrozdziale rozwiążemy problem, który często pojawia się na rozmowach kwalifikacyjnych — odwracanie kolejności znaków w łańcuchu poprzez jego zapisanie na stosie i odczyt: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

# python_lst299.py class Stack: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): last = len(self.items)-1 return self.items[last] def size(self): return len(self.items) stack = Stack() for c in "Witam": stack.push(c) reversed_string = "" for i in range(len(stack.items)): reversed_string += stack.pop() print(reversed_string)

>> matiW

Najpierw kolejno przeglądamy wszystkie znaki łańcucha "Witam" i zapisujemy je na stosie. Następnie pobieramy wszystkie elementy ze stosu, a każdy z nich dodajemy do zmiennej reverse. Po zakończeniu iteracji początkowe słowo będzie zapisane w odwrotnej kolejności, a program wyświetli łańcuch matiW.

a9c8a50d59776690f264c310a5514dcc a

211

Kolejki

Kolejki Kolejną strukturą danych jest kolejka (ang. queue). Także kolejki są podobne do list; pozwalają na dodawanie i usuwanie elementów. Co więcej, kolejki przypominają nieco stosy, gdyż elementy mogą być dodawane i usuwane z nich w ściśle określonej kolejności. Jednak w odróżnieniu od stosów, w których pierwszy element umieszczony na stosie jest jednocześnie ostatnim, który zostanie z niego pobrany, kolejki są strukturami danych „pierwszy zapisany, pierwszy obsłużony” (ang. FIFO — First In First Out) — pierwszy element zapisany w kolejce jest jednocześnie pierwszym, który zostanie z niej pobrany. Strukturę danych typu FIFO można sobie wyobrazić jako zwyczajną kolejkę osób czekających na kupno biletu do kina. Pierwsza osoba stojąca w tej kolejce jest jednocześnie pierwszą, która ten bilet kupi; druga osoba w kolejce kupi bilet jako druga w kolejności i tak dalej. W tym podrozdziale zaimplementujemy kolejkę udostępniającą cztery metody: enqueue, dequeue, is_empty oraz size. Pierwsza z tych metod, czyli enqueue, będzie dodawać element do kolejki, a druga — dequeue — pobierać z niej element. Metoda is_empty będzie zwracać wartość True, jeśli kolejka będzie pusta, lub wartość False w przeciwnym przypadku. Ostatnia metoda, size, będzie zwracać liczbę elementów zapisanych w kolejce. 1 # python_lst300.py 2 3 4 class Queue: 5 def __init__(self): 6 self.items = [] 7 8 def is_empty(self): 9 return self.items == [] 10 11 def enqueue(self, item): 12 self.items.insert(0, item) 13 14 def dequeue(self): 15 return self.items.pop() 16 17 def size(self): 18 return len(self.items)

Bezpośrednio po utworzeniu kolejka jest pusta, zatem wywołanie metody is_empty zwróci True: 1 # python_lst301.py 2 3 a_queue = Queue() 4 print(a_queue.is_empty())

>> True

Dodajmy teraz do kolejki kilka elementów i sprawdźmy jej wielkość: 1 # python_lst302.py 2 3 a_queue = Queue()

a9c8a50d59776690f264c310a5514dcc a

212

Rozdział 21

4 5 for i in range(5): 6 a_queue.enqueue(i) 7 8 print(a_queue.size())

>> 5

Kolejny przykład pokazuje usuwanie elementów z kolejki: 1 2 3 4 5 6 7 8 9 10 11 12 13

# python_lst303.py

>> >> >> >> >> >> >>

0 1 2 3 4

a_queue = Queue() for i in range(5): a_queue.enqueue(i) for i in range(5): print(a_queue.dequeue()) print() print(a_queue.size())

0

Kolejka po bilety Kolejka może symulować grupę osób pragnących kupić bilet do kina: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# python_lst304.py import time import random class Queue: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def enqueue(self, item): self.items.insert(0, item) def dequeue(self): return self.items.pop()

a9c8a50d59776690f264c310a5514dcc



Struktury danych

a

213

Kolejka po bilety

19 20 def size(self): 21 return len(self.items) 22 23 def simulate_line(self, till_show, max_time): 24 pq = Queue() 25 tix_sold = [] 26 27 for i in range(10): 28 pq.enqueue("osoba_nr." + str(i)) 29 30 t_end = time.time() + till_show 31 now = time.time() 32 while now < t_end and not pq.is_empty(): 33 now = time.time() 34 r = random.randint(0, max_time) 35 time.sleep(r) 36 person = pq.dequeue() 37 print(person) 38 tix_sold.append(person) 39 40 return tix_sold 41 42 queue = Queue() 43 sold = queue.simulate_line(5, 1) 44 print(sold)

>> osoba_nr.0 >> osoba_nr.1 ... >>> ['osoba_nr.0', 'osoba_nr.1', 'osoba_nr.2', 'osoba_nr.3', 'osoba_nr.4', 'osoba_nr.5', 'osoba_nr.6', 'osoba_nr.7']

Na początek utworzyliśmy funkcję o nazwie simulate_line, symulującą sprzedaż biletów grupie osób ustawionych w kolejce. Funkcja ta ma dwa parametry — till_show oraz max_time. Pierwszy z tych parametrów jest liczbą całkowitą określającą liczbę sekund pozostałych do rozpoczęcia seansu, kiedy to nie będzie już czasu na kupowanie biletów. Drugi parametr także jest liczbą całkowitą i określa maksymalny czas (mierzony w sekundach), jaki zajmuje kupienie biletu. Wewnątrz funkcji tworzymy nową, pustą kolejkę oraz pustą listę. Na liście będą umieszczane osoby, które już kupiły bilet. Następnie w kolejce zapisywanych jest 100 łańcuchów znaków, zaczynając od "osoba_nr.0", a kończąc na "osoba_nr.99". Każdy z tych łańcuchów znaków reprezentuje jedną osobę stojącą w kolejce po bilety. Wbudowany moduł time zawiera funkcję o nazwie time. Zwraca ona liczbę zmiennoprzecinkową reprezentującą liczbę sekund, które upłynęły od epoki — wyznaczonej daty (1 stycznia 1970 roku) używanej jako punkt odniesienia. Jeśli teraz wywołamy tę funkcję, zwróci ona wartość 1504001334.639 — liczbę sekund, które upłynęły od epoki. Gdybyśmy wywołali tę funkcję ponownie po upłynięciu 1 sekundy, zwrócona wartość zostałaby powiększona o 1. Następnie w zmiennej t_end zapisujemy wynik zwrócony przez funkcję time powiększony o liczbę sekund przekazaną jako parametr till_show. Wartość ta wyznacza punkt w przyszłości.

a9c8a50d59776690f264c310a5514dcc a

214

Rozdział 21



Struktury danych

Pętla while umieszczona w dalszej części funkcji jest wykonywana do momentu, gdy funkcja time zwróci wartość większą od t_end bądź też do momentu, gdy kolejka zostanie opróżniona. Wewnątrz tej pętli używamy funkcji sleep należącej do wbudowanego modułu time, aby wstrzymać wykonywanie programu na pewną liczbę sekund, wybraną losowo z zakresu od 0 do max_time. To wstrzymanie wykonywania kodu ma symulować czas, jaki zajmuje kupienie biletu. Czas, na jaki działanie programu zostanie wstrzymane, jest losowy, aby zasymulować fakt, że czas potrzebny do sprzedania biletu może być różny. Po przerwie spowodowanej wywołaniem funkcji sleep usuwamy z kolejki łańcuch reprezentujący osobę i umieszczamy go na liście tix_sold. Operacja ta reprezentuje sprzedanie biletu danej osobie. W ten sposób zaimplementowaliśmy funkcję symulującą sprzedawanie biletów kolejce osób. Liczba sprzedanych biletów jest zależna od przekazanych parametrów, jak również od losowo generowanych liczb.

Słownictwo Epoka. Wyznaczony moment czasu używany jako punkt odniesienia. FIFO. Pierwszy zapisany, pierwszy obsłużony. Kolejka. Struktura danych typu FIFO. LIFO. Ostatni zapisany, pierwszy obsłużony. Odkładanie. Dodawanie elementów do stosu. Stos. Struktura danych typu LIFO. Struktura danych ostatni zapisany, pierwszy obsłużony. Struktura danych, w której ostatni element zapisany w strukturze jest jednocześnie pierwszym, który zostanie z niej usunięty. Struktura danych pierwszy zapisany, pierwszy obsłużony. Struktura danych, w której pierwszy element zapisany w strukturze jest jednocześnie pierwszym, który zostanie z niej usunięty. Struktura danych. Format używany do przechowywania i organizowania informacji. Zdejmowanie. Usuwanie elementów ze stosu.

Wyzwania 1. Używając stosu, odwróć kolejność znaków w łańcuchu "wczoraj". 2. Użyj stosu, by utworzyć listę o zawartości [1, 2, 3, 4, 5], zapisaną w odwrotnej kolejności. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

22 Algorytmy

„Algorytm jest jak przepis”. — Waseem Latif

Ten rozdział zawiera krótkie wprowadzenie do algorytmów. Algorytm to seria kroków, jakie należy wykonać w celu rozwiązania problemu. Takim problemem może być przeszukanie listy lub wyświetlenie słów piosenki „99 butelek piwa na ścianie”.

FizzBuzz Nadszedł czas, by nauczyć się rozwiązywania testu FizzBuzz; są to popularne pytania zadawane na rozmowach kwalifikacyjnych, które służą do eliminacji kandydatów. Napisz program wyświetlający liczby od 1 do 100. Dla wielokrotności liczby 3 program zamiast liczby ma wyświetlać "Fizz", a dla wielokrotności liczby 5 ma wyświetlać "Buzz". Dla liczb będących wielokrotnością zarówno 3, jak i 5 program zamiast liczby ma wyświetlać "FizzBuzz". Aby rozwiązać ten problem, trzeba znać sposób pozwalający sprawdzić, czy dana liczba jest wielokrotnością 3, wielokrotnością 5 oraz jednocześnie wielokrotnością 3 i 5. Jeśli liczba jest wielokrotnością 3, można ją podzielić przez 3, a reszta z dzielenia wyniesie 0. To samo dotyczy

215

a9c8a50d59776690f264c310a5514dcc a

216

Rozdział 22



Algorytmy

wielokrotności 5. Resztę z dzielenia zwraca operator module (%). A zatem postawiony problem można rozwiązać, przeglądając kolejne liczby i sprawdzając, czy dana liczba jest podzielna przez 3 i 5, tylko przez 3 lub tylko przez 5: 1 # python_lst305.py 2 3 def fizz_buzz(): 4 for i in range(1, 101): 5 if i % 3 == 0 and i % 5 == 0: 6 print("FizzBuzz") 7 elif i % 3 == 0: 8 print("Fizz") 9 elif i % 5 == 0: 10 print("Buzz") 11 else: 12 print(i) 13 14 fizz_buzz()

>> 1 >> 2 >> Fizz ...

Zaczynamy od iteracji po liczbach od 1 do 100. Wewnątrz pętli sprawdzamy, czy aktualnie analizowana liczba jest podzielna przez 3 i 5. Duże znaczenie ma to, by zrobić to jako pierwsze, gdyż jeśli warunek ten zostanie spełniony, musimy wyświetlić słowa "FizzBuzz" i przejść do kolejnej iteracji. Gdybyśmy najpierw sprawdzili, czy liczba jest podzielna przez 3 lub 5 i gdyby okazało się, że jest, nie moglibyśmy wyświetlić "Fizz" lub "Buzz" i przejść do następnej iteracji, gdyż liczba wciąż mogłaby być podzielna przez 3 i przez 5, a w takiej sytuacji wyświetlanie "Fizz" lub "Buzz" byłoby błędem — należy wtedy wyświetlić "FizzBuzz". Po sprawdzeniu, czy liczba jest podzielna przez 3 i przez 5, kolejność wykonywania dwóch pozostałych testów nie ma już żadnego znaczenia. Jeśli liczba jest podzielna przez 3 lub przez 5, możemy wyświetlić odpowiednio "Fizz" lub "Buzz" i zakończyć iterację. Jeśli jednak liczba nie spełni żadnego z trzech warunków, będzie to oznaczać, że nie jest podzielna ani przez 3, ani przez 5, zatem należy ją wyświetlić.

Wyszukiwanie sekwencyjne Algorytm wyszukiwania służy do odnajdywania informacji w strukturze danych, takiej jak lista. Wyszukiwanie sekwencyjne jest prostym algorytmem wyszukiwania, który polega na sprawdzaniu kolejno poszczególnych elementów struktury danych, aby przekonać się, który z nich spełnia zadane kryteria. Każdy, kto kiedykolwiek grał w karty i szukał w talii określonej karty, wykonywał wyszukiwanie sekwencyjne. Operacja ta polega na sprawdzaniu kolejnych kart w talii i jeśli aktualnie sprawdzana karta nie jest kartą poszukiwaną, przechodzeniu do następnej. Kiedy w końcu poszukiwana karta

a9c8a50d59776690f264c310a5514dcc a

217

Palindrom

zostanie odnaleziona, operacja wyszukiwania dobiega końca. Operacja kończy się także w przypadku, gdy przejrzymy całą talię, a karta nie zostanie znaleziona — będzie to oznaczać, że karty nie ma w talii. Poniżej przedstawiona została implementacja wyszukiwania sekwencyjnego w języku Python: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# python_lst306.py def ss(number_list, n): found = False for i in number_list: if i == n: found = True break return found numbers = range(0, 100) s1 = ss(numbers, 2) print(s1) s2 = ss(numbers, 202) print(s2)

>> True >> False

Na początku przypisujemy zmiennej found wartość False. Zmienna ta przechowuje informację, czy algorytmowi udało się odnaleźć poszukiwaną liczbę, czy nie. Następnie w pętli przeglądamy wszystkie wartości zapisane na liście i sprawdzamy, czy są one równe poszukiwanej. Jeśli aktualnie sprawdzana wartość listy jest równa wartości poszukiwanej, przypisujemy zmiennej found wartość True, przerywamy działanie pętli, a następnie zwracamy z funkcji wartość zmiennej found (czyli True). Jeśli aktualnie analizowana wartość jest różna od poszukiwanej, pobieramy następną wartość z listy. Kiedy w ten sposób przeanalizujemy całą listę, zwracamy wartość zmiennej found. Jeśli poszukiwana wartość nie zostanie znaleziona na liście, wartością zmiennej found będzie False.

Palindrom Palindrom to słowo, które jest takie samo, kiedy czytamy je od przodu i od tyłu. Algorytm sprawdzający, czy słowo jest palindromem, można napisać, odwracając kolejność jego liter i sprawdzając, czy tak uzyskany łańcuch znaków jest taki sam jak słowo w swojej pierwotnej postaci. Jeśli oba łańcuchy będą identyczne, będzie to oznaczać, że słowo jest palindromem: 1 2 3 4 5 6 7 8 9

# python_lst307.py def is_palindrome(word): word = word.lower() return word[::-1] == word print(is_palindrome("Mama")) print(is_palindrome("Mim"))

>> False >> True

a9c8a50d59776690f264c310a5514dcc a

218

Rozdział 22



Algorytmy

Metoda lower usuwa z wyrazu wielkie litery i zastępuje je przed porównaniem małymi. Python traktuje M i m jak dwie różne litery, natomiast my chcemy je traktować jak jedną. Kod w postaci word[::-1] odwraca słowo. W języku Python zapis [::-1] jest używany do zwracania wycinka danej iterowalnej, której elementy są zapisane w kolejności odwrotnej niż w oryginale. W powyższym algorytmie odwracamy słowo, aby można było porównać je ze słowem pierwotnym. Jeśli oba słowa są takie same, zwracamy wartość True, gdyż będzie to oznaczać, że słowo jest palindromem. W przeciwnym razie zwracamy wartość False.

Anagram Anagram to słowo utworzone poprzez zmianę kolejności liter w pierwotnym słowie. Słowa absurd i brudas są anagramami, gdyż w każdym z nich można zmienić kolejność liter tak, by uzyskać to drugie. Aby sprawdzić, czy dwa słowa są anagramami, można posortować tworzące je litery w kolejności alfabetycznej, a następnie porównać, czy uzyskane w ten sposób łańcuchy znaków są identyczne: 1 2 3 4 5 6 7 8 9 10

# python_lst308.py def is_anagram(w1, w2): w1 = w1.lower() w2 = w2.lower() return sorted(w1) == sorted(w2) print(is_anagram("absurd", "brudas")) print(is_anagram("ognia", "orgia"))

>> True >> False

Najpierw na rzecz obu przekazanych słów wywołujemy metodę lower, tak by wielkość liter nie miała wpływu na uzyskiwane wyniki. Następnie oba słowa przekazujemy do metody sorted. Metoda ta zwraca litery słowa posortowane w kolejności alfabetycznej. W końcu porównujemy wyniki zwrócone przez oba wywołania metody sorted. Jeśli będą takie same, algorytm zwraca wartość True, w przeciwnym razie zwracana jest wartość False.

Zliczanie wystąpień liter W tym podrozdziale napiszemy algorytm zwracający liczbę wystąpień poszczególnych liter w łańcuchu znaków. Algorytm ten będzie przeglądać łańcuch znak po znaku i zapisywać w słowniku liczbę wystąpień poszczególnych znaków: 1 # python_lst309.py 2 3 def count_characters(string):

a9c8a50d59776690f264c310a5514dcc a

219

Rekurencja

4 count_dict = {} 5 for c in string: 6 if c in count_dict: 7 count_dict[c] += 1 8 else: 9 count_dict[c] = 1 10 print(count_dict) 11 12 13 count_characters("zdyscyplinowany")

>> {'a': 1, 'c': 1, 'd': 1, 'i': 1, 'l': 1, 'o': 1, 'n': 2, 'p': 1, 's': 1, 'w': 1, 'y': 3, 'z': 1}

W tym algorytmie przeglądamy kolejno wszystkie znaki łańcucha przekazanego jako parametr string. Jeśli aktualnie analizowany znak jest już zapisany w słowniku count_dict, skojarzona z nim wartość jest powiększana o 1. W przeciwnym razie dodajemy znak do słownika, kojarząc z nim wartość 1. Po zakończeniu wykonywania pętli for słownik count_dict będzie zawierać pary klucz-wartość dla każdej litery tworzącej łańcuch znaków. Wartością skojarzoną z każdym z kluczy będzie liczba wystąpień danej litery w łańcuchu.

Rekurencja Rekurencja jest techniką rozwiązywania problemów polegającą na dzieleniu początkowego problemu na coraz to mniejsze i mniejsze problemy, aż do momentu, w którym dany problem da się łatwo rozwiązać. Do tej pory rozwiązywaliśmy problemy, używając algorytmów iteracyjnych. Algorytmy tego typu rozwiązują problemy poprzez wielokrotne wykonywanie tych samych czynności, zazwyczaj w pętli. Z kolei algorytmy rekurencyjne bazują na funkcjach, które wywołują same siebie. Każdy problem, który można rozwiązać metodą iteracyjną, można także rozwiązać rekurencyjnie, jednak czasami okazuje się, że algorytm rekurencyjny będzie bardziej eleganckim rozwiązaniem. Algorytm rekurencyjny jest pisany wewnątrz funkcji. Taka funkcja musi określać przypadek bazowy, czyli warunek, który kończy wykonywanie algorytmu rekurencyjnego i zapobiega jego działaniu w nieskończoność. Wewnątrz funkcji umieszczane jest także wywołanie jej samej. Za każdym razem gdy funkcja wywołuje samą siebie, przybliża się także do przypadku bazowego. W końcu warunek przypadku bazowego zostaje spełniony, problem — rozwiązany, a funkcja przestaje wywoływać samą siebie. Algorytm spełniający te trzy warunki spełnia trzy prawa rekurencji. 1. Algorytm rekurencyjny musi mieć przypadek bazowy. 2. Algorytm rekurencyjny musi zmieniać swój stan i przybliżać się do przypadku bazowego. 3. Algorytm rekurencyjny musi, rekurencyjnie1, wywoływać sam siebie. 1

https://interactivepython.org/runestone/static/pythonds/Recursion/TheThreeLawsofRecursion.html

a9c8a50d59776690f264c310a5514dcc a

220

Rozdział 22



Algorytmy

Poniżej przedstawiony został algorytm rekurencyjny generujący słowa popularnej piosenki folkowej „99 butelek piwa na ścianie”: 1 # python_lst310.py 2 3 def bottles_of_beer(bob): 4 """ Wyświetla słowa piosenki 5 99 butelek piwa na ścianie. 6 :param bob: Musi być liczbą 7 dodatnią. 8 """ 9 if bob < 1: 10 print("""Nie ma już butelek 11 piwa na ścianie. 12 Nie ma już butelek piwa.""") 13 return 14 tmp = bob 15 bob -= 1 16 print("""{} butelek piwa na ścianie. 17 {} butelek piwa. Jedną weź 18 i przekaż w koło, {} butelek 19 piwa na ścianie. 20 """.format(tmp, 21 tmp, 22 bob)) 23 bottles_of_beer(bob) 24 25 26 bottles_of_beer(99)

>> 99 butelek piwa na ścianie. 99 butelek piwa. Jedną weź i przekaż w koło, 98 butelek piwa na ścianie. >> 98 butelek piwa na ścianie. 98 butelek piwa. Jedną weź i przekaż w koło, 97 butelek piwa na ścianie. ... >> Nie ma już butelek piwa na ścianie. Nie ma już butelek piwa.

W powyższym programie pierwsze prawo rekurencji jest spełnione dzięki określeniu poniższego przypadku bazowego: 1 # python_lst311.py 2 3 if bob < 1: 4 print("""Nie ma już butelek 5 piwa na ścianie. 6 Nie ma już butelek piwa.""") 7 return

Kiedy zmienna bob przyjmie wartość mniejszą od 1, funkcja wyświetli stosowny tekst i zakończy wywoływać samą siebie. Wiersz bob -= 1 spełnia drugie prawo rekurencji, gdyż dekrementacja zmiennej bob przybliża algorytm do przypadku bazowego. W powyższym przykładzie, w pierwszym wywołaniu funkcji bottles_of_beer przekazaliśmy do niej wartość 99. Przypadek bazowy zostanie spełniony, gdy wartość zmiennej bob będzie mniejsza od 1, a za każdym razem, gdy funkcja wywołuje samą siebie, przybliża się nieco do przypadku bazowego.

a9c8a50d59776690f264c310a5514dcc a

221

Słownictwo

Ostatnie, trzecie prawo rekurencji jest spełnione przez poniższy wiersz kodu: 1 # python_lst312.py 2 3 bottles_of_beer(bob)

Ten wiersz zapewnia, że tak długo, jak długo przypadek brzegowy nie zostanie spełniony, funkcja będzie wywoływać samą siebie. Za każdym razem, gdy funkcja wywołuje samą siebie, w wywołaniu przekazywana jest wartość parametru pomniejszona o 1, dzięki czemu algorytm przybliża się do przypadku bazowego. Pierwsze wywołanie powyższego wiersza kodu spowoduje przekazanie jako parametru wartości 98, w kolejnym wywołaniu parametr przyjmie wartość 97, w następnym — 96 i tak dalej, aż do momentu, gdy wartość parametru będzie mniejsza od 1; w tym przypadku funkcja wyświetli tekst "Nie ma już butelek piwa na ścianie. Nie ma już butelek piwa.", po czym wykona instrukcję return, która zatrzymuje jej działanie. Rekurencja jest jednym z najtrudniejszych zagadnień, z jakimi borykają się początkujący programiści. Jeśli także Tobie sprawia trudności — nie przejmuj się i ucz się dalej. I pamiętaj: aby zrozumieć rekurencję, najpierw musisz zrozumieć... rekurencję.

Słownictwo Algorytm iteracyjny. Algorytm iteracyjny rozwiązuje problem poprzez wielokrotne powtarzanie określonych kroków, zazwyczaj w pętli. Algorytm rekurencyjny. Algorytm rekurencyjny rozwiązuje problem, korzystając z funkcji, która wywołuje samą siebie. Algorytm wyszukiwania. Algorytm wyszukujący informację w strukturze danych (na przykład liście). Algorytm. Seria kroków wykonywanych w celu rozwiązania problemu. Anagram. Słowo utworzone poprzez zmianę kolejności liter innego słowa. Palindrom. Słowo, które ma tę samą postać czytane od początku i od końca. Przypadek bazowy. Warunek kończący działanie algorytmu rekurencyjnego. Rekurencja. Metoda rozwiązywania problemu poprzez jego podział na coraz to mniejsze problemy, aż do momentu, w którym dany problem będzie można łatwo rozwiązać. Wyszukiwanie sekwencyjne. Prosty algorytm wyszukiwania służący do odnajdywania informacji w strukturze danych, polegający na sprawdzaniu kolejnych elementów struktury i porównywaniu ich z poszukiwanym.

a9c8a50d59776690f264c310a5514dcc a

222

Rozdział 22



Algorytmy

Wyzwania 1. Zarejestruj się na witrynie http://leetcode.com i spróbuj rozwiązać trzy problemy algorytmiczne z poziomu prostego. Rozwiązania można znaleźć w przykładach dołączonych do książki, które możesz pobrać z serwera FTP wydawnictwa Helion ftp://ftp.helion.pl/przyklady/proprs.zip.

a9c8a50d59776690f264c310a5514dcc a

Część

V Zdobywanie pracy

a9c8a50d59776690f264c310a5514dcc a

a9c8a50d59776690f264c310a5514dcc a

Rozdział

23 Najlepsze praktyki programistyczne

„Swój kod zawsze pisz w taki sposób, jakby osoba, która będzie go potem utrzymywać, była brutalnym psychopatą, który wie, gdzie mieszkasz”. — John Woods

Kod produkcyjny to kod stosowany w produkcie używanym przez innych. Kiedy udostępniamy oprogramowanie jako produkcyjne, oznacza to, że oddajemy je światu. W tym rozdziale przedstawię kilka ogólnych zasad programistycznych, które mogą pomóc w pisaniu kodu nadającego się na kod produkcyjny. Wiele z tych zasad pochodzi z książki The Pragmatic Programmer Andy’ego Hunta i Dave’a Thomasa, książki która w zdecydowany sposób poprawiła jakość pisanego przeze mnie kodu.

225

a9c8a50d59776690f264c310a5514dcc a

226

Rozdział 23



Najlepsze praktyki programistyczne

Pisz kod w ostateczności Twoim zadaniem jako programisty jest pisanie możliwie jak najmniejszej ilości kodu. Kiedy staniesz przed koniecznością rozwiązania problemu, Twoją pierwszą myślą nie powinna być: „Jak mam rozwiązać ten problem?”. Powinieneś raczej zadać sobie pytanie: „Czy ktoś inny rozwiązał już ten problem wcześniej i czy mogę użyć jego rozwiązania?”. Jeśli próbujesz rozwiązać problem, istnieje spore prawdopodobieństwo, że ktoś zrobił to już wcześniej. Zacznij zatem od poszukania rozwiązania problemu w internecie. Dopiero wtedy, kiedy upewnisz się, że nikt inny nie rozwiązał tego problemu, zacznij go rozwiązywać samodzielnie.

Zasada DRY DRY to zasada programistyczna, której nazwa stanowi skrót angielskich słów Don’t Repeat Yourself — nie powtarzaj się. Chodzi w niej o to, by nie powtarzać w programie tego samego lub bardzo podobnego kodu. Taki kod należy umieścić w funkcji, której będzie można używać w wielu sytuacjach.

Prostopadłość Prostopadłość (ang. orthogonality) to zasada spopularyzowana przez autorów książki Pragmatyczny programista. Jak wyjaśniają Hunt i Thomas: „W kontekście programowania termin ten zaczął oznaczać pewną niezależność lub separację. Dwie lub większa liczba rzeczy są prostopadłe, jeśli zmiany w jednej z nich nie wywołują zmian w pozostałych. W dobrze zaprojektowanym systemie zmiany bazy danych będą prostopadłe do interfejsu użytkownika: będzie można zmienić interfejs użytkownika bez wpływu na bazę danych, jak również zmienić używaną bazę danych bez wpływu na interfejs użytkownika”. Aby przełożyć to na praktykę, zapamiętaj, że „a nie powinno wpływać na b”. Jeśli dysponujemy dwoma modułami, takimi jak moduł a oraz moduł b, to moduł a nie powinien zmieniać elementów modułu b i na odwrót. Jeśli zaprojektujemy system, w którym moduł a zmienia b, który z kolei zmienia moduł c, a ten zmienia moduł d, to sytuacja szybko wymknie się spod kontroli, a system stanie się niemożliwy lub trudny do zarządzania.

Każdy element danych powinien mieć jedną reprezentację Jeśli używamy jakiegoś elementu danych, powinien on być przechowywany tylko w jednym miejscu. W ramach przykładu załóżmy, że piszemy oprogramowanie korzystające z numerów telefonów. Jeśli w naszym programie będziemy używać dwóch funkcji operujących na liście numerów kierunkowych, musimy się upewnić, że w programie będzie istnieć tylko jedna taka lista.

a9c8a50d59776690f264c310a5514dcc a

227

Funkcje powinny robić tylko jedną rzecz

Nie powinny się w nim pojawić dwie listy numerów kierunkowych, każda używana przez inną funkcję. Wszystkie numery kierunkowe powinny być zapisane w jednej zmiennej globalnej. Albo jeszcze lepiej: można je zapisać w pliku lub bazie danych. Problem z powielanymi danymi pojawia się w przypadkach, kiedy trzeba będzie je zmienić — gdyż będziemy musieli pamiętać, aby odpowiednio zmienić je w każdym miejscu, w jakim zostały powielone. Jeśli zmienimy listę numerów kierunkowych w jednej funkcji, lecz zapomnimy zrobić to w drugiej, która także używa tych numerów, nasz program przestanie działać prawidłowo. Problemu tego można łatwo uniknąć, dbając o to, by każdy element danych miał tylko jedną reprezentację.

Funkcje powinny robić tylko jedną rzecz Każda funkcja powinna robić jedną i tylko jedną rzecz. Jeśli zauważymy, że nasze funkcje stają się zbyt długie, warto zadać sobie pytanie, czy realizują one tylko jedno zadanie. Ograniczenie funkcji do wykonywania tylko jednego zadania daje kilka korzyści. Kod stanie się łatwiejszy do czytania i analizy, gdyż nazwa funkcji będzie dokładnie opisywać jej przeznaczenie. W razie problemów z działaniem kod będzie łatwiejszy do testowania, gdyż każda funkcja będzie odpowiedzialna za konkretne zadanie, dzięki czemu będziemy mogli szybciej odszukać i zdiagnozować problematyczną funkcję. Już wielu sławnych programistów stwierdzało: „Tak wiele złożoności w oprogramowaniu jest efektem tego, że jeden element kodu realizuje dwie różne rzeczy”.

Jeśli to trwa zbyt długo, zapewne robimy coś źle Próbujemy zrobić coś, co w oczywisty sposób jest złożone, na przykład chcemy operować na dużych zbiorach danych. Jeśli wtedy uruchamianie programu trwa zbyt długo, prawdopodobnie robimy coś źle.

Wykonuj operacje w optymalny sposób już od samego początku Jeśli podczas pisania kodu pomyślisz: „Wiem, że można to zrobić lepiej, ale jestem już w połowie pracy, więc nie chce mi się przerywać i zastanawiać się, jak to poprawić”, przerwij pracę! Nie kontynuuj pisania tego kodu — napisz go od początku lepiej.

a9c8a50d59776690f264c310a5514dcc a

228

Rozdział 23



Najlepsze praktyki programistyczne

Zachowaj zgodność z konwencjami Poświęcenie czasu na poznanie konwencji stosowanych w nowym języku programowania ułatwi Ci czytanie i analizę kodu pisanego w tym języku. PEP 8 to grupa wytycznych dotyczących pisania kodu w języku Python, które warto przeczytać. Obejmuje ona także zasady kontynuowania pisania kodu w kolejnych wierszach. Zasady te są dostępne na stronie https://www.python.org/dev/peps/pep-0008/.

Używaj dobrego IDE W tej książce do pisania kodu korzystaliśmy z IDLE, środowiska programistycznego dostarczanego wraz z językiem Python. Jednak jest ono tylko jednym z wielu dostępnych, zintegrowanych środowisk programistycznych (IDE) i osobiście nie polecam jego stosowania na dłuższą metę, gdyż ma ograniczone możliwości. Jeśli na przykład otworzymy projekt programu pisanego w Pythonie w lepszym IDE, każdy z plików źródłowych zostanie wyświetlony na osobnej karcie. W IDLE każdy plik trzeba otwierać w odrębnym oknie, co jest męczące i znacząco utrudnia poruszanie się pomiędzy poszczególnymi plikami. Używam IDE o nazwie PyCharm utworzonego przez firmę JetBrains. Dostępne jest ono w wersji darmowej oraz w wersji profesjonalnej. Poniżej przedstawiłem listę tych możliwości PyCharm, które pozwalają mi zaoszczędzić najwięcej czasu. 1. Jeśli ktoś chce zobaczyć definicję zmiennej, funkcji lub obiektu, PyCharm ma skrót pozwalający przeskoczyć prosto do kodu definiującego dany element (nawet wtedy, kiedy definicja ta znajduje się w innym pliku). Inny skrót pozwala wrócić do miejsca, w którym byliśmy wcześniej. 2. PyCharm dysponuje możliwością zapisywania lokalnej historii, która w ogromny sposób poprawiła moją produktywność. PyCharm automatycznie zapisuje nową wersję projektu, za każdym razem kiedy zapisujemy wprowadzane modyfikacje. Można zatem używać PyCharm jako lokalnego odpowiednika systemu kontroli wersji, bez konieczności wysyłania kodów do repozytorium. Sami nie musimy przy tym nic robić — wszystko dzieje się automatycznie. Zanim dowiedziałem się o tej możliwości, zdarzało się, że rozwiązywałem problem, potem zmieniałem rozwiązanie, a jeszcze później jednak postanawiałem wrócić do pierwotnego rozwiązania. Gdybym zapomniał przesłać je na GitHub, po wprowadzeniu zmian bezpowrotnie straciłbym to pierwotne rozwiązanie. Jednak dzięki PyCharm można cofnąć się w czasie o 10 min i wczytać projekt dokładnie w takiej postaci, jaką miał w tym czasie. Jeśli zechcemy zmienić zdanie, będziemy mogli dowolnie przeskakiwać pomiędzy różnymi wersjami rozwiązania i to tak wiele razy, jak tylko zechcemy. 3. W trakcie pracy zapewne wielokrotnie kopiujemy i wklejamy kod. W PyCharm można przesuwać kod w górę i w dół edytowanej strony.

a9c8a50d59776690f264c310a5514dcc a

229

Rejestracja

4. PyCharm współpracuje z kilkoma systemami kontroli wersji, takimi jak Git oraz SVN. Zamiast przechodzić do wiersza poleceń, możemy ich jednak używać bezpośrednio z poziomu IDE. Im mniej razy będziemy musieli przechodzić z IDE do wiersza poleceń i z powrotem, tym bardziej wzrośnie nasza produktywność. 5. PyCharm dysponuje wbudowanym oknem wiersza poleceń oraz oknem Python Shell. 6. PyCharm dysponuje wbudowanym debuggerem. Debugger to program pozwalający na wstrzymanie wykonywania kodu i wykonywanie go wiersz po wierszu oraz podglądanie wartości zmiennych w różnych miejscach kodu. Jeśli jesteś zainteresowany poznaniem programu PyCharm, firma JetBrains przygotowała odpowiedni poradnik, który jest dostępny na stronie https://www.jetbrains.com/help/pycharm/2016.1/quickstart-guide.html.

Rejestracja Rejestracja (ang. logging) to praktyka polegająca na zapisywaniu danych podczas wykonywania programu. Z rejestracji można korzystać, by ułatwić sobie debugowanie programu oraz zyskać dodatkowy wgląd w to, co się dzieje podczas jego wykonywania. Python udostępnia specjalny moduł do rejestracji o nazwie logging; pozwala on na wyświetlanie komunikatów konsoli i zapisywanie ich w pliku. Czasami pisząc programy, robimy błędy, nikt jednak nie chce, by przeszły one niezauważone — dlatego warto zapisywać informacje o tym, co się dzieje w programie, tak by później można je było przeglądać. Rejestracja jest także przydatna do celów gromadzenia i analizy danych. Można by na przykład skonfigurować serwer w taki sposób, by rejestrował dane — w tym datę i godzinę — za każdym razem gdy zostanie odebrane żądanie. Wszystkie te informacje można zapisywać w bazie danych i napisać dodatkowy program, który będzie je analizować i wyświetlać wykres prezentujący na przykład liczbę odwiedzin w poszczególnych godzinach. Bloger Henrik Warne pisze: „Jedną z różnic pomiędzy dobrym oraz złym programistą jest to, że dobry programista dodaje do kodu mechanizmy rejestracji oraz tworzy narzędzia ułatwiające debugowanie programu, kiedy coś pójdzie w nim źle”. Więcej informacji na temat modułu logging można znaleźć na stronie https://docs.python.org/3/howto/logging.html.

Testowanie Testowanie programu oznacza sprawdzenie, czy dany program spełnia wymagania, które stanowiły podstawy jego projektu i utworzenia, reaguje prawidłowo na wszelkiego rodzaju dane wejściowe, wykonuje swoje działania dostatecznie szybko, jest odpowiednio użyteczny, można go zainstalować w docelowych środowiskach oraz odpowiada ogólnym efektom oczekiwanym przez zleceniodawców lub zainteresowanych. W celu testowania programów programiści piszą kolejne programy.

a9c8a50d59776690f264c310a5514dcc a

230

Rozdział 23



Najlepsze praktyki programistyczne

W środowisku produkcyjnym testowanie nie jest czynnością opcjonalną. Każdy program, który ma trafić do fazy produkcyjnej, należy uznać za niekompletny, jeśli nie zostaną do niego napisane odpowiednie testy. Jeśli jednak piszemy program na bieżące potrzeby, którego nie planujemy więcej używać, przygotowywanie dla niego testów może być stratą czasu. Jeśli tworzymy program, który będzie używany przez innych, to testy należy pisać. Jak powiedziało kilku sławnych programistów: „Kod nieprzetestowany jest kodem niedziałającym”. Informacje na temat modułu unittest dostępnego w języku Python można znaleźć na stronie https://docs.python.org/ 3/library/unittest.html.

Przeglądanie kodu Przeglądanie kodu następuje wtedy, gdy ktoś czyta nasz kod i przekazuje nam swoje uwagi. Warto to robić jak najczęściej zwłaszcza wtedy, kiedy samodzielnie uczymy się programowania. Jeśli nawet będziesz się starał stosować do wszystkich najlepszych praktyk opisanych w tym rozdziale, to i tak znajdą się rzeczy, które będziesz robić nieprawidłowo. Dlatego przyda Ci się ktoś doświadczony, kto przeglądnie Twój kod i zwróci uwagę na popełniane błędy, byś mógł je poprawić. Code Review to witryna, na której można opublikować swój kod, by został skomentowany przez społeczność programistów. Każdy może z niej skorzystać i opublikować swój kod. Taki kod przeglądają członkowie społeczności Stack Exchange, a następnie przekazują informacje o tym, co zostało w nim napisane prawidłowo, oraz sugestie dotyczące tego, co można by w nim poprawić. Witryna ta jest dostępna pod adresem http://codereview.stackexchange.com/.

Bezpieczeństwo Bezpieczeństwo to zagadnienie, które programista samouk może łatwo przeoczyć. Najprawdopodobniej na rozmowach kwalifikacyjnych nie padną żadne pytania dotyczące zagadnień bezpieczeństwa, które również nie odgrywają ważnego znaczenia w przypadku programów pisanych podczas nauki programowania. Niemniej jednak, kiedy już znajdziemy pierwszą pracę, staniemy się bezpośrednio odpowiedzialni za bezpieczeństwo pisanego kodu. W tym podrozdziale zamieszczę kilka wskazówek, które pozwolą o nie zadbać. Wcześniej w książce przedstawione zostało polecenie sudo pozwalające na wykonywanie programów z uprawnieniami administratora. Nigdy nie należy go używać do wykonywania programów, jeśli nie jest to absolutnie konieczne; kiedy hacker włamie się do programu, zyska uprawnienia administratora. Oprócz tego, jeśli zarządzamy serwerem, należy wyłączyć możliwość logowania się na konto root. Każdy hacker wie, że takie konto istnieje, dlatego jest ono jednym z podstawowych celów podczas ataków na system. Zawsze należy zakładać, że dane wejściowe podawane przez użytkownika są niebezpieczne. Istnieje kilka rodzajów ataków wykorzystujących błędy w programach pobierających dane od użytkowników — dlatego zawsze należy zakładać, że dane te są niebezpieczne i odpowiednio je traktować.

a9c8a50d59776690f264c310a5514dcc a

231

Słownictwo

Innym sposobem zabezpieczania programów jest minimalizacja powierzchni ataku — różnych obszarów programu, które napastnik może wykorzystać do gromadzenia danych lub przeprowadzenia ataku na system. Zapewniając, że powierzchnia ataku będzie możliwie jak najmniejsza, redukujemy także prawdopodobieństwo, że nasz program będzie podatny na atak. Oto kilka strategii minimalizacji powierzchni ataku: o ile to możliwe, należy unikać przechowywania poufnych informacji, należy przydzielać użytkownikom możliwie najniższy poziom dostępu, należy używać jak najmniej bibliotek pisanych przez innych twórców (im mniej kodu, tym mniej potencjalnych luk w zabezpieczeniach) i w końcu należy usuwać z kodu możliwości, które już nie są potrzebne (im mniej kodu, tym mniej potencjalnych zagrożeń). Unikanie logowania na konto administratora, traktowanie danych wpisywanych przez użytkowników jako niebezpieczne oraz minimalizacja powierzchni ataku to trzy ważne kroki pozwalające poprawić bezpieczeństwo programów. Jednak stanowią one jedynie punkt wyjścia. Zawsze należy starać się myśleć jak hacker. W jaki sposób hacker mógłby wykorzystać ten kod? Takie podejście pozwala odkrywać słabe punkty, które inaczej mogłyby zostać przegapione. Zagadnienie bezpieczeństwa jest zbyt obszerne, bym mógł przedstawić je dokładniej w tej książce, dlatego zawsze należy o nim myśleć i zdobywać informacje na jego temat. Najlepiej wyraził to Bruce Schneier: „Bezpieczeństwo to stan umysłu”.

Słownictwo Debugger. Program pozwalający na wstrzymanie wykonywania kodu, wykonywanie go wiersz po wierszu oraz podglądanie wartości zmiennych w różnych miejscach kodu. DRY. Zasada programistyczna, która zaleca, by nie powtarzać kodu (ang. Don’t Repeat Yourself). Kod produkcyjny. Kod w produkcie używanym przez inne osoby. Powierzchnia ataku. Różne obszary programu, które napastnik może wykorzystać do gromadzenia danych lub przeprowadzenia ataku na system. Produkcja. Oddawanie kodu od użytku, udostępnianie go odbiorcom. Prostopadłość. „W kontekście programowania termin ten zaczął oznaczać pewną niezależność lub separację. Dwie lub większa liczba rzeczy są prostopadłe, jeśli zmiany w jednej z nich nie wywołują zmian w pozostałych. W dobrze zaprojektowanym systemie zmiany bazy danych będą prostopadłe do interfejsu użytkownika: będzie można zmienić interfejs użytkownika bez wpływu na bazę danych, jak również zmienić używaną bazę danych bez wpływu na interfejs użytkownika”. Przeglądanie kodu. Mówimy o nim, gdy ktoś czyta nasz kod i dzieli się z nami opiniami na jego temat. Rejestracja. Praktyka polegająca na zapisywaniu danych podczas wykonywania programu.

a9c8a50d59776690f264c310a5514dcc a

232

Rozdział 23



Najlepsze praktyki programistyczne

Testowanie. Sprawdzanie, czy program „spełnia wymagania, które stanowiły podstawy dla jego projektu i tworzenia, reaguje prawidłowo na wszelkiego rodzaju dane wejściowe, wykonuje swoje działania dostatecznie szybko, jest odpowiednio użyteczny, można go zainstalować w docelowych środowiskach oraz odpowiada ogólnym efektom oczekiwanym przez zleceniodawców lub zainteresowanych”.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

24 Pierwsza praca w charakterze programisty

„Obawiaj się «prawdziwego świata». Jego urok w oczach prelegenta jest zawsze zaproszeniem, by nie stawiać wyzwania jego milczącym założeniom”. — Edsger W. Dijkstra

Ostatnia część tej książki ma Ci pomóc w rozwoju kariery. Zdobycie pierwszej pracy programisty wymaga szczególnego wysiłku, jeśli jednak będziesz postępować zgodnie z zamieszczonymi tu radami, nie powinieneś mieć z tym większego problemu. Na szczęście, kiedy już będziesz programistą, zyskasz nieco doświadczenia i przyjdzie czas na znalezienie kolejnego zatrudnienia, rekruterzy sami będą się do Ciebie zgłaszać.

Określ ścieżkę Od kandydata na programistę oczekiwana jest znajomość konkretnego zestawu technologii, zależnego od dziedziny stanowiska. Choć podczas nauki programowania można zdobywać umiejętności ogólne (taki programista potrafi zająć się wszystkim) i znaleźć zatrudnienie, jednak

233

a9c8a50d59776690f264c310a5514dcc a

234

Rozdział 24



Pierwsza praca w charakterze programisty

lepiej skoncentrować się na tym obszarze programowania, który się najbardziej podoba i stać się w nim ekspertem. Skoncentrowanie się na jednej ścieżce zagadnień programistycznych znacznie ułatwi znalezienie pracy. Dwiema najpopularniejszymi ścieżkami są programowanie aplikacji internetowych oraz aplikacji mobilnych. W ramach każdej z nich można wyróżnić dwie specjalizacje — programowanie front-endu oraz back-endu. Ta pierwsza koncentruje się na tworzeniu tych części aplikacji, które użytkownik widzi, czyli na graficznym interfejsie użytkownika. Z kolei druga zajmuje się tworzeniem tego wszystkiego, czego użytkownik nie widzi — czyli tych fragmentów aplikacji, które dostarczają danych jej interfejsowi użytkownika. W ogłoszeniach o pracę można znaleźć takie tytuły jak „Python Backend Developer”, co będzie oznaczać, że poszukiwana jest osoba potrafiąca tworzyć w Pythonie serwerowe elementy witryn WWW. Opis stanowiska będzie zawierać szczegółową listę technologii, które powinien znać idealny kandydat, wraz z dodatkowymi potrzebnymi umiejętnościami. Niektóre firmy mają odrębne zespoły zajmujące się tworzeniem front-endu oraz back-endu aplikacji. Inne zatrudniają jedynie programistów, którzy potrafią zajmować się oboma tymi zagadnieniami, jednak dotyczy to wyłącznie tych firm, których zadaniem jest tworzenie witryn i aplikacji. Istnieje także wiele innych dziedzin programowania, którymi można się zajmować, takich jak bezpieczeństwo, projektowanie systemów czy też analiza danych. Opisy oferowanych stanowisk są doskonałym źródłem informacji o wymaganiach stawianych kandydatom na różne stanowiska programistyczne. Dobrym miejscem do rozpoczęcia poszukiwań jest lista Python Job Board, dostępna na stronie http://www.python.org/jobs. Warto przeczytać wymagania dla kilku oferowanych stanowisk, jak również listę wymienianych w nich technologii, aby stwierdzić, czego trzeba będzie się nauczyć, by być konkurencyjnym podczas poszukiwania pracy.

Zdobywanie początkowego doświadczenia Zanim zostaniesz zatrudniony jako programista, będziesz musiał zdobyć doświadczenie. Jak jednak można je zdobyć, skoro nikt Cię nie zatrudni bez doświadczenia? Ten problem można rozwiązać na kilka sposobów. Można się zająć jakimś projektem typu open source — stworzyć własny lub dołączyć od grupy rozwijającej jeden z tysięcy projektów tego typu dostępnych w serwisie GitHub. Jeszcze innym rozwiązaniem jest praca na własny rachunek. Można utworzyć profil w serwisie, takim jak Upwork, i rozpocząć starania o proste zadania programistyczne. Sugeruję, by znaleźć kogoś, kogo znamy i kto ma do wykonania jakieś proste zadanie programistyczne, nakłonić go do założenia konta w serwisie Upwork, a następnie oficjalnego zatrudnienia nas do wykonania tego zadania. Jeśli nie będziemy mieć przynajmniej jednej pozytywnej opinii w serwisie, trudno będzie znaleźć w nim pracę. Kiedy jednak pracodawcy zobaczą, że z powodzeniem wykonaliśmy przynajmniej jedno zlecenie, znalezienie następnych stanie się łatwiejsze, gdyż zyskamy jakąś wiarygodność.

a9c8a50d59776690f264c310a5514dcc a

235

Przygotowania do rozmowy kwalifikacyjnej

Przygotowania do rozmowy kwalifikacyjnej Kiedy już nabierzesz nieco doświadczenia, bądź to zajmując się projektami typu open source, bądź też pracując na własny rachunek, będziesz mógł pomyśleć o udaniu się na rozmowę kwalifikacyjną. Moje doświadczenia pokazują, że najbardziej efektywnym sposobem znalezienia ofert pracy i umówienia się na rozmowę kwalifikacyjną jest skorzystanie z serwisu LinkedIn. Jeśli nie masz konta w tym serwisie, utwórz je, by rozpocząć poszukiwanie i nawiązywanie kontaktów z potencjalnymi pracodawcami. Na początku profilu napisz krótkie podsumowanie na swój temat, pamiętając przy tym, by podkreślić swoje umiejętności zawodowe. Wiele osób na początku profilu umieszcza informację typu: „Języki programowania: Python, JavaScript”; co ułatwia rekruterom wyszukiwanie potencjalnych kandydatów na podstawie słów kluczowych. Pamiętaj także, by jako ostatnie zatrudnienie podać nazwę projektu open source lub własnego, jakim się ostatnio zajmowałeś. Po przygotowaniu profilu zacznij nawiązywać kontakty z osobami zajmującymi się poszukiwaniem programistów — w serwisie LinkedIn znajdziesz ich bardzo wiele. Takie osoby zawsze poszukują nowych talentów i chętnie dołączą Ciebie do swojej sieci zawodowej. Gdy zaakceptują Twoje zaproszenie, zapytaj ich o dostępne oferty pracy.

Rozmowa kwalifikacyjna Jeśli rekruter uzna, że nadajesz się do oferty pracy, jaką dysponuje, wyśle Ci wiadomość z propozycją umówienia się na wstępną rozmowę kwalifikacyjną. Rozmowa ta zostanie przeprowadzona przez rekrutera, więc zazwyczaj nie ma charakteru technicznego, choć zdarzało mi się, że rekruterzy już podczas pierwszych rozmów kwalifikacyjnych zadawali pytania techniczne, których nauczyli się na pamięć. Rozmowa zazwyczaj dotyczy znanych Ci technologii, doświadczeń zawodowych i między innymi ma na celu próbę określenia, czy będziesz pasować do charakteru i kultury danej firmy. Jeśli pójdzie dobrze, przejdziesz do dalszego etapu — rozmowy kwalifikacyjnej o charakterze technicznym, która będzie prowadzona z pracownikami zespołu programistów danej firmy. Zadadzą Ci te same pytania, na które odpowiadałeś podczas pierwszej rozmowy, jednak tym razem zostaną one uzupełnione o techniczny test przeprowadzony przez telefon. Przez telefon zostanie podany adres witryny, na której został opublikowany test, który będziesz musiał rozwiązać. Jeśli uda Ci się przejść ten drugi etap rekrutacji, zazwyczaj będzie także trzeci — przeprowadzany osobiście w danej firmie. Podobnie jak podczas dwóch pierwszych etapów, także tym razem spotkasz się z różnymi pracownikami firmy. Będą pytać o umiejętności oraz doświadczenia, a także przeprowadzą dodatkowe testy techniczne. Czasami kandydat zostaje także na lunchu, by przekonać się, jak wyglądają jego interakcje zespołem. Jeśli dana firma przeprowadza testy przy tablicy, zostaniesz poproszony o rozwiązanie kilku problemów programistycznych. Sugeruję kupienie białej tablicy i poćwiczenie, gdyż rozwiązywanie zadań programistycznych na takiej tablicy jest znacznie trudniejsze niż na komputerze.

a9c8a50d59776690f264c310a5514dcc a

236

Rozdział 24



Pierwsza praca w charakterze programisty

Jak radzić sobie na rozmowie Większość rozmów kwalifikacyjnych koncentruje się na dwóch zagadnieniach — strukturach danych oraz algorytmach. Dokładnie wiesz, co masz zrobić, aby pomyślnie przejść rozmowę kwalifikacyjną na stanowisko programistyczne — musisz być bardzo dobry w tych dwóch dziedzinach informatyki. Na szczęście, nauczenie się ich sprawi, że będziesz lepszym programistą. Można dodatkowo zawęzić zakres pytań, na których należy się skoncentrować, myśląc o rozmowie kwalifikacyjnej z pozycji pracownika firmy, który będzie ją przeprowadzał. Pomyśl, w jakiej sytuacji znajduje się ta osoba; ona zawsze twierdzi, że oprogramowanie nigdy nie jest gotowe i to prawda. Twój rozmówca zapewne ma dużo pracy i nie chce poświęcać zbyt wiele czasu na rozmowy z kandydatami. Czy zatem będzie mu się chciało wymyślać wyszukane pytania? Pewnie nie. Takie osoby zapewne poszukają w internecie hasła „pytania na rozmowę kwalifikacyjną na stanowisko programisty” i zadadzą jedno z kilku pierwszych, które znajdą. Ta sytuacja sprawia, że na rozmowach kwalifikacyjnych wciąż pojawiają się te same pytania, a w internecie są dostępne wspaniałe strony, z pomocą których można się na takie pytania przygotować. Gorąco polecam witrynę LeetCode — znalazłem na niej każde z pytań, które zadawano mi na wszystkich rozmowach kwalifikacyjnych, jakie odbyłem.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

25 Praca w zespole

„Nie można utworzyć wspaniałego oprogramowania bez doskonałego zespołu, a większość zespołów programistycznych można porównać do rodzin dysfunkcyjnych”. — Jim McCarthy

Jeśli ktoś jest samoukiem, będzie przyzwyczajony do programowania w samotności. Kiedy jednak zatrudnimy się w jakiejś firmie, będziemy musieli pracować w zespole. Jeśli nawet sami założymy firmę, wcześniej czy później pojawi się konieczność zatrudnienia dodatkowych programistów i w tym momencie także będziemy musieli nauczyć się pracy w zespole. Programowanie jest sportem zespołowym i — podobnie jak w każdym innym sporcie zespołowym — musimy współdziałać ze współpracownikami. Ten rozdział zawiera kilka wskazówek dotyczących tego, jak pomyślnie pracować w środowisku zespołowym.

Opanowanie podstaw Kiedy znajdziesz zatrudnienie w jakiejś firmie, jej zespół będzie oczekiwać od Ciebie kompetencji w zakresie tematyki opisywanej w tej książce. Nie wystarczy, że ją przeczytasz — musisz także opanować poruszane w niej zagadnienia. Współpracownicy będą się denerwować, kiedy bezustannie będziesz prosił o wyjaśnianie podstawowych zagadnień. 237

a9c8a50d59776690f264c310a5514dcc a

238

Rozdział 25



Praca w zespole

Nie pytaj o to, co możesz znaleźć w internecie Kiedy jako samouk staniesz się nowym członkiem zespołu programistów, będziesz musiał się wiele nauczyć i zadawać wiele pytań. Zadawanie pytań to doskonały sposób nauki, trzeba się jednak upewnić, że będą to właściwe pytania. Zadawaj pytania wyłącznie wtedy, gdy spędziłeś co najmniej 5 minut na poszukiwaniu odpowiedzi w internecie. Jeśli będziesz zadawał zbyt dużo pytań, na które bez trudu możesz sam znaleźć odpowiedzi, jedynie zdenerwujesz swoich współpracowników.

Modyfikowanie kodu Czytając tę książkę, udowodniłeś, że jesteś typem osoby, która bezustannie chce się doskonalić. Niestety, nie wszyscy współpracownicy należący do zespołu będą wykazywali równie duży entuzjazm w dążeniu do stania się lepszymi programistami. Wielu programistów nie odczuwa potrzeby ani chęci do dalszej nauki — wystarcza im robienie wszystkiego w sposób daleki od doskonałości. Zły kod szczególnie często występuje w start-upach, w których szybkie oddanie kodu jest niejednokrotnie ważniejsze od napisania kodu wysokiej jakości. Jeśli znajdziesz się w takiej sytuacji, nie zwracaj na to uwagi. Modyfikacja kodu napisanego przez kogoś innego bardzo często może zranić jego ego. Co więcej, jeśli będziesz poświęcać zbyt dużo czasu na poprawianie kodu napisanego przez innych, zabraknie Ci go na czynny udział w nowych projektach, co może sprawić wrażenie, że nie pracujesz dostatecznie ciężko. Najlepszym sposobem, by uniknąć trafienia do takiego środowiska, jest szczegółowe wypytanie firmy, do której aplikujesz, o stosowaną w niej kulturę programowania. Jeśli mimo to znajdziesz się w podobnej sytuacji, powinieneś skorzystać z rady Edwarda Yourdona: „Jeśli Twoje szefostwo wydaje się nie wiedzieć, co robią pracownicy, bądź jeśli Twoja firma wypuszcza kod kiepskiej jakości, który powoduje u Ciebie zażenowanie, odejdź”.

Syndrom oszusta Każdy, kto pracuje jako programista, kiedyś poczuje się przytłoczony. Niezależnie od tego, jak ciężko będziemy pracować, zawsze znajdą się rzeczy, których nie będziemy wiedzieć. Programistom samoukom szczególnie łatwo będzie odczuć niekompetencję, kiedy ktoś zada pytanie dotyczące czegoś, o czym nigdy wcześniej się nie słyszało, bądź też odnieść wrażenie, że w informatyce jest jeszcze tak wiele pojęć i zagadnień, których się nie rozumie. To zdarza się wszystkim, nie tylko Tobie. Byłem zaskoczony, kiedy mój znajomy z dyplomem informatyki obronionym na Uniwersytecie Stanforda stwierdził, że także on czuje dokładnie to samo. Stwierdził, że każdy biorący udział w jego programie borykał się z syndromem oszusta. Zauważył przy tym, że ludzie reagują na tę sytuację na dwa sposoby: podchodzą do niej z pokorą, zgadzają się przyznać, że nie wiedzą wszystkiego, i pracują, by poszerzyć swoją wiedzę, bądź też udają, że wiedzą wszystko (choć tak nie jest) i odrzucają dalszą naukę. Pamiętaj, że dojdziesz do czegoś wyłącznie dzięki ciężkiej pracy i nie ma niczego złego w tym, że czegoś nie będziesz wiedzieć — nikt nie wie wszystkiego. Trzeba zachować pokorę i wytrwale studiować wszystko, czego się nie rozumie — dzięki temu nikt Cię nie powstrzyma.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

26 Dalsza lektura

„Najlepsi programiści nie są tylko trochę lepsi od dobrych programistów. Są od nich lepsi o cały rząd wielkości i to pod każdym względem: koncepcyjnej kreatywności, szybkości, doskonałości projektu oraz umiejętności rozwiązywania problemów”. — Randall E. Stross

Artykuł Davida Byttowa pod tytułem „ABC: Always Be Coding” zawiera doskonałą poradę dotyczącą tego, jak zdobyć pracę programisty. Sam tytuł mówi wszystko: zawsze programuj. Artykuł ten można znaleźć na stronie https://medium.com/always-be-coding/abc-always-be-coding-d5f8051afce2. Jeśli połączysz zasadę ABC z kolejną — ABL, Always Be Learning (czyli zawsze się ucz) — na pewno czeka Cię świetna kariera. W tym rozdziale przedstawię kilka źródeł informacji dotyczących programowania, które uważam za przydatne.

Klasyka Jest kilka książek z dziedziny programowania, które po prostu trzeba przeczytać. The Pragmatic Programmer Andy’ego Hunta i Dave’a Thomasa; Design Patterns napisana przez Ericha Gammę, Johna Vlissidesa, Ralpha Johnsona i Richarda Helma (wzorce projektowe to bardzo ważne zagadnienie, którego jednak nie mogłem przedstawić w tej książce); Code Complete autorstwa 239

a9c8a50d59776690f264c310a5514dcc a

240

Rozdział 26



Dalsza lektura

Steve’a McConnella; Compilers: Principles, Techniques, and Tools napisana przez Alfreda Aho, Jeffreya Ullmana, Monice S. Lam i Ravi Sethi oraz Introduction to Algorithms wydana przez MIT Press. Gorąco polecam także książkę Problem Solving with Data Structures and Algorithms, która stanowi dostępne bezpłatnie, interaktywne, doskonałe wprowadzenie do algorytmów, przygotowane przez Bradleya N. Millera oraz Davida L. Ranuma, dodatkowo jest znacznie łatwiejsza do zrozumienia od publikacji MIT Introduction to Algorithms.

Kursy internetowe Kolejnym sposobem na podniesienie swoich umiejętności programistycznych jest korzystanie z kursów internetowych. Przykłady takich zajęć można znaleźć na stronie https://theselftaughtprogrammer.io/ courses.

Hacker News Hacker News to platforma do prezentowania doniesień pisanych przez użytkowników, dostępna na witrynie inkubatora technologicznego Y Combinator, na stronie https://news.ycombinator.com/. Ułatwia ona bycie na bieżąco z najnowszymi trendami i technologiami.

a9c8a50d59776690f264c310a5514dcc a

Rozdział

27 Dalsze kroki

„Kochaj sztukę, której się nauczyłeś, i w niej znajdź spokój.” — Marek Aureliusz

Przede wszystkim dziękuję za kupienie tej książki. Mam nadzieję, że dzięki niej staniesz się lepszym programistą. Właśnie ją skończyłeś, zatem nadszedł czas, by wziąć się do pracy. Czym zajmiesz się teraz? Będą to algorytmy i struktury danych. Wejdź na witrynę LeetCode i poćwicz te algorytmy. Później ponownie je poćwicz! W tym rozdziale zamieszczę kilka ostatnich uwag dotyczących tego, jak mogą wyglądać Twoje dalsze próby doskonalenia jako programisty (kiedy już skończysz ćwiczenie algorytmów).

Poszukaj mentora Mentor pomoże podnieść Twoje umiejetności programistyczne na następny, wyższy poziom. Jednym z największych problemów z samodzielną nauką programowania jest to, że można tak wiele robić w sposób nieoptymalny, nic o tym nie wiedząc. Jak już wspominałem, można walczyć z tym problemem, oddając swój kod innym do oceny. Mentor także może przeglądać Twój kod razem z Tobą, aby usprawnić i poprawić Twój proces pisania programów, zarekomendować odpowiednie książki i nauczyć pojęć programistycznych, których nie rozumiesz.

241

a9c8a50d59776690f264c310a5514dcc a

242

Rozdział 27



Dalsze kroki

Skacz na głęboką wodę W programowaniu jest stosowane pojęcie „czarnej skrzynki”; odnosi się ono do czegoś, czego używamy, choć nie rozumiemy zasad jego działania. Kiedy zaczynasz programować, wszystko jest taką czarną skrzynką. Jednym z najlepszych sposobów, by stać się lepszym programistą, jest otwieranie każdej z takich czarnych skrzynek, które napotkasz, i podejmowanie prób zrozumienia ich działania. Jeden ze znajomych powiedział mi, że jednym z największych momentów „olśnienia” była chwila, gdy zrozumiał, że wiersz poleceń także jest programem. Otwieranie takich czarnych skrzynek jest czymś, co określam „skakaniem na głęboką wodę”. Takim skakaniem na głęboką wodę było dla mnie napisanie tej książki. Napotkałem pewne pojęcia programistyczne, o których myślałem, że je rozumiem, lecz później okazało się, że nie potrafię ich wyjaśnić. Musiałem więc skoczyć na głęboką wodę. Nie ograniczaj się do uzyskania tylko jednej odpowiedzi, przeczytaj wszystkie wyjaśnienia na dany temat, jakie tylko uda Ci się znaleźć. Zadawaj pytania i czytaj różne opinie dostępne w internecie. Innym sposobem skakania na głęboką wodę jest samodzielne tworzenie rzeczy, które chcesz lepiej zrozumieć. Masz problemy ze zrozumieniem kontroli wersji? W wolnym czasie sam zaimplementuj prosty system kontroli wersji. Poświęcenie czasu na utworzenie takiego projektu będzie opłacalną inwestycją — pozwoli lepiej zrozumieć zagadnienie czy problem, z którymi się borykasz.

Kolejna rada Kiedyś na forum natrafiłem na dyskusję poświęconą różnym sposobom pozwalającym stać się lepszym programistą. Zaskoczyła mnie najwyżej oceniona odpowiedź „Rób coś innego niż programowanie”. Przyznaję, że to racja: czytanie książek, takich jak The Talent Code Daniela Coyle’a, sprawiło, że stałem się lepszym programistą, gdyż dokładnie określił w niej, czego potrzeba, by doskonale opanować dowolną umiejętność. Zwracaj zatem uwagę na rzeczy inne niż programowanie, które można wykorzystać w sferze programowania. Ostatnią radą, jakiej Ci udzielę, jest zalecenie, by czytać jak najwięcej kodu pisanego przez innych programistów. To najlepszy sposób, by poprawić swoje umiejętności. Kiedy się uczysz, staraj się także zachować równowagę pomiędzy pisaniem i czytaniem kodu. Czytanie cudzego kodu będzie początkowo trudne, jednak jest bardzo ważne, gdyż od innych programistów można się ogromnie wiele nauczyć. Mam nadzieję, że podobało Ci się czytanie tej książki w równie dużym stopniu jak mnie jej pisanie. Zapraszam do kontaktu w dowolnej sprawie — napisz do mnie na adres [email protected]. Prowadzę także biuletyn, który możesz subskrybować, jest dostępny na stronie http://theselftaughtprogrammer.io/, oraz grupę na Facebooku, https://www.facebook.com/groups/selftaughtprogrammers, za pośrednictwem której możesz się skontaktować ze mną oraz z innymi osobami uczącymi się programowania. Jeśli podobała Ci

a9c8a50d59776690f264c310a5514dcc a

243

Kolejna rada

się ta książka, rozważ napisanie recenzji w serwisie Amazon, na stronie https://www.amazon.com/ dp/B01M01YDQA#customerReviews, dzięki temu — być może — trafi ona w ręce większej liczby osób, a ja będę zadowolony z każdej otrzymanej opinii. I życzę powodzenia w dalszej programistycznej podróży.

a9c8a50d59776690f264c310a5514dcc a

244

Rozdział 27

a9c8a50d59776690f264c310a5514dcc



Dalsze kroki

a

0 Podziękowania

Chciałbym podziękować każdemu, kto pomógł mi w napisaniu tej książki. Moi rodzice, Abby i James Althoffowie, byli bardzo pomocni podczas całego procesu. Ojciec przeczytał każdą stronę książki i przekazał mi niezwykle cenne opinie. Nie zrealizowałbym tego projektu bez niego. Moja dziewczyna, Lauren Wordell, znosiła mnie cierpliwie podczas całej pracy nad książką. Chciałem bardzo podziękować mojemu niezwykle utalentowanemu ilustratorowi Blake’owi Bowersowi, redaktorom: Stevowi Bushowi, Madeline Luce, Pam Walatce oraz Lawrencowi Sanfilippo oraz przyjacielowi Antoinowi Sindu — kilka naszych rozmów trafiło do tej książki. Chciałem także podziękować Randee Fenner, która wsparła ten projekt na Kickstarterze i przedstawiła mnie Pam. Ogromne podziękowania przekazuję także mojemu byłemu szefowi Anzarowi Afaqowi, który okazał się niezwykle pomocny, kiedy dołączyłem do jego zespołu w eBay. Dziękuję także bardzo wszystkim tym czytelnikom, którzy jako pierwsi przeczytali książkę i podzielili się ze mną swoimi opiniami na jej temat. I w końcu dziękuję także wszystkim osobom, które wsparły tę książkę na Kickstarterze, a zwłaszcza Jin Chun, Sunny Lee oraz Leightowi Forrestowi. Dziękuję Wam bardzo!

245

a9c8a50d59776690f264c310a5514dcc a

246

Podziękowania

a9c8a50d59776690f264c310a5514dcc a

Skorowidz A

E

abstrahowanie, 138, 143 administrator, 170 algorytmy, 215 iteracyjne, 221 rekurencyjne, 219, 221 wyszukiwanie, 216, 221 anagram, 218, 221

efekt uboczny, 133 epoka, 214

F FIFO, First In First Out, 211 FizzBuzz, 20, 215 flagi, 166 framework internetowy, 186 funkcja range, 99 funkcje, 53, 67, 227 definiowanie, 55 parametry opcjonalne, 60 parametry wymagane, 60 wbudowane, 57, 68 wielokrotne stosowanie, 59 wywoływanie, 67

B Bash, 161, 170 baza kodu, 197 bezpieczeństwo, 230 błąd, 36 składniowy, 50

D debugger, 231 definiowanie funkcji, 55 dekrementacja, 34, 50 dodawanie do poczekalni, 197 dopasowywanie cyfr, 178 początku i końca, 176 różnych znaków, 177 DRY, Don’t Repeat Yourself, 226, 231 drzewo, 164 dziedziczenie, 140, 143

G Google Wiadomości, 201 gra w wojnę, 151, 156 Wisielec, 116 graficzny interfejs użytkownika, GUI, 20

247

a9c8a50d59776690f264c310a5514dcc a

248

Skorowidz

H

K

Hacker News, 240 hermetyzacja, 135, 143 HTML, 200, 204

karty do gry, 151 katalog roboczy, 170 klasy, 133 bazowe, 143 pochodne, 143 klauzula, 47, 50 klient, 143 klucz, 81 kod, 25 HTML, 200 produkcyjny, 225, 231 kolejka, queue, 211 kolejność operacji, 50 komentarze, 28, 49 kompozycja, 142, 143 konkatencja, 85 kontener, 69 krotka, 73 lista, 70 modyfikowalny, 81 niezmienny, 81 słownik, 75 w kontenerze, 79 kontrola wersji, 187 konwencja, 67 krotki, 73 kursy internetowe, 240

I IDE, 228 IDLE, 23, 228 importowanie modułów, 107 indeks, 84 końcowy, 93 początkowy, 93 ujemny, 93 inkrementacja, 34, 50 instalowanie, 22 instancja klasy, 129, 133 instrukcja, 47, 50 break, 100, 104 continue, 101, 104 elif, 45, 50 else, 44 if, 44, 50 if-else, 50 with, 114 instrukcje proste, 50 warunkowe, 43, 50 złożone, 50 interaktywna powłoka, 23 interfejs wiersza poleceń, 161, 170 iterowanie, 104

L liczba całkowita, 49 zmiennoprzecinkowa, 49 LIFO, Last In First Out, 208 listy, 70, 81

J jajko wielkanocne, 182 język Python, 22 języki programowania niskiego poziomu, 25 wysokiego poziomu, 25

a9c8a50d59776690f264c310a5514dcc a

249

Skorowidz

Ł

N

łańcuch dokumentujący, 66, 68 łańcuchy znaków, 49 dodawanie znaków, 88 dzielenie, 87 formatowanie, 86 indeksy, 84 konkatencja, 85 odwracanie, 210 potrójne, 84 powielanie, 86 usuwanie odstępów, 89 wyszukiwanie, 90 zabezpieczanie, 90 zastępowanie, 89 zmiana wielkości liter, 86 znajdowanie indeksu, 89

nagłówek, 51 najlepsze praktyki programistyczne, 225 narzędzia programistyczne, 159 numer rewizji, 197

O obiekty, 31, 49 iterowalne, 81 pliku, 114 typu bool, 49 typu NoneType, 49 obsługa wyjątków, 64, 68 odczyt, 114 odkładanie, 214 odstępy, 31 operand, 50 operatory przypisania, 33 arytmetyczne, 37, 50 logiczne, 41, 50 porównania, 40, 50 oprogramowanie typu open source, 20

M menedżer pakietów, 183, 186 apt-get, 186 pip, 186 mentor, 241 metadane, 186 metoda, 69, 81, 129, 133 in, 90 index, 89 join, 88 replace, 89 split, 87 strip, 89 upper, 86 metody magiczne, 130, 133, 147 prywatne, 137, 150 moduł wbudowany, 105, 108 modyfikowanie kodu, 238

P pakiety, 183, 186 palindrom, 217, 221 para klucz-wartość, 75, 82 paradygmat programowania, 125, 133 funkcyjnego, 127 obiektowego, 128 parametry, 54 opcjonalne, 60, 68 wymagane, 60, 68 parsowanie, 204 pętla, 104 for, 95, 104 while, 99, 104

a9c8a50d59776690f264c310a5514dcc a

250

Skorowidz

pętle nieskończone, 104 wewnętrzne, 104 zagnieżdżone, 102 zewnętrzne, 104 pliki automatyczne zamykanie, 111 CSV, 112, 114 odczytywanie danych, 111 ukryte, 167 zapisywanie danych, 109 polecenie, 163 git diff, 195 grep, 175 pip, 184 polimorfizm, 138, 143 pomoc, 122 potoki, 168, 170 powierzchnia ataku, 232 powtórzenia, 179 praca programisty, 233 w zespole, 237 produkcja, 231 program IDLE, 23 pip, 184 programista, 233 programowanie, 21, 25 funkcyjne, 127, 133 obiektowe, 123, 128, 133, 135 abstrahowanie, 138 dziedziczenie, 140 hermetyzacja, 135 kompozycja, 142 polimorfizm, 138 proceduralne, 126, 133 prostopadłość, 226, 231 przeglądanie kodu, 230, 232 przesłanianie metod, 141, 143 przypadek bazowy, 221 przywracanie wersji, 194 pseudokod, 50 Python, 22

R rejestracja, 229, 231 rekurencja, 219, 221 repozytorium, 188, 197 centralne, 197 lokalne, 197 rewizja, 192, 197 rozmowa kwalifikacyjna, 235 rozpoczynanie projektu, 189

S separator, 114 serwer, 20 składnia, 36, 50 słowniki, 75, 81 słowo kluczowe, 30, 49 is, 149 stałe, 33, 49 stan, 133 globalny, 125, 133 stos, stack, 208 struktura danych, 197, 207 kolejka, 211 stos, 208 katalogów, 164 sterująca, 50 syndrom oszusta, 238 system kontroli wersji Git, 187 SVN, 187 operacyjny, 20

Ś ścieżki pliku, 114 bezwzględne, 164, 170 względne, 164, 170 środowisko wirtualne, 186

a9c8a50d59776690f264c310a5514dcc a

251

Skorowidz

proste dopasowania, 175 zachłanne, 182 znaki zwyczajne, 181 wyrażenie, 50 wyszukiwanie sekwencyjne, 216, 221 wyświetlanie, 29 wywoływanie, 67

T talia kart, 153 terminal, 163 test FizzBuzz, 215 testowanie, 229, 231 tworzenie instancji klasy, 130, 133 wycinka, 91, 93 typ danych, 31, 49 Bool, 49 Float, 49 Int, 49 NoneType, 49 Str, 49 typy danych operator przypisania, 50

Z zabezpieczanie, 93 zależności, 186 zapis, 114 zapisywanie programów, 24 zasada DRY, 226 zasięg, 68 globalny, 61, 68 lokalny, 61, 68 zatwierdzanie, 197 plików, 192 zdejmowanie, 214 zdobywanie doświadczenia, 234 zestaw, 51 zintegrowane środowisko programistyczne, IDE, 228 zliczanie wystąpień liter, 218 zmiana wielkości liter, 86 zmienne, 33, 50 globalne, 61, 68 indeksowe, 104 instancyjne, 129, 133, 145, 150 klasowe, 145, 150 prywatne, 137, 150 publiczne, 137, 150 środowiskowe, 168, 170 znacznik HTML, 204 znajdowanie indeksu, 89 znak, 32, 49 nowego wiersza, 91 znaki specjalne, 90

U uprawnienia, 170 uruchamianie programów, 25 użytkownicy, 169 używanie zmiennych, 67

W wartość, 82 wciąganie, 190, 194, 197 Web scraper, 204 wiersz poleceń, 161, 170 wiersze, 30 wycinki, 91 wyjątki, 36, 50, 64 wypychanie, 190, 197 wyrażenia regularne, 173 dopasowywanie cyfr, 178 dopasowywanie początku i końca, 176 dopasowywanie różnych znaków, 177 narzędzia, 182 niezachłanne, 182 powtórzenia, 179

a9c8a50d59776690f264c310a5514dcc a

a9c8a50d59776690f264c310a5514dcc a