Spis treści
- Wprowadzenia – o zawartości podręcznika
- Myśleć jak programista Pythona
- Python jako kalkulator
- Przetwarzanie niewielkich ilości danych
- Python jako język zapisu algorytmów
- Jak radzić sobie ze złożonością
- Operacje na listach i łańcuchach znaków
- Definiowanie i używanie funkcji
- Świat obiektów
- Przetwarzanie danych
- Pisanie niezawodnego kodu
- Przepływ danych i sterowania
- Styl programowania
- Kilka ważnych kwestii na zakończenie…
Wprowadzenia – o zawartości podręcznika
Ponieważ niniejszy podręcznik ma służyć nauce programowania – język Python poznajemy niejako przy okazji. Osobom zainteresowanym systematycznym wykładem tego języka możemy polecić dostępne w języku polskim opracowania:
- https://pl.wikibooks.org/wiki/Zanurkuj_w_Pythonie – otwarty podręcznik Pythona
- https://pl.python.org/docs/tut/tut.html - Przewodnik po języku Python autorstwa Guido van Rossum
Licencja
Podręcznik jest udostępniany na licencji Creative Commons – zwanej w poniższym objaśnieniu warunków wykorzystania „Licencją”. Podręcznik określa się jako „Dzieło”. Użytkownik (czytelnik) korzystając z podręcznika automatycznie akceptuje warunki Licencji – stając się w ten sposób Licencjobiorcą.
Zgodnie z Licencją:
Licencjobiorca ma obowiązek zachować w stanie nienaruszonym wszelkie oznaczenia związane z prawno-autorską ochroną Dzieła oraz zapewnić, stosownie do możliwości używanego nośnika lub środka przekazu oznaczenie:
- imienia i nazwiska (lub pseudonimu, odpowiednio) Twórców, jeżeli zostały one dołączone do Utworu, oraz (lub) nazwę innych podmiotów jeżeli Twórca oraz (lub) Licencjodawca wskażą w oznaczeniach związanych z prawno-autorską ochroną Utworu, regulaminach lub w inny rozsądny sposób takie inne podmioty (np. sponsora, wydawcę, czasopismo) celem ich wymienienia (“Osoby Wskazane”);
- tytułu Utworu, jeżeli został dołączony do Utworu;
- w rozsądnym zakresie URI, o ile istnieje, który Licencjodawca wskazał jako związany z Utworem, chyba że taki URI nie odnosi się do oznaczenia związanego z prawno-autorską ochroną Utworu lub do informacji o zasadach licencjonowania Utworu; oraz
- w przypadku Utworu Zależnego, oznaczenie wskazujące na wykorzystanie Utworu w Utworze Zależnym (np. “francuskie tłumaczenie Utworu Twórcy,” lub “scenariusz na podstawie Utworu Twórcy”).
Licencjobiorca może więc zgodnie z Licencją wykorzystywać i rozpowszechniać Dzieło pod warunkiem dołączenia kopii Licencji lub wskazania adresu strony internetowej (URI), pod którym znajduje się tekst Licencji do każdego egzemplarza Dzieła.
Licencjobiorca nie może oferować ani narzucać żadnych warunków lub ograniczeń nie uwzględnionych w Licencji. Nie może też udzielać sublicencji.
Niniejszy opis zawiera jedynie omówienie najważniejszych zapisów Licencji. Pełna jej treść jest dostępna na stronie: Creative Commons Attribution-ShareAlike 4.0 International License Polskie tłumaczenie (do wersji 3.0): https://creativecommons.org/licenses/by-sa/3.0/pl/legalcode.
Wykorzystane materiały i opracowania
Podstawą opracowania jest polskie tłumaczenie podręcznika „A Byte of Python” C.H. Swaroopa, udostępniony na licencji Creative Commons. Niniejszy podręcznik udostępniamy na tej samej licencji. Jest więc on w świetle prawa „Utworem pochodnym” wobec wspomnianej książki (choć poza wykorzystanymi materiałami zawarto w nim wiele oryginalnych treści). Informacje o wykorzystanym utworze - zgodnie z warunkami licencji:
Autor: Swaroop C H http://www.swaroopch.com/about/
Tytuł: „A Byte of Python”
Strona internetowa (URI):
- Podręcznik na gitbook: > http://python.swaroopch.com,
- Polskie tłumaczenie: > http://python.edu.pl/byteofpython/
Oryginalny podręcznik może być pobrany z powyższych lokalizacji.
Zgodnie z przyjętą licencją, Licencjodawca może zastrzec, by wszystkie zmiany zostały zaznaczone i opisane. Tego zastrzeżenia nie C H Swaroop jednak nie uczynił. Biorąc pod uwag fakt, że niniejszy podręcznik nie jest wiernym tłumaczeniem oryginału, który posłużył jedynie za inspirację – zrezygnowano ze wspomnianych oznaczeń.
Przy opracowaniu niniejszego podręcznika celem nadrzędnym było uzyskanie jasnego i spójnego opisu sztuki programowania, niż wierność przekładu. Dlatego też zmieniono w stosunku do oryginału układ treści – przesuwając opisy mechanizmów trudniejszych i mniej uniwersalnych na sam koniec.
Przy opracowaniu rozdziału 7 (Deginiowanie i używanie funkcji) oraz trzech następnych wykorzystano wcześniejsze tłumaczenie podręcznika „Byte of Python” dostępne na stronie:
http://python.edu.pl/byteofpython/2.x/08.html. Nie jest to jednak proste powielenie treści, ale opracowanie z uwzględnieniem zawartości pozostałych rozdziałów podręcznika.
Kod programów
Równolegle opracowywany jest internetowy portal PyIDE, który jest dostepny na licencji GNU. Portal ten obsługuje wspomnianą wyżej stronę http://python.otwartaedukacja.pl/. Wydawca książki: Fundacja Galicea, Wydawnictwo Verbu http://verbu.pl/.
Język angielski
Podręcznik jest tworzony w języku polskim. Jednak zdecydowano się stosować identyfikatory zmiennych bazujące na języku angielskim. Te kilkadziesiąt słów „współczesnej łaciny” łatwo opanować nawet komuś, kto nie zna języka angielskiego. Zastosowanie takiego rozwiązania ułatwi natomiast integracje z międzynarodowym środowiskiem programistów Pythona.
Z angielskiego pochodzą także tak zwane ‘słowa kluczowe’ - używane do zaznaczenia elementów struktury programu. Nie należy używać tych słów jako identyfikatorów. Pomimo, że Python rozróżnia duże i mal litery i teoretycznie identyfikator For nie będzie się mylił ze słowem kluczowym for – mogłoby to prowadzić do dodatkowych błędów w razie pomyłki.
Poniższa tabelka zawiera spis słów kluczowych:
False | class | finally | is | raise |
None | continue | for | lambda | return |
True | def | from | not | try |
and | del | global | or | while |
as | elif | if | pass | with |
assert | else | import | yield | |
break | except | in |
Myśleć jak programista Pythona
Ucząc się języka obcego musimy opanować podstawy gramatyki i słownictwo. Jednak opanowanie tego języka wymaga czegoś więcej. Jedni to nazywają duchem języka, inni myśleniem lub czuciem w obcym języku. Generalnie chodzi o opanowanie trudnego do opisania stylu wyrażania myśli.
Podobnie jest z językami programowania. Rozwój informatyki sprawił, że styl myślenia w języku programowania wyznaczają trzy elementy:
- Abstrakcyjne środowisko (świat) którego funkcjonowanie jest opisywane (definiowane) przez programy.
- Gramatyka – czyli sposób formułowania wypowiedzi (programów).
- Mechanizmy i idee programowania dostępne w danym języku.
Środowisko
Środowisko Pythona jest obiektowe. Oznacza to, że każdy element danych może mieć własności i metody. Własność to dana zawarta w obiekcie. Na przykład własnością łańcucha znaków (string) jest reprezentowany przez ten obiekt napis. Metoda to sposób zmiany własności lub ich odczytywania. Na przykład łańcuch znaków zawiera funkcję zwracającą napis zmieniony na duże litery (upper). Metody to inaczej funkcje obiektu. Funkcje - czyli oznaczone nazwą fragmenty programów - zapisujemy w postaci: nazwa_funkcji(argument1, argment2,…)
Czyli po nazwie funkcji podajemy w nawiasach okrągłych argumenty (parametry) rozdzielone przecinkami.
Funkcja f1 będąca metodą obiektu obiekt1 może zostać wywołana po zapisaniu: obiekt1.f1()
Na przykład napis (łańcuch znaków) ma metody operowania na łańcuchach. W większości języków programowania chcąc zamienić znaki ‘b’ na ‘B’ w łańcuchu S napisalibyśmy coś w rodzaju: replace(S,’b’,’B’);
Czyli byłoby to polecenie operowania na łańcuchu znaków S przez zewnętrzną funkcję replace. W Pythonie łańcuch znaków jest obiektem, w którym zaimplementowano metody operowania na nim.
Przykład 1 (konsola):
Po uruchomieniu konsoli python (poleceniem python) wpisujemy (po znakach zachęty >>):
Wyjście:
Objaśnienie:
Łańcuch znaków (napis) oznaczamy cudzysłowami. Na przykład ‘123’ w programie oznacza napis 123 (można też stosować podwójny cudzysłów “). Ten napis jest obiektem, który posiada między innymi metodę zmiany replace z dwoma argumentami: co i na co ma być zmieniane. Zapis replace(‘2’,’a’) mówi, że każdą cyfrę 2 należy zamienić na literę a.
Konsola Pythona wyświetla wynik każdego działania – nie musimy więc dodatkowo używać polecenia wyświetlenia wyniku. Jednak gdybyśmy zapisali powyższe polecenie w pliku tekstowym – na przykład objects.py i wykonali powstały skrypt poleceniem: python objects.py – nie otrzymalibyśmy na ekranie żadnego wyniku. W programach trzeba podawać jawnie instrukcje wyświetlania (drukowania). Służy do tego funkcja print. Musimy więc zapisać:
Wyjście:
Objaśnienie:
Poza użyciem metody replace powyższy przykład pokazuje użycie własności obiektów. W tym wypadku chodzi o własność __class__, która określa typ przechowywanych danych. Przy okazji widać, że użycie kropki dziesiętnej zmienia tym z liczby całkowitej (int) na rzeczywistą (float).
Język Python jest językiem operowania na obiektach.
Ponieważ zaś w postaci obiektów może być przedstawione prawie wszystko – spotyka się często zastosowanie Pythona do implementacji wtyczek do różnych programów (na przykład graficznych Gimp i Inkscape). Nawet Microsoft w końcu się przełamał i po latach promowania wyłącznie własnych technologii sięgnął po Pythona.
Mechanizmy
Większość języków programowania powstało po to, by precyzyjnie formułować przepis postępowania (wykonywania obliczeń). Rozwój inżynierii oprogramowania sprawił, że wprowadzono do nich mechanizmy ułatwiające obsługę błędów, podział na moduły, wydzielanie wspólnych elementów (biblioteki) etc… Python jest czymś w rodzaju zbioru najlepszych praktyk w tym zakresie. Pomimo, że jest to język uniwersalny, dąży się w nim do opisania tego, co ma być zrobione, a nie w jaki sposób. W powyższym przykładzie zapisujemy, że mają być zamienione cyfry 2 na litery a. W komputerze musi to polecenie zostać zamienione na przepis postępowania w postaci instrukcji odczytywania i zapisu komórek pamięci. W programach używamy jednak tak ogólnych zapisów (definicji) jak to tylko możliwe.
Wyjście:
Objaśnienie:
Do zmiennej a wpisujemy listę liczb. Lista jest obiektem, który ma swoją metodę sortowania. Prawie każdy kurs programowania zawiera opis algorytmu (algorytmów) sortowania – czyli opis jak to zrobić. Jak widać w Pythonie to nie jest potrzebne. Wystarczy napisać co ma być zrobione.
Ostatni wiersz w powyższym przykładzie pokazuje, że należy odróżnić wynik działania funkcji (w tym wypadku metodę sort()) i wynik zwracany przez funkcję. Wywołanie a.sort() spowoduje posortowanie a, ale zwracany wynik jest pusty (None). Jeśli więc chcemy, aby w zmiennej b znalazły się posortowane liczby z a, to musimy napisać b=a;b.sort(), a nie b=a.sort().
Bogactwo języka sprawia, że jedna z anegdot opowiada jak opisać stworzenie świata w Pythonie:
Przykład:
Jak stworzyć świat?
Objaśnienie:
Czy to zadziała? Oczywiście! Pod warunkiem, że ktoś nam dostarczył implementacji – czyli moduł „przestrzen” z funkcją „stwarzanie” ;-).
Gdzie szukać takich implementacji? Zacząć trzeba na stronie http://pypi.python.org. Klikając w „Browse package” (przeglądanie pakietów) uzyskujemy dostęp do tysięcy różnych implementacji. Pakiety z tej strony instalujemy na własnym komputerze poleceniem pip.
1. Konwersję dat można znaleźć na stronie: https://pypi.python.org/pypi/convertdate/
2. Przygotowujemy własne środowisko – żeby nie zaśmiecać serwera, uzyskać odpowiednią wersję Pythona i możliwość instalacji z internetu bez uprawnień administratora:
3. Instalujemy convertdate:
4. Uruchamiamy konsolę Pythona:
5. Testujemy:
Wyjście:
Objaśnienie:
Zgodnie z kalendarzem hebrajskim, na dzień 1 stycznia pierwszego roku naszej ery przypadał 18 października 3751 roku (zob. strona https://pl.wikipedia.org/wiki/Kalendarz_żydowski).
Gramatyka
Aby ułatwić komputerowi analizowanie tego co napisaliśmy, wprowadza się wyróżnione słowa – zwane słowami kluczowymi i inne znaki którymi zaznaczamy wyróżnione fragmenty tekstu. Python wyróżnia się tym, że zamiast stosowania zwyczajowych nawiasów {} odróżnia bloki tekstu tak zwanymi wcięciami, czyli spacjami o lewej stronie.
Objaśnienie:
Do zmiennej cyfry wstawiamy zbiór (tak zwaną krotkę – po angielsku ‘tuple’) liczb od 0 do 9.
Instrukcja for nakazuje zrobienie czegoś dla wszystkich elementów (w tym wypadku wszystkich cyfr). Słowo kluczowe in (z angielskiego ‘w’) mówi właśnie skąd mamy brać elementy.
Oznaczenie %s w napisie wskazuje miejsce, w które należy wstawić wartość. Po znaku % podaje się wstawiane wartości.
Na przykład
spowoduje wyświetlenie
W wyniku powyższych instrukcji otrzymamy całą tabliczkę mnożenia.
Przy okazji poznajemy kolejny element Pythona: komentarze. To nie są fragmenty wykonywanego programu, ale tekst wpisany przez programistę – aby ułatwić czytanie programu.
Krótkie komentarze zaznacza się znakiem # na początku wiersza.
Ważna uwaga! Pomimo, że komentarze nie są wykonywane – są analizowane. Nie można w nich wpisywać czegokolwiek. W szczególności użyte znaki muszą być z dopuszczalnego alfabetu. Jakiego? To definiuje pierwszy wiersz programu:
Po słowie coding (kodowanie) podajemy standard znaków jaki chcemy użyć. Chcąc używać języka polskiego (literka ‘ż’ w komentarzu) – wybieramy standard utf-8.
[v1]
Python jako kalkulator
Nowoczesne kalkulatory pozwalają na wpisanie ciągu znaków zawierających wyrażenie i wykonanie wszystkich działań za jednym razem. Tak samo może działać konsola Pythona. Więcej na ten temat: https://pl.python.org/docs/tut/node5.html.
Podstawową różnicą jest to, że w wyrażeniach Pythona możemy używać zmiennych. Większość instrukcji programu zawiera wyrażenia. Prostym przykładem wyrazem jest 2 + 3 . Wyrażenie może być zbudowane z operatorów i argumentów.
Operatory to inaczej funkcje, które mogą być zapisane symbolami. Zamiast pisać mnożenie(1,2) zapisujemy 1*2. Kolejność działań może być narzucona przez nawiasy. Na przykład: (3+4)*3. Dane na których są wykonywane działania (argumenty funkcji) nazywamy argumentami lub operandami (https://pl.wikipedia.org/wiki/Operand).
Operatory
W Pythonie mamy dostępnych wiele różnych operatorów. Poniżej podano ich wykaz. Nie musisz wszystkich znać, by pisać programy. Pamiętaj, że zawsze możesz przetestować je używając interpretera Pythona (uruchamianego z konsoli poleceniem python). Na przykład, aby przetestować wyrażenie 2 + 3, należy w wierszu poleceń interpretera Pythona wpisać:
>>> 2 + 3
Oto przegląd dostępnych operatorów:
- + (Plus)
- Dodaje dwa obiekty
- 3 + 5 daje 8 . ‘a’ + ‘b’ daje ‘ab’ .
- (Minus)
- Odejmuje od pierwszego argumentu drugi argument (po > prawej stronie). Jeśli pierwszy argument jest nieobecny, > zakłada się, że jest zerem.
- -5.2 Daje liczbę ujemną, a 50 - 24 daje 26 .
- * (Mnożenie)
- Mnożenia dwóch liczb lub wielokrotne złączenie (powtórzenie) > łańcucha znaków.
- 2 * 3 daje 6 . ‘la’ * 3 daje ‘lalala’ .
- ** (Potęga)
- Zwraca x do potęgi y
- 3 ** 4 daje 81 (to jest 3 * 3 * 3 * 3 )
- / (Dzielenie)
- Podzielić x przez y
- 13 / 3 daje 4.333333333333333
- // (Dziel i zaokrąglenie w dół)
- Dzieli x przez y i zaokrągla wynik w dół do najbliższej liczby > całkowitej
- 13 // 3 daje 4
- -13 // 3 daje -5
- % (Modulo)
- Zwraca resztę z dzielenia
- 13 % 3 daje 1 . -25.5 % 2.25 daje 1.5 .
- << (Przesunięcie bitowe w lewo)
- Przesuwa bity liczby w lewo o ilość pozycji określoną > drugim argumentem. (Każda liczba jest reprezentowana w pamięci > przez bity lub cyfry binarne, czyli 0 i 1)
- 2 << 2 daje 8 . 2 jest w postaci binarnej jako 10.
- Przesunięcie w lewo o 2 bity daje 1000, które reprezentuje > liczbę dziesiętną 8 .
- >> (Przesunięcie bitowe w prawo)
- Przesuwa bity liczby w prawo o ilość pozycji określoną > drugim argumentem.
- 11 >> 1 daje 5 .
- 11 jest reprezentowane w postaci binarnej jako 1011. Po > przesunięciu o 1 bitw prawo zostaje liczba 101 która jest > zapisem wartości dziesiętnej 5 .
- & Bitowa koniunkacja
- Iloczyn liczona bit po bicie
- 5 & 3 daje 1 (101 & 011 = 001).
- | (Bitowa alternatywa)
- Alternatywa liczony bit po bicie:
- 5 | 3 daje 7 (101 | 011 = 111)
- ^ (Bit-owy XOR)
- Bitowe XOR liczb
- 5 ^ 3 daje 6 (101 | 011 = 110)
- ~ (Bit-owa negacja)
- Negacja logiczna x daje -(x + 1)
- \~5 daje -6 . Więcej szczegółów na > http://stackoverflow.com/a/11810203
- < (Mniejsze niż)
- Sprawdza czy x jest mniejsze od y. Wszystkie operatory > porównania zwracają True (prawda) lub False (fałsz). Uwaga – > wielkość liter w zapisie True i False ma znaczenie!
- 5 < 3 daje False i 3 < 5 daje True .
- Porównania mogą być dowolnie łączone: 3 < 5 < 7 daje > True .
- > (Większe niż)
- Sprawdza czy x jest większe od y
- 5 > 3 powraca True . Jeśli oba argumenty są liczbami, są > najpierw konwertowane do wspólnego typu. W przeciwnym razie > zawsze zwraca False .
- <= (Mniejszy lub równy)
- Sprawdza czy x jest mniejsza niż lub równa y
- x = 3; y = 6; x <= y zwraca True
- >= (Większy lub równy)
- Sprawdza czy x jest większe niż lub równe y
- x = 4; y = 3; x >= 3 zwraca True
- == (Równy)
- Sprawdza czy obiekty są równe
- x = 2; y = 2; x == y zwraca True
- x = ‘str’; y = ‘stR’; x == y zwraca False
- x = ‘str’; y = ‘str’; x == y zwraca True
- != (Nie równe)
- Sprawdza czy obiekty nie są równe
- x = 2; y = 3; x != y zwraca True
- not (logiczne NIE)
- Jeśli x jest True, to zwraca False . Jeśli x jest False, to > zwraca True .
- x = True; not x zwraca False .
- and (logiczne I)
- x and y zwraca False jeśli x jest False, inaczej zwraca wartość y
- x = False; y = True; x and y zwraca False, ponieważ x > jest fałszywe. W tym przypadku, Python nie sprawdza y, > ponieważ wiadomo, że lewa strona wyrażenia “i” jest False co > oznacza, że całe wyrażenie jest False niezależnie od > innych wartości. To się nazywa skróconą ewaluacją wyrażeń i > jest ważne – bo na przykład jeśli nawet y nie jest poprawnie > zdefiniowane, to nie pojawi się błąd. Z tego względu poprawnym > jest (1>2) and (1/0==0).
- or (logiczne LUB)
- Jeśli x jest True, to zwraca True, w przeciwnym wypadku zwraca wartość y
- x = True; y = False; x or y zwraca True . Skrócona ewaluacja
> wyrażeń ma zastosowanie także tutaj.
<span id=”RefHeading_Toc8762_1549000628” class=”anchor”><span id=”shortcut-for-math-operation-and-assignment” class=”anchor”></span></span>Łączenie przypisywania i operacji matematycznych
Gdy mamy wykonać działanie matematyczne na zmiennej a następnie przypisać wynik działania z powrotem do tej zmiennej, wykonujemy to następująco:
ten zapis można skrócić jako:
Ogólnie zamiast
zapisujemy
Kolejność wartościowania
Komputery działają sekwencyjnie. W szkole nas uczono, że w wyrażeniach takich jak 2 + 3 * 4, najpierw robimy mnożenie. Mnożenie ma bowiem wyższy priorytet. W języku Python również ustalono priorytety. Zaleca się jednak, aby nie polegać na tym, tylko stosować nawiasy (chodzi o jednoznaczność i czytelność zapisu). Poniższa tabelka zawiera priorytety operatorów:
Operator | Opis |
---|---|
lambda | Wyrażenie lambda |
or | Logiczne OR (lub) |
and | Logiczne AND (i) |
not x | Logiczne NOT (nie) |
in, not in | Testy przynależności |
is, is not | Testy tożsamości |
<, <=, >, >=, <>, !=, == | Porównania |
| | Bitowe OR (lub) |
^ | Bitowe XOR (różnica symetryczna) |
\& | Bitowe AND (i) |
<<, >> | Przesunięcia |
+, | Dodawanie i odejmowanie |
*, /, % | Mnożenie, dzielenie, reszta z dzielenia |
+x, x | Identyczność, negacja |
\~x | Bitowe NOT (nie) |
** | Potęgowanie |
x.atrybut | Odwołanie do atrybutu |
x[indeks] | Odwołanie do indeksu |
x[indeks:indeks] | Wykrojenie |
f(argumenty…) | Wywołanie funkcji |
(wyrażenia…) | Powiązanie lub drukowalna forma krotki |
[wyrażenia…] | Drukowalna forma listy |
{klucz:dana…} | Drukowalna forma słownika |
`wyrażenia…` | Konwersja napisowa |
Operatory o tym samym priorytecie są wymienione w tym samym wierszu w powyższej tabeli. Na przykład, + i -ma ten sam priorytet.
Zmiana kolejności działań
Aby uczynić wyrażenia bardziej czytelne, możemy użyć nawiasów. Na przykład, 2 + (3 * 4) jest zdecydowanie łatwiejsze do zrozumienia niż 2 + 3 * 4, co wymaga znajomości priorytetów operatorów. Jak ze wszystkim, nawiasy powinny być wykorzystywane racjonalnie – nie należy stosować ich w nadmiarze - jak w (2 + (3 * 4)) .
Podstawową funkcją nawiasów jest jednak modyfikacja kolejności działań. Na przykład, jeśli chcesz wykonać dodawanie przed mnożeniem, to można napisać: (2 + 3) * 4 .
*. Łączność
Operatory są zwykle stosowane z lewej do prawej. Oznacza to, że operatory o tym samym priorytecie są stosowane od lewej do prawej. Na przykład, 2 + 3 + 4 jest równoważny z (2 + 3) + 4 .
*. Wyrażenia
Pokażemy użycie prostych wyrażeń do wyliczenia powierzchni prostokąta.
Wyjście:
Ten przykład pokazuje kolejny problem z formatowaniem wyjścia. Skąd Python ma wiedzieć, że napis jest po polsku? Samo wskazanie kodowania (zob. # -*- coding: utf-8 -*- z przykładu 1.3) nie wystarczy. Przed łańcuchem musimy podać literkę u (od unicode). Przy okazji robimy formatowanie jak w przykładzie 1.3:
Wyjście:
Jak to działa
Długość i szerokość prostokąta są przechowywane w zmiennych odpowiednio length i breadth. Używamy ich do wyliczenia wyrażeń określających powierzchnię i obwód prostokąta. Wstawiamy do zmiennej area wynik wyrażenia length * breadth, aby następnie wydrukować go za pomocą funkcji print (druk). W tym przypadku obwodu możemy podać w funkcji drukowania bezpośrednio wyrażenie 2 * (length + breadth) – bez przechowywania wyniku w zmiennej. Jednak należy zwrócić uwagę na to, że operator % oczekuje dokładnie tylu elementów ile zaznaczono (%s) w formatowanym łańcuchu znaków. Dlatego albo należy ująć wyrażenie w nawiasy, albo zastosować zmienną (perimeter).
Podsumowanie
Poznaliśmy sposób konstruowania wyrażeń - podstawowych elementów w dowolnym programie. Dzięki temu konsola Pythona może być używana jako kalkulator.
Przetwarzanie niewielkich ilości danych
Większość działań jakie wykonują programy jest trywialnie prosta. Jednak zanim pojawiły się nowoczesne (obiektowe) programowania – nawet tak banalne zadania jak posortowanie danych, czy wybranie najmniejszego elementu wymagały pisania wielu linijek kodu i nastręczały wiele okazji do przykrych błędów. W tych czasach stosowano powszechnie metodologię programowania od ogółu do szczegółu. Weźmy prosty przykład: podzielić dzieci na kolonii na dwie równe grupy, biorąc pod uwagę wiek dzieci. Załóżmy że dysponujemy listą zawierającą imię i nazwisko oraz wiek każdego dziecka (zapisane wprost z Excela w formacie CSV).
Jak to zrobić?
1) wczytać_listę:
2) posortować_zbiór;
3) wybrać_element_środkowy;
Teraz każdy z tych elementów uzupełniamy o szczegóły. Na przykład przy wybieraniu środkowego elementu należy ustalić czy ilość elementów jest nieparzysta. Jeśli nie – musimy zdecydować, czy wziąć element o numerze n/2, czy też n/2+1 (gdzie n = ilość elementów). Dla wprawnego programisty jakieś pół godziny roboty.
Jak z tym zadaniem zmierzy się programista Pythona?
1. Poszuka w internecie hasła: Python+CSV. Na przykład: https://pythonprogramming.net/reading-csv-files-python-3/. Kopiuj i wklej + drobna modyfikacja i mamy program wczytujący listę zawierającą wiek dzieci.
2. Teraz pora na odszukanie elementu środkowego, czyli mediany. Na pewno trafimy na funkcję numpy.median(). I tu niespodzianka – nie musisz nic sortować!
A oto efekt 10 minut pracy (razem z szukaniem błędu wynikłego z przeoczenia konieczności konwersji danych):
Objaśnienie
1. Polecenia import powodują przyłączenie potrzebnego modułu. W tym wypadku numpy – obliczenia numeryczne oraz csv – aby odczytać dane.
2. Konstrukcja with open(lista.csv) as csvfile: \; readCSV = csv.reader(csvfile, delimiter=;) powoduje utworzenie obiektu zwracającego kolejne wiersze odczytane z pliku lista.csv. Można jej używać bez zagłębiania się w szczegóły (jedyna zmiana wobec standardu skopiowanego z internetu to nazwa pliku z danymi. Jednak dla kompletności wykładu należy dodać, że:
a. Tak zwany delimiter to nic innego jak znak rozdzielającego pola w pliku csv. Jeśli zechcemy zapisać dane z arkusza kalkulacyjnego do tego typu pliku – musimy zdecydować jaki to ma być znak. Jeśli wybierzemy średnik, to typowy wiersz pliku będzie wyglądał następująco: Natalia;Nowak;8
b. Słowo kluczowe with pojawiło się całkiem niedawno (w Pythonie 2.6) i znaczy ono jedynie tyle, że mamy czytać z pliku jeśli ten plik istnieje i uda się go otworzyć (bardziej dogłębne wyjaśnienie: http://users.uj.edu.pl/~ufkapano/algorytmy/lekcja07/with.html).
3. Instrukcja for – z którą już zetknęliśmy się (w przykładzie 1.3) wykonuje działania na wszystkich elementach. Zapis for row in readCSV: oznacza pobieranie z obiektu odczytującego dane (readCSV) kolejnych wierszy (od angielskiego row). Elementy tego wiersza są numerowane od 0. Mamy więc 0=>Imię, 1=> Nazwisko, 2=>wiek. Zapis row[2] oznacza zatem wiek.
4. Zmienna ages (od angielskiego age=wiek) zawiera listę liczb. Inicjuje się ją wstawiając pustą listę oznaczoną []. Dodawaniu do listy służy własność append (ages.append(7) dodaje liczbę 7 do listy). Przygotowując ten przykład
5. Autor nie wziął pod uwagę tego, że zmienna row[] zawiera kolejne elementy wiersza pliku w postaci napisów. Uzyskał więc nie listę liczb, ale listę łańcuchów znaków (liczb w postaci napisów). To spowodowało pojawienie się tajemniczego błędu zgłaszanego przez funkcję median. Aby tego błędu uniknąć trzeba było zastosować tak zwaną konwersję typów. Zapis int(9) oznacza liczbę całkowitą samo 9 to tylko napis. Nie można na przykład napisać w programie '9'-9. Można natomiast 9-int('9'). Usunięcie wspomnianego błędu zajęło mniej więcej tyle czasu, co napisanie programu. Warto zdawać sobie sprawę z tego, że tak właśnie wygląda praca programisty.
Powyższy przykład został opisany dość dokładnie. Warto jednak poświęcić trochę czasu na eksperymenty z nim. Jeśli ktoś dobrze go zrozumie – może śmiało uważać siebie za programistę ;-).
Instrukcja for
Instrukcja for (for..in
) jest zaliczana do tak zwanych pętli – czyli powtarzania działań tak długo, aż pojawią się warunki zakończenia pętli (na przykład nie ma więcej danych do przetworzenia). Chcąc przedstawić coś w miarę prostego w skomplikowany sposób, można napisać, że pętla for definiuje iteracje ** nad sekwencją danych. Nie ma jednak w tym nic tajemniczego lub trudnego. Chodzi wyłącznie o to, że kolejne dane są pobierane do przetwarzania w określonym porządku (w przykładzie 3.1 w kolejności zapisania w pliku), a działania zostaną wykonane na wszystkich danych (chyba, że nastąpi błąd przerywający program).
Jeśli ktoś zetknął się z programami w innych językach, to może mu się nasunąć w tym miejscu pytanie: a co zrobić, jeśli nie chcę wykonać instrukcji zdefiniowanych w pętli dla wszystkich danych, ale określoną ilość razy? Ale przecież to jest to samo! Po prostu dane jakie należy użyć w pętli (po słowie kluczowym in) są kolejnymi liczbami! W Pythonie zapisujemy sekwencję liczb w postaci: range(od,do). Na przykład range(1,5) oznacza 4 liczby kolejne od 1 do 4. Czemu do 4? Bo drugi parametr w range oznacza górne ograniczenie (czyli dostajemy w tym przypadku liczby mniejsze od 5).
Wyjście:
Objaśnienie
1. Wbudowana funkcja range działa jak generator kolejnych liczb (od 1 do 4). Możemy też wygenerować ciąg arytmetyczny inny niż złożony z kolejnych liczb. Funkcja range akceptuje bowiem trzeci parametr określający krok – co ile zwiększamy kolejne liczby. Na przykład, range(1,5,2)
daje tylko dwie liczby [1,3]
. Pamiętaj, że drugi parametr (zakres) określa górne ograniczenie, a nie ilość liczb!
2. Inną ważną kwestią jest to, że range()
jest generatorem zwracającym kolejne liczby pojedynczo, a nie wszystkie liczby naraz. Nie można więc zapisać: lista=range(1,5). Jeśli chcemy uzyskać listę pięciu kolejnych numerów, należy użyć konstrukcji lista=list(range(1,5)).
3. Funkcja range może być wywołana z jednym parametrem. Wtedy przyjmuje się, że dolne ograniczenie wynosi 0. Zapis list(range(5))
daje nam liestę [0, 1, 2, 3, 4].
4. Pętla for posiada element opcjonalny oznaczony słowem kluczowym else (inaczej). Do tego miejsca przechodzi program po wykonaniu pętli (lub jeśli nie ma nic do wykonania).
Struktury danych
We wcześniejszych przykładach zostały użyte bez specjalnego wprowadzenia listy danych. Są to przykłady struktur danych, które mogą być przechowywane w zmiennych. Zamiast jednej prostej danej (jak liczba czy napis) w jednej zmiennej może być wiele powiązanych danych. Poza listami istnieją w Pythonie trzy inne rodzaje struktur: krotka (inaczej kolekcja, rekord), słownik (inaczej tablica asocjacyjna) i zbiór. Struktury te różnią się sposobem w jaki możemy sięgać do elementów (danych) zawartych w nich.
Sposób zapisu oraz podstawowy sposób dostępu do danych zawiera poniższa tabelka:
Struktura | Identyfikator | Przykład | Typowe użycie |
---|---|---|---|
lista | list | Liczby = [1,4,6,3] | Operacje na listach danych (sortowanie, łączenie i dzielenie, modyfikacje) |
krotka | tuple | Czerwień = (255, 0, 0) | Rekord bazy danych, parametr. Krotek nie można modyfikować! |
słownik | dict | Numery = {‘raz’: 1, ‘dwa’: 2, ‘trzy’: 3} | Dostęp do danych poprzez nazwę. |
zbiór | set | set([‘Gniezno’, ‘Kraków’, ‘Warszawa’]) | Sprawdzenie czy jest w zbiorze |
Jak widać struktury różnią się w zapisie rodzajem użytych nawiasów (zbiory mogą być tworzone jedynie przez funkcję – a nie poprzez samo użycie nawiasów. Lepsze zrozumienie zastosowania tych struktur uzyskamy analizując poniższe przykłady.
Listy
Lista służy do przechowywania uporządkowanej kolekcji obiektów (danych). Przykładem takiej listy z którą na pewno każdy się zetknął jest lista zakupów. Praktycznie wszystkie dane można zapisać w postaci list. Inne struktury stosuje się dla wygody (na przykład aby ograniczyć bezsensowne próby modyfikacji). W Pythonie elementy listy dzieli się przecinkami. Całość obejmują nawiasy kwadratowe. Każdy element listy ma swój numer lista_dzieci[5] określa piąty element listy o nazwie lista_dzieci.
Wyjście:
Objaśnienie
1. Zmienna shoplist
jest listą zakupów (nazw rzeczy). Lista może być użyta jako źródło sekwencji danych w pętli for.
2. W Pythonie 3.x wprowadzono parametr end do funkcji print.
Zmienia on sposób zakończenia druku/wyświetlania danych. Zamiast normalnego przejścia do nowej linii, pojawia się znak określony w parametrze end. Wbrew pozorom różnice między podobnie wyglądającym zapisem w wywołaniu print() w Pythonie 2.x i 3.x są bardziej znaczące (w Pythonie 2.x print (1,2,3) oznacza wydruk krotki (1,2,3)). Zapis zastosowany w powyższym przykładzie jest uniwersalny (działa w każdej wersji Pythona)/
3. Gdyby funkcja print dopuszczała jedynie dwa parametry – można by skrócić zapis end=
do ` `. Jednak w print może być więcej parametrów i musimy wskazać przez nazwę do którego odnosi się nasze odwołanie.
4. Poza poznanymi we wcześniejszych przykładach metodami działania na liście (dodanie: append,
sortowanie sort
), użyto usuwania z listy poleceniem del.
Instrukcja del shoplist[0]
usuwa pierwszy element z listy (należy pamiętać, że Python rozpoczyna liczenie od 0). Wśród metod obiektu listy nie ma odpowiednika del. Do usuwania może służyć metoda remove. Wymaga ona jednak podania w parametrach usuwanego obiektu, a nie jego numeru. W tym wypadku: shoplist.remove(banany). Zewnętrzną funkcją jest też len() określającea długość listy.
Krotki
Krotki są używane do przechowywania razem kilku obiektów. Są one podobne do list, ale pozbawione części funkcjonalności (nie można ich modyfikować). Krotki są definiowane poprzez umieszczenie elementów rozdzielonych przecinkami w nawiasach okrągłych () - podczas gdy listy określa się nawiasami kwadratowymi.
Krotki są zwykle stosowane w przypadkach, gdy chcemy uniknąć możliwości zmieniania danych. Na przykład dane odczytane z bazy danych powinny być używane w niezmienionej formie.
Wyjście:
Objaśnienie
1. Zmienna zoo
zawiera elementy krotki. Widzimy, że funkcja len
podobnie jak w przypadku list zwraca ilość elementów. Podobnie jak listy – krotki mogą być użyte jako sekwencja przeglądana w pętli for.
2. Po likwidacji starego ZOO zwierzęta zostały przeniesione do oddzielnej lokalizacji w nowym ZOO. Zainicjowanie zmiennej new_zoo
twory nową krotkę zawierającą dwa nowe zwierzęta, a w trzecim elemencie krotki – dane o zwierzętach ze starego ZOO. Ta operacja wymaga kilku słów komentarza:
- Pominięto w niej nawiasy – gdyż one są przy tworzeniu krotek opcjonalne (niemniej zachęca się do ich stosowania) wyrażenie a=1,2,3,4 jest równoważne a=(1,2,3,4) – można się o tym przekonać wykonując druk (print) a lub a.__class__.
- Możliwość umieszczania struktury w strukturze pozwala tworzyć listy list (zamiast tablic wielowymiarowych) i inne złożone struktury. Funkcja len liczy ilość elementów struktury podanej w parametrze – a wewnętrzne struktury są traktowane jak jeden element. Dlatego len(new_zoo) zwraca 3 – choć ilość zwierząt jest większa.
3. Podobnie jak w przypadku list, możemy uzyskać dostęp do elementów w krotce określając pozycję elementu numerem podanym w parze nawiasów kwadratowych. Jest to tak zwany operator indeksowania ** Trzeci elementu w new_zoo
ma indeks 2 (liczymy od 0) i możemy odwołać się do niego pisząc new_zoo[2].
Trzecia pozycja w tym trzecim elemencie (zwierzęta ze starego ZOO) to new_zoo[2][2]
.
Każde dwa elementy rozdzielone przecinkiem (lub jedna zakończona przecinkiem) są traktowane jak krotka.
wynik:
Słowniki
Słownik można przyrównać do książki adresowej, gdzie można znaleźć adres lub dane teleadresowe osoby, znając tylko jego / jej imię i nazwisko. Słownik kojarzy klucze (nazwy) z wartościami (szczegóły). Stąd używana czasem nazwa ‘tablice asocjacyjne’ (od asocjacja = skojarzenie). Należy pamiętać, że klucz musi być unikalny. Gdyby rzeczywistą książkę adresową chcieć umieścić w takiej strukturze, to pojawi się problem z różnymi osobami o tym samym nazwisku. Nie będzie takiego problemu, jeśli posłużymy się numerem PESEL.
Klucze w słowniku są niezmienne. Zmieniać można natomiast wartości. Czyli klucze powinny być danymi prostymi (liczby, napisy). Słowniki zapisujemy w postaci par klucz:wartość, rozdzielonych przecinkami i ujętymi w nawiasy klamrowe. Na przykład: d = {k`lucz
1 : wartosc
1, klucz
2 : wartosc
2 }`.
Pary klucz-wartość w słowniku nie są uporządkowane w żaden sposób. Słownik nie może więc być przeglądany w pętli for (jako sekwencja użyta po słowie in). Jednak w obiekcie słownika mamy trzy metody zwane widokami, które zwracają odpowiednio listę par, listę kluczy i listę wartości: dict.keys(), dict.values(),dict.items(). Słownik {1:jeden,2:dwa} jest klasy dict, ale {1:jeden,2:dwa}.items() to już lista.
Objaśnienie
1. Tworzymy słownik ab
pomocą wcześniej omówionej notacji. Uzyskujemy dostęp do adresów na podstawie nazwiska – stosując prostą notację z nawiasami klamrowymi. Można też używać usuwania (del) – tak jak w listach.
2. W programie pokazano przeglądanie elementów słownika z wykorzystaniem metody items(). Zwraca ona po kolei pary klucz, wartość – jako elementy krotki.
3. Dodawanie i zmiana elementów słownika wygląda tak samo. Zapis: ab[Guido] = guido@python.org oznacza, że jeśli nie ma danych dla klucza ‘Guido’ zostanie dodany do słownika element z takim kluczem. Jeśli już w słowniku jest – nastąpi zmiana adresu.
4. Sprawdzenie, czy element o danym kluczu jest na liście, wykonujemy operatorem ** in** (zobacz zapis if Guido in ab:).
Zbiory
Zestawy to nieuporządkowan**a kolekcja prostych obiektów. Są one stosowane, gdy ważne jest tylko istnienie obiektu w kolekcji, lub ilość wystąpień. Można je kojarzyć ze zbiorami w matematyce. Mamy podobne operacje: sprawdzenie członkostwa, podzbiory, przecięcia itd….
bri = set([‘brazil’, ‘russia’, ‘india’]) print(‘india’ in bri) bric = bri.copy() bric.add(‘china’) print(‘BRI=’); print(bri) print(‘BRIc=’);print(bric) print(‘BRI podzbiorem BRIc?’) print(bric.issuperset(bri)) bri.remove(‘russia’) print(bric) print(bri & bric) bric.intersection(bri) print(bric)
Wyjście:
Objaśnienie
Przykład jest dość prosty, ponieważ wiąże się podstawami matematyki teorii mnogości nauczanych w szkole. Zwróć uwagę na możliwości umieszczania kilku instrukcji w jednym wierszu, rozdzielonych przecinkami.
Python jako język zapisu algorytmów
Na początku podręcznika podkreślono, że programując w Pythonie staramy się opisać co ma być zrobione, a nie w jaki sposób. Czasem jednak nie udaje się uniknąć implementowania algorytmów (czyli szczegółowych przepisów - jak coś zrobić). W takim przypadku operowanie na danych i użycie pętli for nie wystarczy. Konieczne jest użycie instrukcji sterowania oraz manipulowania danymi. Następny będzie poświęcony manipulowaniu danymi. W tym rozdziale poznamy natomiast bliżej instrukcje sterowania kolejnością wykonywanych instrukcji. Więcej informacji na ten temat znajdziesz w modułowym kursie programowania - z moduł Programy_a_algorytmy a zwłaszcza z podrozdział Implementacja_algorytmów.
Instrukcje programów w Pythonie są wykonywane sekwencyjnie – w kolejności zapisania. Jednak algorytmy które implementujemy w programach wymagają powtarzania pewnych operacji lub wykonania ich jedynie pod pewnymi warunkami. Powtarzanie fragmentu kodu zapisuje się instrukcją pętli a opcjonalne wykonanie fragmentu kodu - instrukcją warunkową.
Dotąd zetknęliśmy się z instrukcją pętli for – przy pomocy której zapisywane były programy przetwarzania struktur danych. W niniejszym rozdziale poznamy instrukcję warunkową ( if ) oraz instrukcję pętli ( while ). Pętla while różni się od pętli for przede wszystkim tym, że nie musimy mieć w chwili jej rozpoczęcia informacji o zbiorze danych jaki będzie przetwarzany. Warunkiem powtarzania pętli może być dowolne wyrażenie logiczne (tak zwany niezmiennik pętli).
Spośród spotykanych w innych językach instrukcji w Pythonie nie ma case oraz repeat.
Być może dla kogoś ułatwieniem w zrozumieniu działania instrukcji będzie zabawa z klockami, jaka jest dostępna na stronie: http://www.otwartaedukacja.pl/programowanie/bl/code/
Instrukcja warunkowa (if)
Instrukcja if służy do zaznaczenia bloku instrukcji, który się wykona wyłącznie wtedy, gdy prawdziwe będzie wyrażeni logiczne sprawdzane przed wejściem do tego bloku. Jeśli warunek jest spełniony, wykonywany jest blok instrukcji (możemy go określić jako “blok warunkowy”). w przeciwnym wypadku możemy wskazać inny blok instrukcji do wykonania. Służy do tego słowo kluczowe else. Użycie else jest opcjonalne.
Wyjście:
Objaśnienie
Program pozwala na wprowadzanie liczby od użytkownika (funkcją input() ) i sprawdzanie, czy zgadł on liczbę zapisana w pamięci zmienna number). Następnie w zależności od tego, czy wprowadzono liczbę zapamiętaną (23), mniejszą lub większą – wykona si inny blok instrukcji.
Funkcja wbudowana input drukuje na ekranie parametr i czeka na dane zakończone klawiszem Enter. Następnie input() zwraca wprowadzony ciąg znaków.
W programie ciąg znakow jest konwertowany do liczby (za pomocą int) i zapisywany do zmiennej guess (zgadywanie).
Sprawdzane wyrażenia logiczne to porównywanie liczb zapamiętanych w zmiennych numer i guess.
Bloki instrukcji są zaznaczane poprzez wcięcia (spacje z lewej).
Zauważ, że if sprawozdanie zawiera dwukropek na końcu - jesteśmy wskazując Pythonie, że blok sprawozdania następuje.
W programie użyto słowa kluczowego elif. Jest to skrót od dwóch słów: else-if i działa właśnie tak – jako ich połączenie. Dzięki temu zmniejsza się ilość wymaganych wcięć (porównaj ten sam tekst po zamianie elif na else: if ….).
Zarówno elif jak i else są opcjonalne. Minimalna struktura instrukcji if może wyglądać następująco:
Co najmniej jedna instrukcja w bloku warunkowym jest wymagana!!! Jeśli nic nie ma do zrobienia – użyj instrukcji pustej pass.
Pęlta while
Pętla while jest podobna do instrukcji warunkowej (if). W obu wypadkach blok instrukcji wykonuje się pod warunkiem, że podany warunek (wyrażenie logiczne) jest spełniony. Jedyna różnica polega na tym, że blok warunkowy w instrukcji if wykona się raz, gdy tymczasem blok pętli while wykonuje się tak długo, jak długo warunek jest spełniony.
Złóżmy, że mamy zmienną n w której aktualnie jest liczba 5. Porównajmy dwa przykłady:
1)
oraz:
2)
W przykładzie 1) zostanie wyświetlona liczba 5, a po zakończeniu instrukcji wartość n wynosi 4. W przypadku 2) zostaną wypisane liczby od 5 do 1, a po skończeniu instrukcji n wyniesie 0.
Zmodyfikujmy analogicznie przykład ze zgadywaniem liczb – zezwalając na wielokrotne odgadywanie poprzez zamianę if na while.
Wyjście:
Objaśnienie
Zabawa w zgadywanie prawie taka sama jak poprzednio – ale powtarzamy ją w pętli dopóki użytkownik nie zgadnie. Wskazuje na to zmienna logiczna running. To proste wyrażenie logiczne (z jednej zmiennej) jest zawsze prawdziwe w obrębie pętli. Dlatego nazywa się to niekiedy niezmiennikiem pętli. Należy zwrócić szczególną uwagę na to, że jeśli nie zmienimy tego wyrażenia – petla może nigdy się nie skończyć. Dlatego należy zwrócić szczególną uwagę na to, żeby niezależnie od tego jakie dane będą przetwarzane – nastąpiły zmiany powodujące wyjście z pętli. Jest to jdna z przyczyn dla których uzycie pętli for jest prostsze.
Blok else jest wykonywany, gdy while warunek (niezmiennik) pętli staje False - może to nastąpić nawet gdy warunek nie jest nigdy spełniony (wtedy działa to dokładnie jak if).
W Pythonie istnieje dwie dodatkowe instrukcje pozwalające sterowaniem pętli: continue i break (zob. dalej). Nie są one w pełni zgodne z ideą programowania strukturalnego – ale bywają wygodne. Nie należy ich nadużywać, ale warto znać.
Instrukcje break i continue
Instrukcja continue powoduje pominięcie reszty bloku instrukcji w pętli. Nie ma ona więc sensu, gdy występuje poza pętlą, lub jako ostatnia instrukcja w bloku. Po wykonaniu instrukcji Python przystępuje do wykonania następnej iteracji (powtórzenia) pętli. Może
Przykładowe wyjście
Objaśnienie
1. To gra w zgadywanie – podobna do pewnej gry karcianej. Ukryta jest pierwsza liczba punktów (points). Podajemy następne uważając, by nie przekroczyć 21. Liczby muszą być od 1 do 12. Dodatkowo 0 kończy grę(gdy grający boi się przegrać i przekroczyć 21). Instrukcja continue jest tu wykorzystana do tego, aby zignorować liczby nie spełniające warunków (0..12).
2. Należy pamiętać o tym, że continue działa także w pętli for.
Powyższy przykład zawiera dwa typowe elementy: ignorowanie błędnych danych w pętli je obrabiającej oraz wykorzystanie dodatkowej zmiennej dla zmiany wyrażenia pętli (niezmiennika).
Ten drugi element nieco zaciemnia czystość kodu. Gramy o to, by uzyskać sumę mniejszą od 21, ale nie przekroczyć tej liczby. Dobrze byłoby, aby warunek pętli odnosił się do rzeczywistości, a nie był sztuczny. Można do tego wykorzystać instrukcję break, która służy do przerwania pętli bez względu na to czy zostały spełnione warunki jej zakończenia. Program przechodzi do następnej instrukcji po pętli. Działa zarówno w pętli for jak i while.
Przykładowe wyjście:
Objaśnienie
Blok instrukcji w pętli zostaje przerwany, gdy uzyskamy dane czynią bezsensownym dalsze ich przetwarzanie. Może to dotyczyć danych istotnych tylko w tej iteracji – wtedy stosujemy continue (dane nie spełniają warunków), albo całej pętli (wychodzimy poza niezmiennik pętli) – wtedy przerywamy (break). K każdym razie – zarówna dla continue jak i break mamy pominięcie reszty bloku, ale break dodatkowo powoduje zakończenie całej pętli.
Pamiętaj, że break można stosować także w pętli for.
Podsumowanie
Poznane instrukcje wyczerpują schematy potrzebne do zapisania dowolnych algorytmów.
Jak radzić sobie ze złożonością
Wielkość programów liczonych w wierszach kodu rzadko jest mniejsza niż tysiąc. Czasami jest liczona w milionach. Dlatego umiejętność radzenia sobie z ich złożonością jest w pracy programisty ważniejsza niż opanowanie języka programowania. W tym rozdziale poznamy mechanizmy używane do budowania złożonych programów w języku Python.
Funkcje
W większości podręczników programowania funkcje definiuje się jako fragmenty kodu wielokrotnego stosowania. Na przykład w kodzie ( https://pl.wikipedia.org/wiki/Szyfr_Solitaire):
Powtarza się wyliczenie wartości zmiennej szyfr. Możemy wydzielić ten fragment nadając mu jakąś nazwę (tu: szyfruj):
Słowo kluczowe def oznacza definicję funkcji – czyli właśnie tego powtarzalnego fragmentu programu. Po nagłówku zaczynającym się od def następuje blok kodu stanowiący tak zwane „ciało" funkcji. Interpreter Pythona napotykając napis szyfruj() odszukuje definicję funkcji i wykonuje blok ciała funkcji.
W takim prostym stosunkowo kodzie korzyści zastosowania funkcji nie są oczywiste - zwłaszcza, że rodzi ono dodatkowe problemy o których trzeba pamiętać.
Przykład powyższy nie jest w pełni poprawny. Zmienne są tworzone z chwilą ich użycia. Jeśli zmienna pojawi się w funkcji – to jest nazywana zmienną lokalną i nie istnieje po zakończeniu funkcji. Jeśli więc dopiszemy na końcu print(szyfr) to nie wydrukuje się wynik działania funkcji. Poniżej znajduje się wyjaśnienie pojęcia wyniku funkcji, dzięki któremu można ten program poprawić.
Na dodatek łatwo zmienić kod programu eliminując powtórzenia:
Ten sposób upraszczania kodu jest lepszy niż stosowanie funkcji. Podstawowym zastosowaniem funkcji nie jest wcale eliminacja powtórzeń, ale właśnie uczynienie kodu bardziej czytelnym. Jeśli algorytm szyfrowania jest złożony, albo chcemy zwrócić uwagę na to, że fragment kodu jest definicją szyfrowania – stosujemy funkcję – nawet gdy w danym momencie nie przewidujemy powtórzeń:
Określenie funkcji jako wydzielenie powtarzanego fragmentu kodu nie jest dobre jeszcze z jednego powodu: sugeruje, że możemy po prostu zrobić kopiuj i wklej powtarzalnego fragmentu. To nie jest do końca prawda. Funkcja powinna być kodem autonomicznym – czyli jak najmniej powiązanym z innymi fragmentami. Gdzie są używane w niej zmienne liczba, klucz i szyfr? W dalszych rozdziałach podręcznika poznamy działanie funkcji bardziej dogłębnie, ale na początek musimy przyswoić sobie pojęcie parametru i wyniku funkcji. Parametrem jest zmienna której wartość przekazywana jest w chwili wywołania (użycia) funkcji. Wynikiem jest wartość przekazywana na zewnątrz. Służy do tego polecenie return.
W powyższym przykładzie zdefiniowaliśmy parametry (liczba i klucz) – do tego właśnie służą nawiasy po nazwie funkcji. A co się stało ze zmienną zawierającą wynik (szyfr)? Proszę zwrócić uwagę na to, że powyżej zdefiniowaliśmy wynik jako wartość przekazywana na zewnątrz. A więc wartość zmiennej, a nie zmienną. Do jakiej zmiennej wstawić tą wartość definiujemy w chwili użycia (wywołania) funkcji:
Warto zwrócić uwagę na konwencję zastosowaną w nazwach. Nazwy zmiennych i parametrów określono rzeczownikami (opisują one co zmienna przechowuje), a funkcji czasownikiem (jakie działania należy wykonać na danych). Taka konwencja nie jest obowiązkowa i nie jest stosowana konsekwentnie – ale ułatwia ona czytanie kodu.
Autonomiczność funkcji wiąże się także z tym, że nazwy parametrów są istotne tylko wewnątrz funkcji. Nazwy przekazywanych zmiennych mogą być zupełnie inne. W miejsce zmiennych można zresztą używać w wywołaniu funkcji dowolnych wyrażeń. Przekazywana jest bowiem wartość, a nie zmienna. A oto kompletny przykład:
Objaśnienie
Pobrany ciąg znaków zostaje zamieniony na liczbę (w stosunku do wcześniejszego opisu zrezygnowaliśmy z liczb ujemnych). Następnie zostaje wywołana funkcja szyfruj. Parametr liczba przyjmuje wartość zmiennej wiadomosc, a parametr klucz przyjmuje wprost podaną wartość 89. Wynik działania funkcji zostaje zapisany do zmiennej szyfr.
Obiekty
Aby zapewnić poprawne działanie funkcji, należy posługiwać się tym samym kluczem do szyfrowania i deszyfracji. Nic prostszego – wystarczy wpisywać stale ten sam klucz w parametrze wywołania. A jeśli zechcemy zmienić klucz – to musimy szukać każdego wywołania. Możemy łatwo coś ominąć lub się pomylić. Istnieje jednak mechanizm grupujący w jednym miejscu dane i funkcje na nich działające. Nazywa się to programowaniem obiektowym. Dzięki niemu możemy parametry ustalić tylko raz – z chwilą definiowania obiektu. Obiektem jest właśnie zgrupowanie danych i funkcji na nich działających. Definicję obiektów nazywamy klasą i określamy słowem kluczowym class:
Funkcje zawarte w obiekcie (zdefiniowane dla klasy) nazywamy metodami (metoda zmiany danych). Jako pierwszy parametr jest do tych funkcji przekazywany obiekt na rzecz którego funkcja jest wykonywana.
* Wyjście:*
Objaśnienie
W tym przykładzie pokazano czym różni się klasa od obiektu. Klasa została zdefiniowana w pierwszych trzech linijkach. Później zostały utworzone dwa obiekty tej klasy. Następnie została ustawiona własność klasy. Zwrócić należy uwagę na to, że własności są tworzone dynamicznie - nie musimy tego zawierać w definicji klasy. Jednak dla przejrzystości kodu nie zaleca się takiego programowania.
Często własności incjuje się w funkcji specjalnej __init__ - nazywanej konstruktorem, która jest wykonywana z chwilą tworzenia obiektu.
Objaśnienie :
Utworzenie obiektu (wiersz szyfrant=TSzyfrant(89)) powoduje wywołanie konstruktora z parametrem 89. Takie nazwy rozpoczynające się i kończące od __ (dwóch podkreśleń) są w Pythonie stosowane do specjalnych identyfikatorów systemowych. Własnosć klucz nie musi być zapisana jawnie w kodzie - bo tworzy się ona dynamicznie - z chwilą wstawienia wartości (tu: w konstruktorze).
Moduły
Definicja klasy powinna być taka, aby jej użycie było jak najprostsze. Generujemy obiekt i wywołujemy odpowiednią metodę. To co jest w środku obiektu nie musi nas interesować. Skoro tak – to najlepiej umieścić te szczegóły gdzie indziej – w innym pliku. Tak tworzy się moduły. Moduł w Pythonie to plik tekstowy z nazwą o rozszerzeniu „.py".
Na naszym przykładzie – możemy stworzyć plik cipher.py z zawartością:
Aby wykorzystać moduł, importujemy z niego to, co potrzebujemy – instrukcją import:
Dziedziczenie
Siła programowania obiektowego polega nie tylko na ukrywaniu szczegółów. Ważna jest możliwość tworzenia obiektów pochodnych. Czyli obiektów dziedziczących wszystkie cechy obiektu wcześniej zdefiniowanego. Dziedziczenie zaznaczamy dodając nazwę klasy z której dziedziczymy w nawiasach. Zobaczmy to na przykładzie modułu szyfr:
Klasa TSolitaire dziedziczy z klasy TSzyfrant metody kodowania liczb i wykorzystuje je do kodowania łańcuchów znaków. A oto jak możemy to wykorzystać:
Pakiety
Moduły można umieszczać w podkatalogach (folderach), grupując tematycznie. Uzyskujemy w ten sposób dalsze uporządkowanie. Podkatalog zawierający plik o nazwie __init__.py (może być pusty) Python traktuje jak pakiet modułów. Import z modułu umieszczonego w pakiecie polega na poprzedzeniu nazwy moduły nazwą pakietu (podkatalogu) z kropką na końcu. Na przykład gdy umieścimy moduł szyfr w katalogu/pakiecie szyfrownia, to import będzie wyglądał następująco:
Nic innego w programie nie ulegnie zmianie.
Pakiety mogą być zagnieżdżone. Powiedzmy, że chcesz stworzyć pakiet o nazwie swiat zawierającą pakiety azja, afryka itd., zaś w nich na przykład indie czy madagaskar.
Oto, jak powinna wyglądać twoja struktura katalogów:
Aby użyć funkcji lub danej z modułu zawartego w pakiecie, wykonujemy import podając całą ścieżkę rozdzieloną kropkami.
Załóżmy, że mamy pakiet1 z modułami:
- modul11 zawierający funkcję fun11a i fun11b,
- modul2 zawierający funkcje fun12
- Oraz pakiet2 z modułem fun2.
Przykład ich użycia:
Główną rolą pliku __init__.py jest poinformowanie Pythona, że katalog zawiera pakiet modułów. Jednak plik ten nie musi być pusty. Możemy w nim zaimportować udostępniane elementy pakietu i dokonać zainicjowania zmiennych.
Jeśli na przykład w pakiet1.__init__.py wpiszemy:
To nasz testowy program możemy zmodyfikować następująco:
Dekoratory funkcji
Ponieważ funkcja jest w Pythonie obiektem, można dynamicznie zmieniać jej strukturę. Zostało to wykorzystane w mechanizmie zwanym dekoracją funkcji. Jest to stosunkowo nowy i dość zaawansowany sposób radzenia sobie ze złożonością. Dlatego w tym elementarnym podręczniku jedynie wspominamy o nim, podając jako przykład wykorzystania framework Flask ( http://flask.pocoo.org/). Prostą stronę internetową można w nim zaimplementować w kilku linijkach:
Poprzedzenie definicji funkcji zapisem @app.route("/") mówi, że w rzeczywistości zostanie wykonana metoda route z obiektu app, a “udekorowana” funkcja hello dostarczy jedynie części treści do wygenerowania strony.
[v3]
Operacje na listach i łańcuchach znaków
Listy to podstawowa struktura danych używana przy przetwarzaniu danych w Pythonie. Jeśli mamy do czynienia z danymi pobieranymi ze źródeł zewnętrznych (na przykład odczytywanych z pliku CSV), to najczęściej pojawiają się one w postaci krotek. Natomiast dane lokalne - wytwarzane i zmieniane w programie są albo danymi elementarnymi (liczby, wartości logiczne), łańcuchami znaków albo listami.
Sekwencje
Listy, krotki i łańcuchy znaków mają wiele cech wspólnych. Są one sekwencjami – czyli zawierają ciąg uszeregowanych elementów. Na sekwencjach można wykonywać operacje:
- testowanie członkostwa, czyli in a not in wyrażeniach;
- indeksowania, czyli odniesienie do elementu sekwencji poprzez wskazanie numeru; na przykład lista[5] wskazuje szósty element listy (liczymy od zera);
- przeglądania przy pomocy instrukcji for (for element in lista).
Dla tych trzech rodzajów sekwencji wymienione powyżej (listy, krotki i łańcuchy), dostępna jest dodatkowo operacja krojenia (slice)__, która pozwala nam uzyskać fragment sekwencji.
Sekwencję oznaczamy podając jej pierwszy element i pierwszy nie należący do sekwencji. Czyli sekwencja[1:3] zwraca fragment rozpoczynający się od elementu o numerze 1 (nie jest to pierwszy element, bo ten ma numer 0) a skończywszy na elemencie numer 2 (bo 3 oznacza pierwszy, który następuje po sekwencji.
Pominięcie zarówno dolnego jak i górnego ograniczenia ([:]) powoduje utworzenie kopii całej sekwencji.
Liczba ujemna oznacza końcowy element. O ile jednak sekwencja[1] oznacza drugi element (liczymy od zera), to sekwencja[-1] oznacza ostatni element.
Zobaczmy to na przykładach.
Przykład:
Wyjście:
Objaśnienie
Należy zwrócić uwagę na to, że pominięcie ograniczenia górnego lub dolnego sekwencji (na przykład [:3] nie ma dolnego ograniczenia) powoduje wzięcie sekwencji od początku lub do końca.
Wyjście:
Objaśnienie
Python numeruje sekwencje od 0 – dlatego shoplist[0] to pierwsza, a shoplist[3] – czwarta pozycja w shoplist.
Idenks może być też wartość ujemną, w tym przypadku pozycja jest obliczana od końca sekwencji. Dlatego shoplist[-1] odnosi się do ostatniego elementu w kolejności oraz shoplist[-2] pobiera przedostatni element w sekwencji.
Fragment shoplist[1:3] zwraca się fragment o sekwencji rozpoczynającej się od indeksu 1 (czyli drugiego elementu), a kończący na elemencie o indeksie 2, ale nie zawiera elementu o indeksie 3, który stanowi górne ograniczenie. Natomiast shoplist[:]zwraca kopię całej sekwencji.
Na przykład shoplist[:-1] powróci fragment sekwencji, która wyklucza ostatni element, ale zawiera całą resztę.
Referencje
Do czego może nam służyć kopia całej sekwencji ([:])? W języku Python działanie przypisania zmienna1=zmienna2 zależy od tego, co zawiera zmienna2. Jeśli są to dane proste (liczba, wartość logiczna), to w po tej operacji zmienna1 będzie zawierać kopię tych danych. Jeśli natomiast zmienna2 zawiera typ złożony, to wykonanie podstawienia powoduje, że zmienna odnosi się do tego samego obiektu! Oznacza to, że po dokonaniu podstawienia zmienne wskazują ten sam fragment pamięci komputera. Zawierają referencje do tego samego miejsca.
Wyjście:
Objaśnienie
Gdy usuwamy z referencji – zmiana jest dokonywana na wszystkich zmiennych wskazujących na to samo. Gdy usuwamy z kopii – oryginał nie jest zmieniany.
Łańcuchy znaków
A jak jest w przypadku łańcuchów znaków (napisów)? Gdy wpiszemy
to tworzymy kopię, czy przypisujemy do b tą samą referencję? Z punktu widzenia programisty to nie ma znaczenia, gdyż napisy nie mogą być modyfikowane (są „immutable"). Jak sobie zatem radzić z ich przetwarzaniem? Służą do tego funkcje, które przetwarzają łańcuch znaków i na wynik zwracają wynik tego przetworzenia. Łańcuch jest obiektem, a funkcje na nim działające są zdefiniowane dla klasy str. Pełną listę takich funkcji (metod obiektu) zwraca polecenie help(str). Najbardziej użyteczne pokazuje poniższy przykład:
Wyjście:
Objaśnienie
Przykład zawiera najważniejsze operacje na łańcuchach: sprawdzanie zawartości (find, startswith, in) i łączenia (join). Funkcja find zwraca -1 jeżeli tekst nie zawiera szukanego fragmentu. W przeciwnym przypadku zwraca indeks początku.
Zwykłe łączenie napisów można wykonać operatorem dodawania: "abc"+"aaa" to tyle co "abcaaa". Nie można natomiast dodawać do łańcuchów innych typów (nie następuje automatyczna konwersja na napis). Zapis "abc"+3 jest nielegalny. Ciekawą właściwością języka jest natomiast zapis "abc"*3, który jest poprawny i oznacza powielenie tekstu (daje wynik "abcabcabc").
Użytecznym sposobem przetwarzania tekstów może niekiedy być zamiana ich na listy i odwrotnie:
Czasem potrzebna jest też konwersja znaków na kody (cyfry za pomocą których jest kodowany napis w pamięci komputera) i odwrotnie.
Python akceptuje łańcuchy znaków zapisane w wielu liniach. W takim przypadku należy apostrof (' lub ") początkowy i końcowy powtórzyć trzykrotnie:
Jeśli w łańcuchu występuje apostrof ograniczający napis – należy poprzedzić go znakiem . Czyli "apostrof to znak \" " oznacza to samo co 'apostrof to znak " '.
Formatowanie napisów
W niniejszym podręczniku często występuje składanie (formatowanie) łańcucha znaków z wzorca i danych. Miejsca we wzorcu, w których mają zostać wstawione dane zaznaczmy znakami %s:
To jednak nie są jedyne możliwości.
Jeśli chcemy aby liczby miały odpowiednią ilosć cyfr i miejsce dziesiętne, możemy w miejsce %s użyć %f:
Więcej możliwości: https://pl.python.org/docs/lib/typesseq-strings.html
W powyższych przypadkach kolejność użycia zmiennych do wypełnienia wzorca wynika z ich kolejności w krotce (sekwencji). Możemy jednak użyć słownika, wskazując miejsce w formacie nazwami kluczy ujętymi w nawiasy okrągłe:
[v3]
Definiowanie i używanie funkcji
Poprzednie rozdziały zawierają elementarną wiedzę na temat programowania w języku Python. Jest ona wystarczająca do rozpoczęcia samodzielnego tworzenia programów. Każdy może samemu testować tą wiedzę na prostych przykładach – takich jak zawarto w treści podręcznika. W przykładach tych pominięto niektóre mniej istotne zagadnienia. Szczególnie dotyczy to podstawowego budulca programów, jakim są funkcje. Obecnie przyjrzymy się im bliżej.
Wprowadzenie
Samo określenie „funkcja” pochodzi z języka matematyki. Na przykład zapis sin(x) oznacza funkcję sinus z parametrem x. Możemy przyjąć, że za nazwą sin kryje się obliczenie wartości funkcji dla określonego parametru. Ta idea została w informatyce zaakceptowana i poszerzona. Funkcją nazywa się oznaczony nazwą fragment kodu, zwracający jakieś wyniki dla określonych parametrów.
Funkcje definiuje się używając słowa kluczowego def. Po nim następuje nazwa identyfikująca funkcję, następnie para nawiasów, które mogą zawierać kilka nazw zmiennych, a na końcu dwukropek. Poniżej zaczyna się blok instrukcji, które są częścią tej funkcji wykonywaną po wywołaniu (użyciu) funkcji.
Rezultat:
Objaśnienie:
Definiujemy funkcję powiedzAhoj używając składni opisanej wcześniej. Ta funkcja nie wymaga żadnych parametrów i w związku z tym nie ma żadnych nazw (zmiennych) zadeklarowanych w nawiasach. Funkcję możemy wywołać wielokrotnie (tu: dwukrotnie) – w różnych miejscach. Może to pomóc w unikaniu powielania tego samego lub podobnego (parametryzowalnego) kodu wielokrotnie.
Parametry funkcji
Funkcja może posiadać parametry, czyli wartości, które dostarcza się do niej. Parametry są podobne do zmiennych, różnią się jedynie tym, że pierwsze przypisywanie wartości (zainicjowanie) następuje podczas wywoływania funkcji. Inicjowanie to może następować poprzez podanie wartości (wyrażenia) lub zmiennej. Jeśli do parametru jest przekazywane wyrażenie (także łańcuch znaków), to zmiana parametru wewnątrz funkcji oczywiście nie rodzi żadnych skutków na zewnątrz. Jeśli przypomnimy sobie problem referencji z poprzedniego rozdziału, to możemy się domyślić co będzie, gdy jako parametr przekażemy zmienną zawierającą listę danych. Oczywiście zostanie utworzony parametr funkcjonujący tak jak zmienna, ale wskazujący na ten sam obszar pamięci, jak zmienna przekazywana do funkcji. Poniższy przykład powinien to wyjaśnić:
Wynik:
Objaśnienie
- Należy zwrócić uwagę na ostatnią linię wyniku. Pokazuje ona, że po wywołaniu funkcji zmianie uległa zawartość zmiennej lista. Inaczej niż w przypadku zmiennej a, która zawiera dane prostego typu.
- Zauważmy, że każda z powyższych funkcji została wykonana dwukrotnie. To pokazuje jak trafne jest użyte określenie „parametr”. Blok kodu wewnątrz funkcji jest wykonywany dla różnych wartości – czyli zostaje „sparametryzowany”.
Parametry i argumenty
Często stosuje się konwencję nazewnictwa, ograniczającą użycie określenia „parametr” do definicji funkcji. Wartość nadawana parametrom w chwili wywołania nazywa się „argumentami”. Zamiast więc mówić funkcja „sumuj1” została wywołana z parametrami 3 i 6 (co nie jest błędem, choć kogoś może razić) – mówimy, że funkcja ta została wywołana z argumentami 3 i 6.
Zobaczmy jeszcze jeden przykład, w którym wykorzystanie funkcji if sprawia, że dla różnych argumentów sterowanie wewnątrz funkcji przebiega nieco inaczej:
Wynik:
Objaśnienie:
Definiujemy funkcję wypiszMax, w której wypisujemy na ekran największą z liczb a i b za pomocą prostej kombinacji if…elif…else.
Podczas pierwszego uruchomienia wypiszMax dostarczamy bezpośrednio wartości do parametrów (czyli argumenty). Za drugim razem wywołujemy ją używając dwóch zmiennych. Linijka wypiszMax sprawia, że wartość argumentu x zostaje przekazana jako parametr a, a wartość argumentu y jako parametr b.
Wynik funkcji - wyrażenie return
Wyrażenia return używamy do wyjścia z funkcji. Możemy opcjonalnie zwrócić w tym momencie jakąś wartość.
Rezultat:
Objaśnienie:
Funkcja maximum wyszukuje maksymalną wartość spośród podanych. Używa w tym celu prostej konstrukcji if…else a następnie zwraca tę wartość dzięki poleceniu return. Zauważ, że użycie wyrażenia return bez wartości jest równoznaczne z użyciem return None. None to specjalny typ w Pythonie, który reprezentuje po prostu nic. Używa się tego na przykład, gdy się chce przekazać, że zmienna nie ma wartości. Każda funkcja domyślnie otrzymuje na końcu return None, chyba że napiszesz własne return. Możesz to sprawdzić chociażby w ten sposób:
Wyrażenie pass wskazuje na pusty blok wyrażeń.
Domyślne wartości parametrów
W Pythonie istnieje możliwość zdefiniowania części parametrów jako opcjonalnych. Wówczas musimy podać wartość domyślną, którą parametr przyjmuje, jeśli nie został podany odpowiadający mu argument przy wywołaniu funkcji. Domyślną wartość parametru definiuje się tak jak przypisywanie wartości zmiennej – z użyciem znaku = (przypisania) po którym następuje wyrażenie, jakiego wartość ma zostać przypisana parametrowi, gdy nie zdefiniowano tej wartości (argumentu) przy wywołaniu funkcji. Choć Python dopuszcza definiowania domyślnej wartości jako zmiennego wyrażenia (zależnego o zmiennych i funkcji), to nie należy z tej możliwości korzystać. Wartość domyślna parametru powinna być niezmienna.
Rezultat:
Objaśnienie:
Funkcja powiedz ma wypisywać podaną jej wiadomość określoną ilość razy. Jeżeli nie podamy wartości, napis zostanie wyświetlony domyślnie, czyli raz. Osiągnęliśmy to przez przypisanie domyślnej wartości 1 do parametru ile.
Przy pierwszym użyciu tej funkcji podaliśmy jedynie napis, który dzięki temu został wyświetlony jedynie raz. Za drugim razem, oprócz napisu dołożyliśmy argument 5, a zatem uzyskaliśmy 5 powtórzeń napisu.
Ważne!
Domyślne wartości mogą posiadać jedynie te parametry, które są na końcu listy. Innymi słowy, nie możesz nadać wartości domyślnej parametrowi, jeżeli po nim wystąpi taki, który nie będzie jej posiadał (idąc wzdłuż ciągu parametrów danej funkcji). Jest to spowodowane tym, że wartości są przypisywane według kolejności występowania parametrów. Na przykład możesz napisać def funkcja(a, b = 5), ale nie możesz napisać def funkcja(a = 5,b)!
Wywołanie funkcji z odwołaniem do parametrów poprzez nazwę
Przypisanie wartości argumentów poszczególnym parametrom następuje w kolejności od lewej do prawej. Jeżeli jednak w definicji funkcji podano domyślną wartość więcej niż jednego parametru – to możemy chcieć przekazać wartość tylko do jednego z nich i to niekoniecznie tego pierwszego z lewej.
Mówi się czasem, że są to argumenty ze słowem kluczowym - ale to określenie jest mylące, gdyż chodzi o nazwy definiowane przez programistę, a nie słowa kluczowe języka.
Rezultat:
Objaśnienie:
Ta funkcja posiada jeden parametr bez domyślnej wartości i dwa, które ją posiadają. Za pierwszym razem wpisujemy f(3, 7), dzięki czemu parametr a dostaje wartość 3, parametr b dostaje 7, a parametr c domyślną wartość 10. Za drugim razem wpisujemy f(25, c = 24), więc parametr a otrzymuje wartość 25 (zgodnie z kolejnością), zaś parametr c dostaje 24, dzięki słowu kluczowemu. Parametr b ma domyślną wartość 5.
Za trzecim razem wpisujemy f(c = 50, a = 100) używając jedynie słów kluczowych. Zauważ, że przypisujemy wartość najpierw parametrowi c, niezależnie od tego, że parametr a występuje wcześniej w deklaracji funkcji.
Zmienna ilość parametrów
Funkcje z domyślną wartością parametrów mogą być wywoływane z różną ilością argumentów. Jednak lista argumentów nie być dowolna – bo musi się zgadzać z listą zdefiniowanych parametrów. Istnieje jednak sposób definiowania funkcji o zupełnie dowolnej liście parametrów. Poprzedzenie nazwy parametru gwiazdką (*) oznacza dowolną ilość nie nazwanych argumentów, widocznych w postaci krotki. Dwie gwiazdki (**) oznaczają słownik z argumentami przekazanymi wraz z nazwą parametrów.
Wyjście:
Objaśnienie:
W pierwszym wywołaniu uzyskaliśmy krotkę trzech wartości. W drugim mamy argumenty z nazwą – więc zostały one umieszczone w strukturze słownika. Tylko pierwszy argument jest przekazany w formie krotki.
Zakres widoczności parametrów i zmiennych
Częstą przyczyną błędów w programach jest użycie zmiennej poza zakresem jej widoczności. W obrębie funkcji każda użyta zmienna jest z reguły lokalna – czyli nie jest ma nic wspólnego z ewentualnymi zmiennymi o takich samych nazwach, ale użytymi poza blokiem (ciałem) funkcji.
W funkcji można używać nazw zmiennych na kilka sposobów:
- Najczęściej są to nazwy parametrów.
- Poza parametrami można w bloku funkcji tworzyć nowe zmienne. Są one widoczne tylko w obrębie funkcji. Po zakończeniu wykonania funkcji przestają one istnieć. Tym bardziej nie ma znaczenia sama definicja funkcji.
- Istnieje jednak sposób na to, by wewnątrz funkcji uzyskać dostęp do zmiennych zdefiniowanych w głównym bloku funkcji. Służy temu opisane poniżej słowo kluczowe global.
- Lepszym (zdecydowanie) od użycia global sposobem dostępu do zmiennych spoza funkcji jest przekazywanie poprzez parametry struktur (na przykład list) i obiektów. Jeśli dokonamy zmian we wskazanym przez parametr obiekcie, to dokonane zmiany pozostaną po zakończeniu funkcji. Nazwy zmiennych i metod ze środka obiektu poprzedzamy nazwą tego obiektu (lub parametru wskazującego na ten obiekt) i kropką. Temu zagadnieniu zostanie poświęcony kolejny rozdział podręcznika.
- Identyfikatory pochodzące z innych modułów pojawiają się wskutek wykonania instrukcji import. Są one widoczne w całym module od chwili wykonania instrukcji import. A więc także wewnątrz funkcji zdefiniowanych w tym module!!
Zmienne lokalne
Oczywiście nazwy parametrów są widoczne tylko w obrębie funkcji. Użycie takiej samej nazwy poza funkcją nie prowadzi do konfliktów. To nie są te same nazwy / zmienne, choć są takie same. Pokazuje to poniższy prosty przykład. Jest rzeczą niezmiernie ważną, aby go dobrze zrozumieć.
Rezultat:
Objaśnienie:
Parametr x ona widoczny tylko wewnątrz funkcji. Na zewnątrz mamy inną zmienną o takiej samej nazwie. Z chwilą wywołania funkcji, wartość zmiennej x zostaje przepisana do parametru x. Zmiana tego parametru (nadanie mu wartości 2) nie powoduje żadnych skutków dla zawartości zmiennej x, której nadaliśmy wartość 50. Końcowe wywołanie print, pokazuje wartość x w bloku głównym i dowodzi, że nie została ona zmieniona.
Tak samo jak parametry są traktowane zmienne lokalne. Jeśli zmienimy nazwę parametru na inną (oczywiście musi to dotyczyć także pierwszego print w funkcji) – działanie programu nie zmieni się!
Przestrzenie nazw
W powyższym akapicie mówi się raz o zmiennych, a innym razem o nazwach zmiennych. To jak to w końcu jest? Chodzi o zakres widoczności identyfikatorów, czy zmiennych na jakie wskazują? Przyjęło się mówić o zakresach widoczności zmiennych lub parametrów. Każda zmienna ma swój zakres widoczności, czyli blok funkcji albo obiekt w została ta zmienna zdefiniowana (w Pythonie – przez pierwsze jej użycie / zainicjowanie). Ta tradycja nie jest jednak najlepsza, gdyż chyba bardziej zrozumiałe (a przede wszystkim bardziej uniwersalne) jest operowanie pojęciem przestrzeni nazw (miejsce „zakresu widoczności”).
Pojęcie przestrzeni nazw (ang. namespace) nie pochodzi od twórców języka Python, ale jest bardziej uniwersalne. To bardziej precyzyjne określenie kontekstu. Analogicznie jak w języku potocznym - to samo słowo może oznaczać co innego w różnych kontekstach. Zakłada się jednak, że w danej przestrzeni nazw użyte identyfikatory są niepowtarzalne. Natomiast w różnych przestrzeniach nazw mogą istnieć takie same identyfikatory, ale oznaczające niekoniecznie to samo.
Możemy zatem mówić o przestrzeni nazw klasy i obiektu a także funkcji. To tylko wygodne pojęcie określające jaki jest zasięg widoczności identyfikatorów. Nie kryją się za nim żadne specjalne mechanizmy języka Python.
Odwołanie do identyfikatorów z innej przestrzeni nazw jest czasem możliwe poprzez dodanie przedrostka z identyfikatorem tej przestrzeni – na przykład nazwy klasy lub obiektu (temu będzie poświęcony następny rozdział).
Używanie pojęcia przestrzeni nazw pozwala lepiej zrozumieć to, że należy odróżnić znaczenie identyfikatorów (dostępność i znaczenie w programie) od tego co oznaczają (zmienne, obiekty etc). Przestrzeń nazw w jakiej używamy identyfikatorów decyduje wyłącznie o tym, czy są one dostępne i sensowne. Natomiast dostęp do pamięci komputera z użyciem tych identyfikatorów to trochę inne i bardziej złożone zagadnienie. Programistę siłą rzeczy bardziej interesuje to, co przy użyciu nazwy może wykonać (na przykład zainicjować oznaczoną tą nazwą zmienną). Ale na przykład fakt, że po wyjściu z programu znikają zmienne lokalne nie wynika z tego, że opuszczamy przestrzeń nazw, ale z zasad zarządzania pamięcią w Pythonie. Jeśli zmienna wskazuje na strukturę (lista, obiekt), to jej zniknięcie nie musi się wiązać ze zniknięciem struktury. Struktura znika dopiero gdy znikną wszystkie zmienne na nią wskazujące! Widać więc, że nazwy żyją sobie (w swoich przestrzeniach nazw) a zmienne sobie (w swoich zakresach widoczności / istnienia) ;-).
Powyższa dygresja w zasadzie nie ma znaczenia dla zrozumienia mechanizmów Pythona. Jednak każdy aktywny programista na pewno spotka się nie raz z tym zagadnieniem – więc warto przy okazji je sobie przyswoić.
Użycie wyrażenia global
Jeżeli wewnątrz funkcji chcemy użyć zmiennej zdefiniowanej w głównym bloku programu (czyli poza funkcjami i klasami), możemy wskazać Pythonowi, że nazwa nie jest lokalna,ale globalna za pomocą słowa kluczowego global.
Załóżmy dla przykładu, że blok funkcji rozpoczyna się od instrukcji:
Gdyby nie było instrukcji ze słowem global – podstawienie do x jedynki spowodowałoby zainicjowanie zmiennej lokalnej (lub zmianę parametru x – jeśli taki zdefiniowano). Użycie global sprawia, że dokonujemy zmiany zmiennej globalnej.
Jednakże to nie jest najlepszym rozwiązaniem, ponieważ wtedy czytający program musi dla jego zrozumienia szukać zmiennej poza blokiem funkcji. Użycia wyrażenia ze słowem kluczowym global należy zatem unikać. Wiedza z tego podrozdziału powinna być więc przydatna ewentualnie do analizy cudzych, najczęściej niezbyt dobrze napisanych programów ;-).
Rezultat:
Objaśnienie:
Używamy wyrażenia global w celu zadeklarowania, że x jest zmienną globalną — w związku z tym, gdy przypisujemy jakąś wartość do x we wnętrzu funkcji, widać tę zmianę również wtedy, gdy używamy x w głównym bloku programu. Możesz określać więcej niż jedną zmienną globalną w tej samej linijce, na przykład global x, y, z.
Świat obiektów
W rozdziale dotyczącym radzenia sobie ze złożonością, przedstawiono obiekty jako jeden ze sposobów budowania programu z mniejszych części. Wyróżniliśmy definicję obiektu – czyli klasę, od samego obiektu – czyli „instancji” klasy (obiekt utworzony zgodnie z definicją). Korzystając ze stworzonej (samemu lub przez społeczność programistów) definicji klasy, można wykreować wiele obiektów – stosowanie do potrzeb. Obiekty te różnią się od siebie wartością własności (czyli zmiennych zawartych w obiekcie) i sposobem użycia. Zmienne wewnątrz obiektu nazywamy polami lub własnościami. Użycie obiektu polega na operowaniu na własnościach (odczyt, zmiana) oraz wywoływaniu funkcji zdefiniowanymi w klasie obiektu. Funkcje te – zwane metodami klasy - definiują możliwe zachowania obiektów danej klasy. Stosowanie obiektów pozwala na ukrycie szczegółów implementacji. Możemy traktować je jak „czarną skrzynkę”. Ich stosowanie nie wymaga wiedzy na temat tego co jest w środku. Nie wnikamy w to, jak funkcje (metody) obiektu zostały zaimplementowane. Ważne jest tylko to, że wywołanie takiej metody w określonym stanie daje określony efekt.
Analogicznie postrzegamy świat realny. Włączamy telewizor i nie zastanawiamy się w tym momencie jak to działa. Telewizor to taki obiekt z określonymi funkcjami dostępnymi przy użyciu pilota. Postrzeganie świata przez pryzmat obiektów okazuje się bardzo uniwersalne. Dlatego programowanie obiektowe jest intuicyjnie proste. Przynajmniej jeśli chodzi o użycie gotowych klas.
W tym rozdziale zajmiemy się jednak także nieco trudniejszą kwestią jaką jest definiowanie klas. Na początek dwa przykłady ilustrujące różnicę między klasą a obiektem. Pierwszy należałoby opatrzyć komentarzem „nie róbcie tego w domu”. Tak się po prostu nie powinno programować – tu robimy to wyłącznie dla odsłonięcia pewnych cech klas i obiektów.
Wyście:
Objaśnienie:
Choć przykład nie jest zbyt złożony, to nawet doświadczonym programistom może sprawić problem przewidzenie wyników na podstawie kodu programu. Klasa Sumator można służyć do pamiętania (we własności o nazwie wartosc) odległości pokonanej krokami długości „krok” (inna własność obiektu).
To czego nie powinno się robić – to stosowanie zmian w definicji klasy poza tą klasą. Gdzie potem w dużym kodzie szukać tych zmian? Nie powinniśmy więc tworzyć takich instrukcji:
Na dodatek widzimy, że pierwsza z powyższych linii okazała się w programie bez znaczenia. Jeśli ktoś dobrze pozna język Python to zrozumie dlaczego. Ale taka wiedza nie jest niezbędna dla rozpoczęcia zabawy w programowanie ;-). Tworząc obiekt nie wpisujemy do niego kopii definicji klasy. A więc wstawienie do własności o nazwie „wynik” liczby 2 ma takie samo znaczenie dla obu obiektów (sumator1 i sumator2). Nie ma zaś znaczenia to, że przed wygenerowaniem pierwszego obiektu zmieniliśmy własność dla klasy (bo ta zmiana została „przykryta”). Równie ciekawie i równie źle wygląda ostatnia linijka programu. Metody klasy zawsze są wywoływane dla jakiegoś obiektu (instancji klasy). Jeśli nie stosujemy notacji obiekt.metoda(), to musimy podać w pierwszym parametrze ten obiekt. Tylko po co? Na szczęście nie jest możliwe wywołanie metody klasy bez podania instancji: Sumator.dodaj(1). To tylko zaciemniłoby różnicę między obiektem i klasą.
Jeśli pomimo wyjaśnień powyższy przykład jest niezrozumiały, to nie ma się czym przejmować. Poniżej napiszemy go tak, jak się programować powinno. Obiekty mają przecież uprościć a nie zagmatwać kod programu.
Wyjście:
Objaśnienie:
Teraz wszystko powinno być intuicyjnie zrozumiałe. Zdefiniowaliśmy klasę Sumator2 i utworzyli dwa obiekty tej klasy, inicjując ich własności z chwilą tworzenia. Służy do tego specjalna funkcja zdefiniowana w klasie obiektu zwana konstruktorem (lub kreatorem). Nosi ona nazwę __init__ i jest wywoływana za każdym razem, gdy tworzymy obiekt. Zapis Sumator(2,2) oznacza: utwórz obiekt i wywołaj konstruktora __init__ z parametrami (2,2).
Ponieważ własności są inicjowane w konstruktorze – dwie pierwsze linijki definicji klasy (wynik = 0 i krok = 0) są zbędne. Pozostawiamy często takie inicjowanie wyłącznie dla jasności kodu (aby nie musieć analizować kodu konstruktora, aby ustalić jakie obiekt danej klasy ma własności.
Poprzedzając nazwę zmiennych słowem self wskazujemy, że chodzi o własność obiektu, a nie o zwykłą zmienną lub parametr. Także definicja każdej funkcji będącej metodą klasy powinna zawierać jako pierwszy parametr self - jest przez niego przekazywany obiekt na rzecz którego wywołujemy funkcję.
Mamy więc dwa magiczne momenty (dziejące się poza naszym kodem) na które należy zwrócić uwagę:
- z chwilą tworzenia obiektu wywoływany jest konstruktor __init__
- z chwilą wywołania metody dla danego obiektu, zostaje on przekazany przez dodatkowy parametr self (nazwa stosowana zwyczajowo – ale nie spotyka się raczej jej zmiany).
W naszym przykładzie nie ma w ogóle odniesień bezpośrednich do klasy obiektu! Taki styl programowania jest bardzo polecany w Pythonie.
Adres zwrotny
Wróćmy na chwilę do magicznego słówka „self”. Tak nazywamy parametr, którym metody klas różnią się od zwykłych funkcji. On jest zawsze umieszczany na początku listy parametrów. Ten parametr odnosi się do samego obiektu. Dzieje się tak bez dodatkowych zapisów w programie. Python robi to samemu.
Załóżmy, że masz klasę MojaKlasa oraz instancję tej klasy o nazwie mojobiekt. Gdy wywołujesz metodę tego obiektu jako mojobiekt.metoda(arg1, arg2), Python automatycznie tłumaczy to na MojaKlasa.metoda(mojobiekt, arg1, arg2). Właśnie dlatego musimy dodawać parametr self – który w tym wypadku przyjmuje wartość mojobiekt.
Oznacza to także, że nawet jeśli napiszesz metodę, która będzie wywoływana bez argumentów, to i tak musisz ją zdefiniować jeden parametr – self – wskazujący na obiekt. Z uwagi na to, że self wskazuje on miejsce (obiekt) wywołania metody – nazywa się go czasem adresem zwrotnym.
Uporządkujmy naszą wiedzę o obiektach
Zaczęliśmy od trudnego przykładu – pora więc na coś łatwiejszego. Obiekty to bardzo istotna rzecz, więc nawet jeśli wydają się zrozumiałe – warto analizując proste przykłady podsumować naszą wiedzę w tej materii.
Klasy
Oto najprostsza możliwa klasa:
Rezultat:
Objaśnienie: Nową klasę tworzymy za pomocą polecenia class i nazwy klasy. Potem następuje wcięty blok poleceń, które składają się na ciało klasy. W naszym przypadku blok ten jest pusty, co sygnalizujemy poleceniem pass.
Następnie tworzymy obiekt tej klasy przez użycie nazwy klasy i pary okrągłych nawiasów. Dla sprawdzenia potwierdzamy typ zmiennej przez wypisanie jej na ekran. Python mówi nam, że mamy instancję klasy Osoba w module __main__ (tak nazywa się automatycznie moduł, który jest wykonywany jako pierwszy / główny).
Zauważmy, że jest wypisywany także adres w pamięci komputera, gdzie ten obiekt jest przechowywany. Oczywiście adres ten będzie inny przy każdym uruchomieniu programu, ponieważ Python może przechowywać obiekty, gdzie tylko znajdzie na nie miejsce.
Metody obiektowe
Jak już była mowa, klas/obiekty mogą posiadać metody, które są niczym innym, jak funkcjami z dodatkowym argumentem self. Teraz zobaczymy przykład takiej metody.
Rezultat:
Objaśnienie:
Ten krótki przykład można także zapisać jako Osoba().przywitajSie()
W tym przykładzie widzimy adres zwrotny self w akcji. Zauważmy, że metoda przywitajSie nie wymaga żadnych argumentów, a mimo to w jej definicji widnieje self. Metoda __init__ Istnieje wiele nazw metod, które mają specjalne znaczenie dla klas w Pythonie. Najczęściej takie wbudowane funkcje specjalne mają identyfikatory zaczynające się i kończące podwójnymi podkreślnikami. Metoda __init__, która jest wywoływana w momencie, kiedy tworzony jest obiekt danej klasy. Jest ona przydatna, kiedy chcemy zainicjować obiekt w jakiś sposób.
Rezultat:
Objaśnienie:
Definiujemy metodę __init__ z parametrem imie (oprócz obowiązkowego self). Tworzymy w niej także pole o nazwie imie. Zauważmy, że są to dwie różne zmienne, mimo że mają tę samą nazwę. Użycie notacji z kropką umożliwia nam rozróżnienie ich od siebie.
Najważniejsze w tym przykładzie jest jednak to, że nie wywołujemy jawnie metody __init__, ale tylko podczas tworzenia nowego obiektu podajemy argumenty owej metody w nawiasach po nazwie klasy. Na tym właśnie polega specjalne znaczenie metody __init__.
Dzięki przeprowadzeniu inicjowania naszego obiektu, możemy używać pola self.imie w metodach tej klasy, jak zrobiono to w metodzie przywitajSie. Własności klas a przestrzenie nazw Pola (własności) obiektów, są zmiennymi mającymi sens jedynie w przestrzeni nazw danej klasy. Oznacza to, że możemy używać ich nazw tylko w kontekście danej klasy czy obiektu.
Wyjście
Objaśnienie
Zdefiniowaliśmy klasę Robot i wygenerowaliśmy dwa obiekty – roboty tej klasy. Nazwa robota jest własnością dotyczącą tylko obiektów opisujących roboty i poza przestrzenią nazw klasy Robot może nie być dostępna, lub oznaczać zupełnie coś innego.
Dziedziczenie
Jedną z najważniejszych korzyści wynikających z programowania obiektowego (nazywanego też obiektowo orientowanego) jest możliwość ponownego wykorzystania już raz napisanego kodu. Osiągamy to poprzez mechanizm dziedziczenia. Dziedziczenie można sobie łatwo wyobrazić jako tworzenie typów pochodnych od istniejących klas.
Załóżmy, że musisz napisać program, który będzie zbierał dane na temat wykładowców i studentów na uczelni. Mają oni pewne wspólne cechy, jak imię, wiek, czy adres. Mają także cechy specyficzne, jak pensja, szkolenia i urlopy dla wykładowców oraz oceny i czesne dla studentów.
Możemy oczywiście stworzyć dwie niezależne klasy dla każdego typu, ale wtedy wspomniane cechy wspólne muszą zostać zdefiniowane w obu tych klasach. Takie postępowanie szybko okaże się nieporęczne.
Możemy oczywiście stworzyć dwie niezależne klasy dla każdego typu, ale wtedy wspomniane cechy wspólne muszą zostać zdefiniowane w obu tych klasach. Takie postępowanie szybko okaże się nieporęczne. Mechanizm dziedziczenia pozwala utworzyć klasę ogólną SchoolMember (członek społeczności szkolnej), z której klasy dla wykładowców i studentów dziedziczą wspólne własności. Dodajemy tylko specyficzne dla nich dane i zachowania. Takie podejscie ma wiele zalet.
- Jeśli dodamy lub zmodyfikujemy jakąś funkcjonalność w klasie SchoolMember, zmiany te zostaną oczywiście automatycznie uwzględnione przez klasy pochodne. Można na przykład wyobrazić sobie taką sytuację, w której na uczelni wprowadzono wspólne dla wykładowców i studentów karty identyfikacyjne. Wtedy wystarczy, że dodamy odpowiednie pole z numerem karty do klasy SchoolMember, aby ta funkcjonalność pojawiła się zarówno w klasie wykładowców, jak i studentów. Z drugiej strony zmiany w klasach pochodnych nie wpływają na funkcje „przodka” ani pozostałych klas pochodnych.
- Kolejną zaletą jest fakt, że można traktować obiekty studentów i wykładowców wspólnie jako obiekty jednej klasy - „SchoolMember”. Na przykład podczas liczenia wszystkich ludzi związanych z uczelnią przeglądamy obiekty klasy SchoolMember, nie wnikając czy to są studenci, czy wykładowcy. Taka właściwość klas nazywa się polimorfizmem. Polimorfizm w skrócie polega na tym, że zawsze, kiedy oczekiwany obiekt danej klasy, możesz zamiast niego użyć także obiekt klasy pochodnej.
- Kod zawarty w klasie podstawowej jest wielokrotnie wykorzystujemy. Bez mechanizmu dziedziczenia musielibyśmy pisać ten kod dla każdej klasy osobno. Klasa SchoolMember jest w naszym przypadku klasą podstawową, klasą przodka albo superklasą. Klasy Wykladowca i Student są klasami pochodnymi albo podklasami.
Zobaczmy teraz, jak wygląda wyżej opisany przykład zapisany jako program w Pythonie.
Rezultat:
Objaśnienie
Aby użyć dziedziczenia, w linijce, w której definiujemy klasę, za jej nazwą zamieszczamy w krotce nazwy klas, z których dziedziczymy. W innym miejscu w kodzie widzimy jawne wywołanie metody __init__ z klasy podstawowej, co było możliwe przez przekazanie jej self. Tym sposobem możemy małym nakładem pracy zainicjalizować tę część klasy pochodnej, która została odziedziczona z klasy podstawowej. Musisz tutaj zapamiętać jedną ważną rzecz: Python nie wywoła automatycznie konstruktora (metody __init__) klasy podstawowej w konstruktorze klasy pochodnej — musisz zrobić to samodzielnie.
Zobaczyliśmy w tym przykładzie, że wywoływanie metod z klasy podstawowej polega na poprzedzeniu ich nazwą klasy podstawowej i kropką oraz przekazaniu im zmiennej self jako pierwszy argument.
Zwróć uwagę na to, że możemy traktować instancje klas Wykladowca oraz Student jak instancje klasy SchoolMember, kiedy używamy metody powiedz z klasy SchoolMember. Zauważ jednak, że wywoływana jest nie metoda powiedz z klasy podstawowej, ale jej wersja z klasy pochodnej, której obiekt aktualnie kryje się pod maską klasy podstawowej. Dzieje się tak dlatego, że Python zawsze rozpoczyna szukanie metod od klasy, z której obiektem w danej chwili ma do czynienia. Dopiero kiedy nie znajdzie żądanej metody, szuka jej w klasach podstawowych w takiej kolejności, w jakiej zostały one zamieszczone w krotce w definicji klasy pochodnej. Jeszcze jedna uwaga co do terminologii — jeśli w krotce z klasami dziedziczonymi znajduje się więcej niż jedna klasa, takie dziedziczenie nazywamy dziedziczenie wielokrotnym albo wielodziedziczeniem.
Metody statyczne
W niektórych językach programowania (na przykład w popularnym języku Java) nie ma możliwości definiowania funkcji poza klasami i obiektami. Nawet najprostsza funkcja nie odwołująca się do niczego poza parametrami musi być wewnątrz jakiejś klasy. Naturalne w świecie Pythona operowanie na obiektach (a nie na klasach) nakazywałoby w takiej sytuacji tworzyć obiekty tylko po to, by wykonać funkcję. Aby tego uniknąć wprowadzono w klasach funkcje statyczne – które istnieją nawet gdy nie ma żadnego obiektu tej klasy. Coś podobnego wprowadzono w Pythonie przy pomocy wbudowanej funkcji staticmethod (metoda statyczna). Zmieńmy nieco powyższy przykład dodając pole do zapamiętanie w klasie (a nie obiekcie) populacji wszystkich robotów danej klasy. Wykorzystamy poprzednią definicję klasy stosując dziedziczenie:
Wyjście:
Objaśnienie
Możemy stosować dwa typy pól w obiektach — zmienne klas i zmienne obiektów. Do pierwszych odwołujemy się poprzez nazwę klasy, a do drugich poprzez przedrostek self. Zmana (własność) klasy jest wspólna dla całej klasy. Tu zmienną (własnością) klasy jest populacja, a zmienną obiektów jest nazwa.
Zmienne klasy są dzielone, co oznacza, że są dostępne dla wszystkich instancji danej klasy. Istnieje tylko jedna kopia zmiennej klasy, czyli jeśli jeden obiekt zmieni w jakiś sposób tę zmienną, to zmiana ta będzie widziana również przez wszystkie pozostałe instancje.
Tak więc do zmiennej populacja, która jest zmienną klasy, odnosimy się przez RobotNG.populacja, a nie przez self.populacja. Natomiast do zmiennej obiektu nazwa odnosimy się w metodach tego obiektu przez self.nazwa. Zapamiętaj tę różnicę pomiędzy zmiennymi klas i zmiennymi obiektów. Zapamiętaj też, że zmienna obiektu o takiej samej nazwie, jak zmienna klasy, zasłoni zmienną klasy! Zauważ, że metoda __init__ inicjalizuje instancję klasy Robot nazwą podaną przez argument. Metoda __init__ obiektu pochodnego (RobotNG) zwiększa dodatkowo o 1 licznik populacja. Pokazano także odwołanie do metody (konstruktora) przodka. Robimy to po prostu podając nazwę klasy przodka (zamiast self.). Zauważ też, że wartość self.name jest inna dla każdego obiektu, ponieważ jest to zmienna obiektu.
Zapamiętaj, że do metod i pól tego samego obiektu możesz odnosić wyłącznie za pomocą self.
Oprócz __init__, istnieją również inne specjalne metody. Jedną z nich jest __del__, która jest wywoływana w momencie, w którym obiekt umiera, to znaczy kiedy już nigdy nie będzie użyty i zostaje zwrócony systemowi, aby ten mógł zwolnić zajmowaną przezeń pamięć. W tej metodzie możemy zmniejszać na przykład licznik RobotNG.populacja o 1 (przykład tego nie obejmuje)
Metoda __del__ jest uruchamiana, kiedy obiekt już nie jest w użyciu, ale nie ma żadnej gwarancji, kiedy to nastąpi. Jeśli chcesz jawnie ją wywołać, możesz użyć polecenia del.
Metoda jakDuzo jest przykładem metody należącej do klasy, a nie do obiektu (nie posiada self). Oznacza to, że możemy zdefiniować ją jako classmethod lub staticmethod w zależności od tego, czy chcemy zachować informację, do jakiej klasy ta metoda należy. Ponieważ nie potrzebujemy w naszym programie takiej informacji, użyjemy staticmethod.
Dekoratory – czyli nowa magia
Skoro jak powiedziano wcześniej taki styl programowania (sięganie do klas, a nie obiektów) zaciemnia nam program – to czemu do tego wracamy? I jeszcze te nowe dziwne w świecie Pythona „statyczne metody” (staticmethod). Powyższy przykład pozwoli nam Głównie dlatego, by zrozumieć działanie dekoratorów. Jak wcześniej to opisano, Python niejawnie tłumaczy wywołanie metody obiektu na wywołanie funkcji zdefiniowanej w klasie, wstawiając referencję obiektu do parametru self. Zamiast więc złożonej konstrukcji Klasa.metoda(obiekt) mamy eleganckie obiekt.klasa(). Podany wcześniej przykład z robotami pokazuje, że także nie zalecane modyfikowanie definicji (klasy) może mieć sens. Aby więc ukryć paskudne szczegóły, tu także pojawiła się magia w postaci dekoratorów. Tu mamy możliwość zastosowania prostego dekoratora:
Dekoratory można sobie wyobrazić jako skróty do wywołania jawnego polecenia, jak to widzieliśmy w przykładzie. Ogólnie zapis:
można traktować jako równoważnik równoważnik do zapisu:
Prosty przykład dekoratora
Wyjście:
Objaśnienie:
Dodanie oznaczenie dekoratora @helloworld powoduje, że wywołanie myfunc() zostanie poprzedzone funkcjonalnością z helloworld. Nieco bardziej złożony przykład dotyczy funkcji z parametrami.
Wyjście
Objaśnienie
Użycie funkcja2 wyjaśnia działanie dekoratora przy funkcja1. Należy zwrócić jednak uwagę na dwa dodatkowe elementy:
- Wewnątrz funkcji dekorator zdefiniowano inną funkcję i przekazano ją jako parametr. To jest rzadko stosowane - poza właśnie definicją dekoratorów.
- Nieco częściej można spotkać alternatywny zapis parametrów: *args, **kwds. Oznacza to dowolną listę parametrów. Opisano to w poprzednim. Więcej informacji o dekoratorach znajdziesz na stronie: http://www.rwdev.eu/articles/decorators
Interfejsy i wtyczki
Powszechność występowania obiektów świata skłania do stosowania języków obiektowych do sterowania nimi. Robimy to za pomocą różnych interfejsów i wtyczek.
- Interfejsem – czyli inaczej złączem jest specyfikacja połączenia. Coś w rodzaju instrukcji obsługi. Gdy ta obsługa ma się odbywać z użyciem programu – instrukcja przyjmuje zazwyczaj postać opisu obiektu (obiektów). Szczegółowa dokumentacja opisująca jak taki obiekt zbudować, nazywa się API (z angielskiego Application Programming Interface). Przykładowo encyklopedia internetowa ma swoje API zdefiniowane na stronie: https://www.mediawiki.org/wiki/API:Main_page. Jak w większości popularnych interfejsów, programiści Pythona już z tej instrukcji skorzystali. Nie musimy więc wcale jej czytać. Wystarczy krótki opis obiektu, by go użyć. Najpierw instalujemy w swoim systemie:
teraz już możemy sięgać do Wikipedii z programu:
- Wtyczki (ang. plugin) to bardziej złożony sposób korzystania z obiektów. Chodzi mianowicie o rozszerzenie ich funkcjonalności. W tym wypadku uruchamiamy program tylko wewnątrz innego programu. Na przykład popularny program graficzny Gimp ma swoje API. Napiszmy prostą wtyczkę, która przypomina nasze ulubione kolory.
Stwórzmy program mojekolory.py
Program należy skopiować w miejsce przeznaczone na wtyczki – zgodnie z instrukcją: https://www.gimpuj.info/index.php?topic=65974.0 W tym programie funkcja MojeKolory sięga do obiektu pdb, który reprezentuje ustawienia Gimpa i wywołuje metody zmiany kolorów. Reszta powyższego programu to rejestracja wtyczki – zmiana menu. W podmenu Filtry pojawi się funkcja „Standardowe Kolory” (dostępna w trakcie edycji obrazu). Jej wybranie powoduje uruchomienie procedury MojeKolory, która ustawia domyślne kolory tła i pędzla.
Przetwarzanie danych
Struktury danych takie jak listy są bardzo użyteczne i wygodne w użyciu. Mają jednak pewną wadę. Są przechowywane w pamięci operacyjnej komputera. Pamięć ta ma ograniczoną pojemność a na dodatek jest ulotna. Jeśli więc zostanie wyłączone zasilanie, albo program zostanie usunięty z pamięci – dane znikną wraz z nim. W niniejszym rozdziale zajmiemy się metodami trwałego zapisu danych.
Pliki
Pliki to dane zapisane na dysku lub innym urządzeniu. Dostęp do plików uzyskuje się w Pythonie poprzez standardową (dostępną bez importu dodatkowych modułów) klasę file. Utworzenie obiektu tej klasy wiąże się z otwarciem pliku. Otwarcie pozwala na zapis i/lub odczyt danych. Plik otwieramy wywołując funkcję open podając w parametrach nazwę pliku. Drugi (opcjonalny) parametr wskazuje, czy zamierzamy do pliku pisać (‘w’), czy z niego czytać (‘r’). Funkcja open zwraca się do systemu operacyjnego aby zezwolił na dostęp (odczyt / zapis) do pliku. Gdy to się uda, zwracany jest obiekt klasy file.
Pliki najczęściej zawierają teksty i przetwarza się je wierszami.
Rezultat:
Objaśnienie:
- Tworzymy plik wierszyk.txt otwierając go w trybie do zapisu (z argumentem ‘w’). Jeśli plik o takiej nazwie jeszcze nie istniał, to zostaje on utworzony z pustą zawartością. Jeśli już istniał, to jego zawartość zostaje skasowana (jeśli chcemy dopisać treść na końcu nie kasując zawartości, to zamiast parametru ‘w’ powinniśmy użyć parametru ‘a’). Do tego pliku zapisujemy tekst podzielony na wiersze.
- Następnie otwieramy ten sam plik ponownie, tym razem w trybie do odczytu. Następnie linijka po linijce odczytujemy plik i wypisujemy każdą linijkę.
Program jest bardzo prosty, ale jest tu parę drobiazgów na które warto zwrócić uwagę:
- Zmienna wierszyk jest wielowierszowym ciągiem znaków. W Pythonie takie teksty tworzymy przez użycie potrójnego apostrofu (‘’’) jako ogranicznika.
- Bezpośrednio po otwarciu tego napisu jest użyty znak kontynuacji ‘\’. Gdyby go nie było, napis zawierałby dodatkową pustą linijkę na samym początku.
- Zapis danych na dysk nie następuje natychmiast, ale dopiero wtedy, gdy uzbiera się ich większa ilość. Dlatego na końcu powinno się wykonać operację odwrotną do otwarcia: close. Zapisuje ona wszystkie dane na dysk i kasuje obiekt pliku. Jeśli chcemy zacząć odczytywać ten plik bez zamykania - musimy się upewnić, że nasze zmiany naprawdę znalazły się na dysku. Służy do tego metoda flush.
- Gdybyśmy nie wywołali flush, to zmiany w pliku i tak zostałyby zapisane automatycznie najpóźniej w momencie zakończenia programu. Gdy program kończy swoje działanie, wszystkie zmiany są zapisywane i pliki są zamykane przez system operacyjny.
- Iteracja po obiekcie file oznacza iterację po linijkach (for otrzymuje kolejne linijki).
Jeśli chcemy zapisać inne niż tekst dane, możemy posłużyć się funkcją pakowania danych (pack) z modułu struct. Zamienia ona tekst na dane binarne (w takiej postaci jak przechowywane są w pamięci komputera). Każda dana zajmuje tyle miejsca (w bajtach), ile podano w tabelce opisującej tą funkcję https://docs.python.org/3/library/struct.html?highlight=struct.pack:
Wyjście
Objaśnienie
- Funkcja pack tworzy bufor danych, który może być zapisywany do pliku binarnego (funkcją write dla obiektu file) – identycznie jak do plików tekstowych zapisuje się napisy (string). Pierwszy parametr funkcji pack zawiera określenie jakiego rodzaju dane mają być pakowane – zgodnie ze wskazaną wcześniej tabelką (h oznacza małą liczbę całkowitą – short integer). Po nim następują dane do upakowania.
- Odczytując dane binarne musimy podać ilość danych do odczytania. Następny odczyt odbywa się od miejsca, gdzie skończył się poprzedni. Mówi się czasem o wskaźniku pliku – jako wskazaniu na miejsce odczytu/zapisu. Można dzięki temu do jednego pliku pisać i czytać wiele danych binarnych. Taki sekwencyjny (jeden po drugim) odczyt danych może dla dużych plików być nieefektywny (gdy chcemy odczytać dane nie zapisane na samym początku pliku). Dlatego dla plików na dysku wprowadzono przesuwanie wskaźnika funkcją seek. W naszym przykładzie seek(2) oznacza przejście do 3-go bajtu liczonego od początku pliku.
- Literka ‘b’ dodana do parametru określającego tryb otwarcia pliku (‘wb’ zamiast ‘w’ oraz ‘rb’ zamiast ‘b’) oznacza, że chcemy zapisywać / odczytywać dane binarne. W przypadku Pythona 2.x w zasadzie ta różnica jest bez znaczenia (ale jest akceptowana), ale w Pythonie 3 jest istotna.
Pickle
Dostęp do plików binarnych choć efektywny, może powodować trudne do zlokalizowania błędy. Wystarczy drobna pomyłka w wyliczeniu ilości bajtów lub ich oznaczenia w funkcji pack/unpack – i otrzymujemy błędne dane. Dlatego standardowa biblioteka Pythona zawiera moduł pickle (konserwacja, utrwalanie), który przejmuje za nas tego typu działania. Służy on do zapisywania i odczytywania dowolnych obiektów Pythona. Tego typu obiekty są nazywane trwałymi (ang. persistent) a plik je przechowujący pamięcią trwałą (persistent storage). Zakończenie programu nie powoduje bowiem ich zniknięcia.
Wyjście
Objaśnienie:
- Aby zapisać ‘utrwalane’ dane do pliku, otwieramy go – podobnie jak w poprzednim przykładzie w trybie do zapisu binarnego (‘wb’ = write – binary).
- Aby odczytać takie dane z pliku, otwieramy plik w trybie do odczytu. Funkcja pickle.load() pozwala nam odczytać dokładnie to co zapisaliśmy.
Pliki i wyrażenie with
Konieczność zamykania plików to coś, o czym programista powinien pamiętać. Często jednak jest to pomijane (jak w poprzednim przykładzie), bo przecież i tak z chwilą zakończenia programu wszystko zostanie zamknięte. Nie jest to uniwersalne i godne polecenia rozwiązanie. Dlatego wprowadzono instrukcję with i wyposażono obiekty otwierane (takiej jak file) w funkcjonalność pozwalającą na automatyczne ich zamykanie. Obiekt jest zamykany z chwilą zakończenia bloku with.
Objaśnienie:
- Dzięki użyciu funkcji open razem z wyrażeniem with — nie przejmujemy się zamykaniem pliku, gdyż with open robi to automatycznie.
- Instrukcja with została wbudowana w Pythonie w wersji 2.6. W Pythonie 2.5 with musi być najpierw zaimportowane z modułu __future__:
- Instrukcja with zawsze wywołuje funkcję file.__enter__ przed rozpoczęciem wykonania swojego bloku oraz – na koniec funkcję file.__exit__.
Bazy danych
Upowszechnienie się baz danych sprawiło, że nie musimy implementować szczegółów operacji zapisu i odczytu danych, ale możemy je zlecić innym programom: serwerom baz danych. Zlecenia te są formułowane w języku SQL, którego najprostsze polecenie ma kształt:
Oznacza to odczytanie wszystkich danych (*) z tabeli o nazwie tabela.
W internecie można znaleźć wiele kursów języka SQL. Jako całe wprowadzenie wystarczy jednak poniższy prosty (choć kompletny) przykład.
Wynik:
Objaśnienie:
- Istnieje wiele różnych baz danych. Powyższy przykład dotyczy najprostszej z nich: SQLite. Dane są pamiętane w przypadku tej bazy w jednym pliku. W przypadku bardziej zaawansowanych baz danych komunikacja odbywa się za pośrednictwem sieci. Dla SQLite potrzebny jest tylko odpowiedni moduł – w przypadku Pythona jest to sqlite3.
- Połączenie z bazą danych (funkcja connect) ustala zazwyczaj jedynie parametry komunikacji. W przypadku SQLite polecenie sqlite3.connect(‘test.db’) tworzy bazę – o ile wcześniej ona nie istniała.
- Komunikacja z bazą odbywa się z użyciem języka SQL. Polecenia nie są przesyłane wprost poprzez obiekt połączenia (db w powyższym przykładzie), ale przez obiekt kursora (ang. cursor). Umożliwia to stworzenie wielu kursorów dla jednej bazy – na przykład dwa kursory do przeglądania równolegle dwóch tabel.
- Do wysłania polecenia SQL można użyć funkcji executescript lub execute. W tym drugim przypadku jako wynik funkcji dostajemy rezultat zapytania w postacie generowanych wierszy.
- Polecenie SQL ‘CREATE TABLE “user” … ‘ tworzy tabelę o nazwie „user”. Polecenie ‘insert into ….’ wstawia wiersze do tej tabeli. Natomiast ‘select …’ odczytuje wstawione dane.
- Funkcja commit() w obiekcie połączenia z bazą powoduje zakończenie zapytania i zapisanie danych na dysku.
[v3]
Pisanie niezawodnego kodu
Wyjątki, czyli przewidywanie niespodziewanego
Rozważmy proste wywołanie polecenia print. Co się stanie, gdy przypadkiem zapiszemy print jako Print? W tym wypadku Python zgłasza błąd nazwy polecenia.
Python wyświetla rodzaj błędu (NameError) oraz miejsce, w którym ten błąd wystąpił. Użycie identyfikatora NameError jest związane z mechanizmem obsługi sytuacji wyjątkowych do których należy wystąpienie błędu w programie. Mechanizm ten nazywa się wyjątkami.
NameError jest nazwą wyjątku, jaki udało się zidentyfikować. Jest to identyfikator klasy pochodnej wobec Exception.
Inny przykład wyjątku możemy uzyskać wprowadzając błędne dane. Funkcja raw_input zakłada, że jakieś dane się pojawią.
Jeśli zamiast wprowadzania danych naciśniemy Ctrl-d – będzie
to oznaczało koniec danych.
Python zgłasza błąd typu EOFError, co oznacza, że znalazł symbol końca pliku (który jest reprezentowany przez ctrl-d) w miejscu, w którym on nie powinien występować (EOF = End Of File = koniec pliku).
Obsługa wyjątków
Możemy zaprogramować własną obsługę wyjątków używając instrukcji try…except….
Po słowie kluczowym except umieszczamy kod, który zostanie wykonany, gdy w bloku instrukcji po słowie kluczowym try nastąpi wyjątek.
Rezultat:
Za pierwszym razem naciśnięto ctrl-d, za drugim ctrl-c.
Objaśnienie:
- W razie wystąpienia wyjątku w bloku instrukcji po słowie try, resztatego bloku jest pomijana. Python próbuje ustalić jaki rodzaj wyjątku wystąpił i przechodzi do odpowiedniej dla niego sekcji obsługi – po słowie except. W przypadku, gdy rodzaj błędu nie zostanie określony, wykonywany jest ostatni blok except – który nie jest oznaczony nazwą wyjątku.
- Dla każdego try musi być zdefiniowane przynajmniej jedna sekcja wyjątków except.
- Gdy nie obsłużymy błędu za pomocą except, wtedy Python domyślnie zatrzymuje program i wyświetla komunikat o błędzie – tak jak to wcześniej pokazano.
- Blok else w przypadku try działa podobnie jak przy if oraz while – wykonywany jest wówczas, gdy nie wystąpił żaden wyjątek.
Zgłaszanie wyjątków
Wyjątki zdarzają się nie tylko z powodu nieprzewidzianych okoliczności. Programista może przewidzieć sytuację wyjątkową i po jej stwierdzeniu zgłosić wyjątek instrukcją raise.
Rezultat:
Objaśnienie
Jak już wcześniej była mowa – dla każdego wyjątku tworzony jest obiekt klasy pochodnej w stosunku do Exception. Możemy oczywiście stworzyć własną klasę wyjątków. W tym przykładzie stworzyliśmy klasę o nazwie WyjatekKrotkiegoWpisu. Tworzenia własnych klas zapewnia nie tylko rozróżnianie wyjątków, ale można stworzyć dodatkowe własności w klasie wyjątku do przechowywania związanych z obsługiwanym zdarzeniem wartości. W naszym przykładzie jest to dlugosc, czyli długość wpisanego tekstu oraz conajmniej, czyli minimalna długość, jakiej program oczekuje.
try…finally…
Czasem trzeba wykonać jakieś działania niezależnie od tego, czy nastąpił wyjątek. Na przykład chcemy zawsze zamknąć otwarty plik. Służy do tego blok instrukcji try umieszczany po słowie finally.
Rezultat:
Objaśnienie
Wprowadzenie opóźnienia po odczytaniu każdej linijki (time.sleep) umożliwia naciśnięcie ctrl-c i przerwanie działania programu jeszcze w czasie odczytu.
Wyjątek KeyboardInterrupt (przerwanie z klawiatury) zostanie wyświetlony i program zakończy swoje działanie. Jednakże tuż przed końcem programu blok finally umożliwia poprawne zamknięcie pliku.
Jest to rozwiązanie alternatywne do opisanej w poprzednim rozdziale instrukcji with.
Zostawianie śladów
Samo wyświetlenie komunikatu może nie wystarczyć dla zdiagnozowania ewentualnych problemów. Jeśli na przykład wyjątek pojawi się w złożonym systemie, który musi tolerować problemy (na przykład serwer www) – warto jednak by ślad po tych problemach pozostał do późniejszej analizy. Służy do tego system logowania (dziennik) błędów. W Pythonie zaimplementowano go w module logging.
Poniżej nieco bardziej złożony przykład pokazuje raz jeszcze obsługę błędów, ale tym razem z zapisywaniem problemów do loga.
Objaśnienie
- W programie zaimplementowano klasę KodPocztowy do przechowywania kodów pocztowych. Zapamiętywanie kodu zapewnia metoda set. Sprawdza ona poprawność kodu i zgłasza wyjątek. Zdefiniowano do tego celu własny wyjątek o nazwie BlednyKodPocztowy. Utworzono także obiekt kod klasy KodPocztowy. Ten fragment programu nie wyróżnia się niczym w stosunku do poprzednich przykładów dotyczących wyjątków.
- Różnica polega na tym w jaki sposób program obsługuje wyjątki. Do tego celu użyty został mechanizm logowania błędów – logging. Funkcja logging.basicConfig ustawia nazwę pliku do którego będą zapisane błędy, oraz poziom śledzenia (w tym wypadku najwyższy – DEBUG).
- Wywołanie logging.warning(komunikat) powoduje zapisanie komunikatu do późniejszej analizy.
Automatyczne dokumentowanie
Python posiada mechanizm zwany opisami dokumentującymi określany skrótem docstrings od angielskiej nazwy (documentation strings). Jest to potężne narzędzie, którego warto często używać, gdyż pomaga w lepszym zrozumieniu działania programu. Pamiętaj, że program piszesz raz, ale czytany i analizowany będzie wielokrotnie! Mechanizm docstring w Pythonie może być wykorzystywany nawet w trakcie eksploatacji, gdyż potrafi on uzyskać na przykład opis uruchomionej funkcji!
Rezultat:
Objaśnienie:
Opis w postaci łańcucha znaków w pierwszym wierszu funkcjito jej DocString (opis dokumentujący). Taki sam opis można zastosować do modułów i klas!
Zaleca się (choć nie jest to obowiązkowe), aby opis docstring zaczyna… się dużą literą, pierwszy wiersz był zakończony kropką, po nim następował wiersz pusty, a dalej dokładniejsze wyjaśnienia.
Opis (docstring) jest dostepny za pośrednictwem własności __doc__ (uwaga na podwójne podkreślenia). W Pythonie wszystko jest obiektem - także nawet funkcje – dlatego można definować własności funkcji (takie jak __+doc__).
Zamiast odwołania do __doc__ można używać funkcji help(). W tym wypadku help(printMax).
Opis nie jest wówczas jedynie wyświetlany, ale można go przeglądać. Uwaga! Z przeglądania tego opisu wychodzi się naciskając klawisz q.
Kodowanie oparte o testy
Kodowanie sterowane testami polega na tym, że najpierw przygotowujemy test fragmentu programu, a później dopiero kodujemy ten fragment. Test dzieli się na funkcjonalne i jednostkowe. Test funkcjonalny sprawdza działanie funkcji programu z zewnątrz – z perspektywy użytkownika. Pozwala stwierdzić czy program działa zgodnie ze specyfikacją. Testy jednostkowe natomiast sprawdzają działanie z perspektywy programisty – od środka. Gdy program nie działa poprawnie – pomagają precyzyjnie ustalić przyczynę. Ta strategia tworzenia oprogramowania jest stosunkowo młoda. W niniejszym podręczniku jedynie sygnalizujemy jeden ze sposobów realizacji tej idei (obszerniejszy przykład można znaleźć na stronie: http://www.python.rk.edu.pl/w/p/programowanie-oparte-o-testy/).
Wykorzystamy obiekt szyfrowania z jednego z poprzednich przykładów.
Objaśnienie
- Test funkcjonalny w powyższym przykładzie to po prostu dodatkowa funkcja w której wykonuje się obliczenia dla przykładowych danych.
- Nieco bardziej złożone jest pisanie testów jednostkowych. Używane w nich jest polecenie assert, które sprawdza poprawność wykonanych obliczeń. Po słowie kluczowym assert podaje się warunek. Gdy warunek nie jest spełniony – wywoływany jest wyjątek. Dodatkowo po warunku można podać komunikat przesyłany do wyjątku.
- W kodowaniu sterowanym testami zaczynamy od napisania testu, zastępując wywoływane funkcje „zajawkami” (nagłówek + słowo kluczowe pass). Test oczywiście nie przejdzie póki nie wykonamy poprawnej implementacji funkcji.
[v5]
Przepływ danych i sterowania
Zdobyliśmy już całkiem sporo informacji na temat programowania w Pythonie. W obecnym rozdziale spojrzymy na to zagadnienie z nieco innej strony. Zastanowimy się jak w Pythonie zorganizowany jest przepływ danych i sterowania oraz interakcja z otoczeniem. Tu nie ma jednej uniwersalnej metody. Zależnie od środowiska w jakim ma działać nasz program oraz celów w jakim on powstaje, musimy dobrać odpowiednie rozwiązanie. Najczęściej można przy tym skorzystać z czegoś, co inni wymyślili przed nami. Przegląd najważniejszych idei znajdziesz w modularnym kursie programowania http://otwartaedukacja.pl/programowanie/. Poniżej wykorzystamy zawartą tam klasyfikację, aby pokazać jak różne mogą być zastosowania Pythona.
W tym rozdziale najważniejsza jest tak zwana wiedza „miękka” - czyli idee i rozwiązania stosowane przez programistów. Pozwoli to nie tylko uporządkować i utrwalić pewne pojęcia, ale też lepiej zrozumieć sens tego co wykonuje program. Pojawią się jednak także pewne fundamentalne zagadnienia, które nie były wcześniej omawiane (posługiwanie się konsolą, programy systemowe, obsługa błędów, graficzny interfejs użytkownika). To są obszerne zagadnienia, których pełne omówienie wykracza poza zakres tego podręcznika. Poznajemy je dogłębnie wtedy, gdy musimy rozwiązać konkretne zadanie. Tutaj zostaną one pobieżnie omówione na tyle, aby (miejmy nadzieję) wiedzieć gdzie szukać i przy okazji wyjaśnić ideę różnego typu przepływów informacji w programach.
Co to jest sterowanie?
Interpreter Pythona wykonuje kolejne instrukcje programu. Wskazanie na aktualnie wykonywaną instrukcję nazywa się potocznie sterowaniem. Program wykonuje kolejne instrukcje w sposób sekwencyjny – tak jak zostały zapisane. Poznaliśmy już wcześniej zmiany sterowania związane z instrukcjami warunkowymi, pętlami, wywołaniem procedur i własności obiektów.
Wykonywane instrukcje mogą zmieniać dane (zmienne). Sterowanie możemy śledzić wykonując program krok po kroku przy pomocy debuggera (funkcjonalność większość IDE – na przykład PyScriptera dla Windows). Debuggery mają także możliwość śledzenia tego jak zmienia się zawartość zmiennych (dane).
1.Konsola Pythona
Konsola Pythona to program, który odczytuje instrukcje w jakimś języku programowania i je wykonuje. Stan w jakim znajduje się komputer może zależeć od wcześniej wykonanych instrukcji jedynie wówczas, gdy zmieniały one jakieś dane (zmienne).
W przypadku Pythona sprawa jest banalnie prosta. Wpisywane przez nas instrukcje (zob. rozdział „Python jako kalkulator) interpreter czyta i wykonuje po naciśnięciu klawisza Enter.
A jak jest z blokami instrukcji?
Uruchom interpreter Pythona poleceniem:
i wypróbuj:
Po wpisaniu wiersza zaczynającego instrukcję for (a kończącego się dwukropkiem), interpreter nic nie robi – czeka na pusty wiersz. Wpisujemy więc instrukcje drukowania (print) i kończymy pustym wierszem (Enter na początku). Zwróć uwagę na spacje przed słowem print!
Interpreter traktuje ciąg wprowadzanych znaków jako jeden strumień, kończący się naciśnięciem klawiszy Ctrl+D (przy wciśniętym Ctrl naciśnij D). Napotkanie takich klawiszy kończy przetwarzanie.
Czy po każdej instrukcji interpreter wraca do stanu początkowego? Nie - jeśli ma nie zakończoną instrukcję (jak for w powyższym przykładzie). Poza tym pozostaje „efekt uboczny” w postaci zmiennej „liczby”. Zmienną tą możemy usunąć poleceniem del:
Na początku wyświetlona została zawartość zmiennej. Po jej usunięciu interpreter wraca do stanu w jakim był w chwili jego uruchomienia! Próba wyświetlenia zmiennej liczby kończy się błędem.
Wszystkie zmienne jakie są w aktualnym stanie zdefiniowane w interpreterze można wyświetlić wbudowaną w niego funkcją dir().
Możemy sprawdzić kończąc działanie interpretera (Ctrl + D) i uruchamiając go na nowo, że powyższe identyfikatory są wbudowane (istnieją już na starcie).
2.Skrypty, programy wsadowe i pliki
Program wsadowy nie komunikuje się z użytkownikiem w trakcie działania. Czyta dane z zadanego wejścia (zazwyczaj urządzenie zewnętrzne) i po przetworzeniu zapisuje je na zadanym wyjściu (zazwyczaj urządzenie zewnętrzne). Tak działały najstarsze programy, które przetwarzały dane zapisywane na taśmach magnetycznych (wcześniej – kartach perforowanych). Dane te nazwano plikami (mówi się też o plikach danych). Pojęcie pliku (ang. file) uogólniono na każdą kolekcję wielu danych, zapisanych w znany sposób i zazwyczaj oznaczonych nazwą. Sekwencyjny zapis i odczyt plików jest podstawowym sposobem operowania na dużych ilościach danych.
Spróbujmy stworzyć taki przykładowy program, który przetworzy nam listę osób na zbiór adresów mailowych. Załóżmy, że mamy listę w pliku csv (zob przykład 3.1 - mediana):
Wejście (zawartość pliku csv, którego nazwę podaliśmy jako parametr):
Wyjście (zawartość pliku email.txt):
Objaśnienie
- Załóżmy, że program zapisaliśmy w pliku o nazwie „em1.py”. Jego wywołanie następuje poprzez podanie interpretera z nazwą pliku programu oraz nazwą pliku z danymi. Uruchom konsolę systemu operacyjnego (pod Windows poleceniem cmd) a następnie wydaj polecenie (zakładając, że dane są w pliku lista.csv, a program w pliku em.py):
- Pierwszy wiersz zawiera nazwę interpretera. Jego wpisanie pozwala w systemach typu Unix (na przykład Linux) wywoływać program bez jawnego podawania interpretera. Na przykład:\ ~/em.py lista.csv\ Przedrostek ~/ oznacza tutaj, że plik jest w naszym katalogu domowym (w przeciwieństwie do Windows bieżący katalog nie jest domyślnym dla poszukiwania programu).\ Dodatkowo trzeba pamiętać, że plik programu musi mieć atrybuty pozwalające na jego uruchomienie.
- Jeśli zmienna __name__ (systemowa – wbudowana w Pythona) zawiera nazwę “__main__” - to oznacza, że moduł został uruchomiony jako główny moduł programu.
- Zmienna sys.argv zawiera listę parametrów z jakimi został wywołany program. Zerowy element w liście zawiera nazwę uruchomionego programu. Gdy wywołano program:\ ./em.py lista.csv\ to sys.argv[0] ma wartość ‘./em.py’ a sys.argv[1] ma wartość ‘lista.csv’\ Wiersz poleceń może mieć więcej parametrów. Istnieje moduł getopt, ułatwiający ich analizę (zob.opis w podręczniku „Zanurkuj w Pythonie”).
- W funkcji doit_file otwieramy plik i kojarzymy z nim obiekt reader z modułu csv, który przetwarza przeczytane wiersze na listy.
- Do pliku fileout zapisujemy tekst. Znaki \n oznaczają koniec wiersza.
3.Potoki i gniazda.
Teksty które program wypisuje wskutek użycia polecenia print są traktowane tak jak pliki (ciąg znaków tworzących plik tekstowy). Jest to standardowy plik wyjściowy dostępny pod zmienną stdout z modułu sys. Istnieje też standardowy plik wejściowy
sys.stdin. Używanie tych dwóch plików umożliwia korzystanie z mechanizmów systemu operacyjnego do przekierowania strumienia danych. Można na przykład skierować wyjście z programu do pliku używając operatora >.
Efektem powyższej komendy będzie zapisanie wyniku działania programu (standardowej wyjście) program1 do pliku plik1.txt.
Można także łączyć programy w ten sposób, że standardowe wyjście z jednego staje się standardowym wejściem drugiego. Zapisujemy to operatorem | (potok). Na przykład:
Zmodyfikujmy nieco poprzedni przykład. Aby zastosować standardowe wejście i wyjście.
Wyjście:
Sprawdźmy działanie programu komendą:
dla Windows:
Uzyskamy na ekranie wynik:
Jeśli podamy komendę:
to wynik zostanie wpisany do pliku „maile.txt”.
Podobnym do powyższego (łączenia przez pliki standardowe) mechanizmem komunikacji z programem są gniazda (socket). Zamiast uruchamiać program za każdym razem, gdy jest potrzebny – możemy zbudować program, który stale „rezyduje” w pamięci czekając na polecenia. Polecenia przesyła się przez gniazdo. Tą samą drogą otrzymuje się zazwyczaj wyniki. Gniazda w systemie operacyjnym mają swoje numery (podobnie jak pliki). Możemy je wykorzystywać do komunikacji między komputerami – odwołując się do nich poprzez numer i adres sieciowy komputera na którym gniazdo zostało utworzone.
Przykład:
http://www.python.rk.edu.pl/w/p/python-i-programowanie-sieciowe/
4.Obiekty
Programowaniu obiektowemu został poświęcony cały odrębny rozdział podręcznika. Tego typu programowanie jest w tej chwili powszechnie stosowane. Jednym z takich zastosowań jest tworzenie graficznych interfejsów do komunikacji z programem. Elementy interfejsu (takie jak okna, pola, przyciski, etykiety) są traktowane jak obiekty, których własności ustawiamy dla uzyskania odpowiedniego wyglądu. Natomiast metody obiektów służą do sterowania ich zachowaniem.
Aby ułatwić sobie tworzenie takich interfejsów, wykorzystuje się gotowe biblioteki programów. Taką popularną biblioteką jest wxPython.
A oto najprostszy przykład jej użycia.
Objaśnienie.
Aplikacja graficzna wykonuje pętlę (MainLoop) czekając na działania ze strony użytkownika. Poleceniem SetTopWindow wyświetlamy okno (frame) w którym ma zachodzić interakcja. Wnętrze okna nazywa się panelem i jest obiektem typu wx.Panel. Na panelu umieszcza się kontrolki (ang. widgets), które są odpowiedzialne za poszczególne elementy wizualne. Tu użyliśmy kontrolki wx.StaticText do umieszczenia na panelu etykiety.
Zauważmy, że biblioteka wx stosuje trzy rodzaje powiązania obiektów ze sobą. Może to następować poprzez parametr przy tworzeniu obiektu (jak w instrukcji wx.Panel(frame, -1) – podajemy w parametrze okno dla którego tworzymy panel). Gdy to powiązanie jest zmienne – jak w przypadku otwartego okna – stosujemy odrębną funkcję – jak w instrukcji app.SetTopWindow(frame). Trzecim sposobem jest bezpośrednia zmiana własności. Na przykład okno ma jeden panel dostępny przez własność o nazwie panel. Możemy tą własność ustawiać jak w instrukcji frame.panel = panel (zauważmy, że frame.panel i panel to dwie różne zmienne).
Powyższy przykład celowo został napisany w ten sposób, że ustawienie wszystkich elementów graficznych następuje na zewnątrz obiektu aplikacji graficznej. Jednak najczęściej stosuje się tworzenie obiektów pochodnych (poprzez dziedziczenie) i ustawianie elementów graficznych wewnątrz konstruktora (__init__) lub specjalnej metody OnInit. Zauważmy, że w ten sposób następuje izolowanie kodu dotyczącego poszczególnych klas i obiektów. Program staje się o wiele bardziej czytelny (co by było, gdybyśmy w poprzednim przykładzie inicjowali 50 okienek z 10 kontrolkami każde?).
Objaśnienie
Jest to ten sam przykład, co 9.2a, ale wszystkie operacje dotyczące aplikacji graficznej zostały zaimplementowane w metodzie OnInit klasy pochodnej do wx.App. Należy zwrócić uwagę na fakt, że zamiast do zmiennej app, wewnątrz klasy odwołujemy się do parametru self – pod którym kryje się oczywiście odwołanie do obiektu aplikacji. W analogiczny sposób można tworzyć klasy pochodne do innych elementów graficznych.
5.Zdarzenia
Programowanie obiektowe jest czasem utożsamiane z programowaniem sterowanym zdarzeniami. Bierze się to stąd, że częścią obiektów takich jak w powyższych przykładach jest obsługa zdarzeń. Co będzie gdy klikniemy w oknie lub zmienimy jego rozmiar? Zostanie wywołana odpowiednia metoda obiektu wyświetlającego zawartość okna. Aby to zilustrować, dodajmy do naszego okna kontrolkę przycisku.
Objaśnienie
Kontrolkę przycisku tworzymy wykorzystując klasę wx.Button. W parametrach podajemy między innymi (opcjonalnie) pozycję (pos) i rozmiar (size).
W implementacji obiektu aplikacji pojawiała się metoda onButton1Click, która jest odpowiedzialna za obsługę zdarzenia. Wiążemy tą metodę ze zdarzeniami dotyczącymi przycisku metodą Bind:
Wewnątrz funkcji onButton1Click zaimplementowana została zmiana etykiety label1. Aby ułatwić odwołanie do etykiety, została ona zainicjowana jako własność obiektu (użycie przedrostka self).
6.Wyjątki
Zdarzenia w programie nie muszą wiązać się wyłącznie z obsługą graficznego interfejsu użytkownika. Bardziej fundamentalne znaczenie ma występowanie sytuacji wyjątkowych – takich jak błędy w programie lub danych. Co się stanie na przykład wtedy, gdy w przetwarzanym pliku csv napotkamy źle sformatowany wiersz? Albo podamy błędną nazwę pliku? Takie sytuacje możemy próbować przewidzieć, dodając do programu warunki sprawdzające poprawność. Ale jeśli w programie zrobimy błąd - wtedy trudno nawet oczekiwać, że taka sytuacja zostanie przewidziana. Można jednak przewidzieć pojawienie się jakiegoś (jakiegokolwiek) błędu i oprogramować obsługę takiej sytuacji.
Służą do tego wyjątki, które zostały szerzej opisane w poprzednim rozdziale. Wracamy do tego zagadnienia przede wszystkim dlatego, żeby pokazać nietypowy przepływ sterowania w przypadku wydarzenia się (lub zgłoszenia) wyjątku.
Poniżej uproszony nieco przykład z rozdziału poświęconego wyjątkom. Drukowanie kolejnych liczb pokazuje jak przebiega sterowanie.
Wyjście:
Objaśnienie
Uzyskany wynik potwierdza, że w programie prawidłowo przewidziano przepływ sterowania oraz miejsca ominięte w razie błędu danych.
7.Systemy wielowarstwowe i rozproszone, interfejsy.
W podrozdziale dotyczącym gniazd wspomniano o komunikacji między programami w sieci. Ponieważ w typowym przypadku programujemy komunikację z programem napisanym przez kogoś innego, dąży się do ustalenia jak najbardziej uniwersalnych zasad komunikacji, zwanych interfejsami. Każdy z nas korzystając z internetu styka się z interfejsem między przeglądarką a serwerem sieciowym (najczęściej jest tu wykorzystywany protokół http lub https). Najczęściej jednak mamy do czynienia z programami odwołującymi się do baz danych. Do serwera na którym umieszczono bazę danych możemy odwoływać się przesyłając polecenia przez gniazda (cob wyżej). Łatwiej jednak używać bibliotek, które całą komunikację wykonają za nas, przekazując nam gotowe dane w formacie struktur Pythona (zob. rozdział poświęcony przetwarzaniu danych).
Innym przykładem rozproszonej aplikacji może być serwer stron internetowych. Elementy takiej aplikacji mogą rezydować na komputerach nawet na różnych kontynentach!
[v3]
Styl programowania
Należy pisać kod programów w taki sposób, by nadawał się do ponownego wykorzystania. Stąd reguła: program piszemy raz, ale czytany może być wiele razy. Musi zatem być on napisany zgodnie ze standardami, z użyciem zrozumiałych identyfikatorów i podzielony na fragmenty w sposób intuicyjnie prosty.
Stykają się tutaj dwie przeciwstawne tendencje. Z jednej strony przyjmuje się standardy prowadzące do ujednolicenia (nieważne w jakim języku programujesz – zasady są takie same). Z drugiej – wybieramy język Python między innymi z uwagi na wygodne mechanizmy. Pisząc program w Pythonie używamy zatem pewnych rozwiązań sprawiających, że kod ma swoją specyfikę. Jest „pythonowaty” (ang. pythonic). Zarówno te ogólne zalecenia jak i „pythonizmy” składają się na coś, co można nazwać stylem programowania.
Niniejszy rozdział poświęcony jest temu zagadnieniu. Zawiera on nie tylko zbiór praktycznych zaleceń. Poznamy przy okazji kolejne – dotąd nie opisywane w tym podręczniku konstrukcje języka Python. Niektóre z tych konstrukcji (lub sposobów ich użycia) nie są zalecane. Opiszemy takie przypadki, aby z jednej strony było wiadomo czego konkretnie unikać, a z drugiej – aby pomóc w zrozumieniu kodu, w którym jednak takich konstrukcji użyto.
Na wstępie jedna ważna uwaga:
Opanowanie konwencji nie warunkuje umiejętności tworzenia programów. To jest proces, który zachodzi równolegle z rozwojem umiejętności programowania. Nie poprzedza i nie warunkuje tej umiejętności. To tylko dobre rady: jak postępować, aby nasz kod był coraz lepszy.
PEP8
Niniejszy podręcznik nie zawiera kompletnego opisu zalecanego stylu programowania. Pominięto między innymi konwencje związane z nazewnictwem zmiennych, stosowaniem odstępów etc…
Tego typu zalecenia można znaleźć w specyfikacji PEP8 (polskie omówienie jest na przykład na blogu blog.grzeszy.net lub w podręczniku „Efektywny python …”). PEP to skrót od „Python Enhancement Proposals” (propozycje rozwoju Pythona). Numerowane dokumenty PEP są gromadzone na stronie www.python.org/dev/peps/. Ten z numerem 8 (PEP 8) zawiera powszechnie akceptowane zalecenia dotyczące stylu programowania . Na stronie pep8.org jest promowany program pep8, który sprawdza nasz kod i drukuje zalecenia zgodne z pep8.
Jak go przetestować?
Zen Pythona
Kod programu w Pythonie wyróżnia się przejrzystością. To nie jest przypadek, ale efekt starań twórców tego języka i przyjętych przez nich założeń. Założenia te – w postaci szeregu aforyzmów zostały udostępniane jako Zen Pythona (nazwa pochodzi od buddyjskiej filozofii zen). Aby je wyświetlić, należy napisać w wierszu poleceń interpretera Pythona:
Wyświetli się spis zaleceń w języku angielskim:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea – let’s do more of those!
Tłumaczenie (http://blog.zabiello.com/2008/09/01/zen-of-python) na język polski:
Nie ma potrzeby, aby uczyć się tych reguł. Jednak warto od czasu do czasu je sobie wyświetlić i zastanowić się, czy je stosujemy. W całym rozdziale będziemy o tych regułach pamiętać i czasem nawiązywać do nich. Na początek warto zwrócić uwagę na jedno: programowanie to nie tylko intelektualny wysiłek, ale też fajna zabawa. Dobry programista to pasjonat, który lubi to co robi. Ta pasja jest napędzana satysfakcją jaką daje dobrze działający program.
Lukier składniowy
Określenie „lukier składniowy” (ang. syntactic sugar) jest używane w odniesieniu do elementów składni języka, które są nadmiarowe, ale zostały wprowadzone dla wygody programisty. Czasem może to być postrzegane jako zbędne udziwnienia. Jednak w Pythonie tego rodzaju rozszerzenia wprowadzane są zgodnie z powyższymi zasadami „zen” - więc zapewniają nie tylko większą wygodę, ale i wzrost czytelności. Dobrym przykładem takiego rozszerzenia są dekoratory, które poznaliśmy już wcześniej.
Określenie „lukier składniowy” nie jest precyzyjne – bo ma charakter publicystyczny a nie techniczny. Do tego typu rozwiązań można zaliczyć kilka konstrukcji o których będzie mowa w niniejszym rozdziale (wyrażenia „for” oraz „if”, funkcje lambda). Z całą pewnością można tak nazwać poniższe dwa rozwiązania: podział tekstu na wiersze oraz łączenie porównań.
Dzielenie tekstu na wiele wierszy
Jednym z zaleceń PIP8 jest ograniczenie długości wierszy do 80 znaków. Co jednak zrobić, gdy ten wiersz jest bardzo długi? Pokazuje to poniższy przykład:
Wyniki:
Objaśnienie:
- W przypadku instrukcji sprawa jest prosta. Wiersz można podzielić na kilka krótszych używając znaku kontynuacji \ (krotka k1).
- Znak kontynuacji nie jest potrzebny gdy interpreter „wie”, że instrukcja nie jest zakończona i bedzie kontynuowana (krotka k2 nie kończy sie nazwiasem zamykającym).
- W przypadku dłuższy łańcuch znaków (stałych tekstowych) można stosować także backslash (\) albo łączenie tekstów (metodą join). Ale to wprowadzają dodatkowe komplikacje. Na przykład jeśli nie dosuniemy nowego wiersza do lewego marginesu - powstaną spacje. Istnieje prostsze rozwiązanie: kilka łańcuchów znaków ujętych w nawiasy okrągłe jest traktowane jak jeden łańcuch (dlugi_napis). Należy zwrócić uwagę na brak przecinka rozdzielającego łańcuchy znaków. Tym różni się zapis w wielu wierszach jednej stałej od krotki zawierającej wiele stałych.
Łączenie porównań
Jeśli chcemy sprawdzić, czy zawartość zmiennej mieści się w określonym zakresie, można użyć dwóch porównań złączonych spójnikiem and:
Objaśnienie:
Python połączenie takich porównań w łańcuch. Tego nie ma (chyba?) w żadnym innym języku programowania. Dlatego budzi to kontrowersje. Niemniej warto wiedzieć, że coś takiego istnieje.
Generowanie wielu powtórzeń tego samego
Operator mnożenia (*) w Pythonie może być używany do list:
Wynik:
Rozpakowywanie sekwencji
Rozpakowywanie krotek to często spotykane rozwiązanie, na które czasem nawet nie zwracamy uwagi z uwagi na jego naturalność. Korzystamy z tego przy przeglądaniu sekwencji krotek (na przykład czytanych z bazy danych), czy zamianie wartości zmiennych.
Wynik:
Objaśnienie:
- Pętla for działa w przypadku sekwencji krotek podobnie jak przy odczycie z bazy danych. Podając identyfikatory poszczególnych elementów dokonujemy ich rozpakowania. Każdy element staje się zmienną o podanym identyfikatorze.
- Rozpakowywania krotek można użyć na przykład do zamiany wartości dwóch zmiennych. Lewa strona równania w przedostatnim wierszu zawiera rozpakowane kroki (wartości w zmiennych). Po prawej stronie równania mamy nową krotkę (a nie tą rozpakowaną). Ale oczywiście można wykorzystać zmienne z rozpakowania - to umożliwia zamianę wartości.
Można także rozpakowywać listy (operatorem *) i słowniki (operator **). Ale to jest rzadko używane i w zasadzie tylko do przekazywania parametrów:
Wynik:
Idiomy w programowaniu
Idiomy to pewne „osobliwości języka” pozwalające w sposób nietypowy coś wyrazić. Programiści Pythona nazywają tak („Python Idioms”) ideę wybierania spośród różnych możliwości zapisu (wzorców) takiego, które jest optymalny (zalecany). Poniższa lista idiomów nie jest kompletna (to nie jest formalny standard).
Logiczna wartość różnych danych
Dane typu logicznego (boolean) mogą być pamiętane w zmiennych tak samo jak dane innych typów. Można więc zapisać:
Warunek w instrukcji if można zapisać także tak: if not (dobra == True), lub tak: if dobra == False. Jednak zaleca się korzystanie z tej pierwszej możliwości.
Nie tylko zmienne zawierające wartość True albo False posiadają wartość logiczną. Na przykład wartość pusta (None) lub pusta lista jest traktowana na równi z fałszem.
Zamiast:
można (i warto) zapisać:
Poniższa tabelka zawiera spis wartości logicznej True (prawda) lub False (fałsz) różnych wyrażeń:
True | False |
---|---|
Niepusty łańcuch znaków | Pusty łańcuch znaków |
Liczba różna od zera | Liczba 0 (lub 0.0) |
Kolekcja (lista, słownik etc.) niepusta: len(x) > 0 | Kolekcja pusta ([] , () , {} , set() ): len(x) == 0 |
- | None |
Można też sprawdzać wartość logiczną obiektów! W chwili porównania wywoływana jest metoda wewnętrzna (systemowa) __bool__ (Python 3.x) lub __nonzero__ (Python 2.x). Można oczywiście zdefiniować swoją implementację tych metod.
Jeśli obiekt służy nam do zapamiętywania danych (pojemnik / container) – można sprawdzać ilość tych danych. Służy do tego metoda __len__:
Wynik:
Objaśnienie:
Obiekt klasy Test zawsze zwraca False. Ustala tą wartość metoda __bool__ lub __nonzero__. Natomiast wartość obiektu klasy Queue zależy od tego, czy zainicjowano go z pustą listą.
Co można zapisać w jednym wierszu? Wyrażenia if.
Python pozwala na umieszczenie kilku wierszy kodu w jednym z użyciem średników. Zamiast więc pisać:
możemy napisać:
Można też w jednym wierszu zawrzeć instrukcję wraz z jednowierszowym blokiem. Wtedy nie używamy średnika:
Kiedy należy stosować taki zapis? W zasadzie nigdy – on nie jest zalecany.
Jednak możemy czasem (jeśli nie wpływa to na czytelność kodu) zamienić kilka wierszy instrukcji na jeden. Weźmy na przykład fragment kodu, który zwraca nam łaciński skrót (stosowany najczęściej tam gdzie nie używa się zegara 24 godzinnego – na przykład w USA) oznaczający przed południem (PM) i po południu (AM):
Możemy zamiast instrukcji if użyć wyrażenia if. Jest to konstrukcja języka Python pozwalająca w miejsce wartości wpisać sposób jej wyliczenia z użyciem instrukcji takich jak if lub for.
Objaśnienie
Najpierw podajemy wartość, potem warunek zapewniający, że ta wartość będzie użyta, a następnie – alternatywę. Zwróć uwagę na to, że nie używa się w tym wypadku dwukropka po słowach kluczowych if i else.
Taką konstrukcję można użyć na przykład do odmiennego obsłużenia wartości pustej. Na przykład:
W powyższej instrukcji warto zwrócić uwagę na zapis „symbol is None”. Jest on równoważny z „symbol == None”, ale bardziej czytelny. Symbol pusty (None) ma swoją interpretację w pamięci komputera, ale stosując operator „is” (jest) lub „is not” (nie jest) abstrahujemy od tego. Ważne jest także to, że taka konwencja (is / is not) jest stosowana w bazach danych (SQL), gdzie nie można stosować zwykłych porównań z symbolem pustym (rolę „None” pełni tam „null”).
Konstrukcji wyrażenia if nie można komplikować (tak jak w zwykłej instrukcji warunkowej). Chodzi wyłącznie o wybór z dwóch wartości. Nie możemy nawet łączyć wyrażenia if z wyjątkami. Taki zapis NIE JEST POPRAWNY:
Wyrażenia for
Podobnym do wyrażenia if jest tworzone na bazie pętli wyrażenie for. Stosuje się je do generowania list. Podobnie jak w wyrażeniu if najpierw podajemy wyrażenie wyliczające wartości, a później iterację (for) pozwalającą wygenerować wiele tych wartości.
Na przykład listę kwadratów liczb od 0 do 9 można zapisać:
wynik:
Objaśnienie:
Utworzono dwie listy używając wyrażeń for.
Aby zilustrować problem komplikacji załóżmy, że chcemy wygenerować sześć przypadkowych liczb wskazujących kierunek ruchów pionka po szachownicy warcabów (-1 = w lewo, +1 = w prawo).
Możemy to zapisać:
Funkcja choice z modułu random dokonuje przypadkowego wyboru z listy podanej jako parametr (tutaj: [-1,1]). Ale co zrobić, aby uniknąć wyjścia poza szachownicę (x<1 lub x>8)? Możemy zapisać to następująco:
Tylko czy to jest na pewno uproszczenie? Użyta w poprzednim przykładzie instrukcja zwiększania wartości x jest poprawna, ale zbyt złożona. A przecież można zapis jeszcze bardziej skomplikować:
Mamy kod krótki ale nieprzejrzysty. Zdecydowanie bardziej przejrzyste jest tutaj zastosowanie instrukcji for, a nie wyrażenia for:
Kod dłuższy ale przejrzysty (bardziej „rzadki”).
Anonimowe funkcje lambda
W jednym wierszu można także zdefiniować funkcję! Taka anonimowa (pozbawiona nazwy) funkcja często występuje w połączeniu z wyrażeniem for.
wynik:
Objaśnienie:
W jednym wierszu zdefiniowano funkcję (lambda) i użyto ją do wygenerowania wartości elementów listy.
Nie jest to zbyt przejrzyste. Można to uprościć zapamiętać funkcję w zmiennej (kojarząc ją w ten sposób z nazwą).
Tylko czy nie prościej po prostu zdefiniować zwykłą funkcję?
Uproszczenie instrukcji warunkowych
Jak już wspomniano (przy okazji opisu wartości logicznych różnych danych) – zaleca się unikać operatorów porównania tam, gdzie są zbędne.
Czasem w celu osiągnięcia bardziej przejrzystego kodu można nawet podzielić wyrażenie na kilka – wprowadzając zmienne do zapamiętania wyniku częściowego.
Zamiast
Zapisz:
Na przykład algorytm rozrzucania pionków od warcabów na planszy:
Objaśnienie
Pierwsza część warunku (zob. wiersz komentarza) sprawdza, czy pole jest czarne. Użycie zmiennej nie tylko upraszcza warunek, ale pozwala zachować informację o polu i stanowi dodatkowe objaśnienie (nazwa zmiennej).
Eliminacja zbędnych pętli przy pomocy funkcji wbudowanych
W Pythonie mamy kilka funkcji wbudowanych, działających na kolekcjach:
- any() - jeden z elementów sekwencji jest równoważny True
- all() - wszystkie elementy sekwencji
- min() - najmniejszy element
- max() - największy element
- sum() - suma elementów
Ich używanie pozwala uniknąć zbędnych pętli.
Korzystaj z wyjątków!
Często w programach sytuacja wyjątkowa jest generowana próbą użycia nie istniejącego obiektu (na przykład element listy o indeksie poza zakresem). Zaleca się zatem (ale nie w Pythonie), aby sprawdzać przed użyciem (LBYL = “Look Before You Leap”). Alternatywą jst zasada: Prościej spróbować niż sprawdzać (EAFP = “It’s Easier to Ask for Forgiveness than Permission”).
Oczywiście można to połączyć z blokami else (inny wyjątek) i finally (wykonywanymi na koniec instrukcji):
Jak importować
Chcąc zaimportować większą ilość identyfikatorów z jakiegoś modułu możemy teoretycznie wykonać:
To nie jest zalecane, ale zazwyczaj działa. Gwiazdka oznacza wszystko co jest do zaimportowania. Czyli nie wiemy co konkretnie. Dochodzi do bałaganu (zaśmiecenia przestrzeni nazw). Czasem może spowodować zmianę znaczenia identyfikatorów, jakiej się nie spodziewamy.
Jeśli chcesz zaimportować wiele nazw, to już lepiej importować cały moduł. Wewnętrzne obiekty będą dostępne jeśli poprzedzimy je nazwą modułu:
Jeśli nazwa modułu jest długa, możemy zastosować alias (pseudonim):
Programowanie obiektowe
Prawdziwie wielkie możliwości i swobodę w komplikowaniu lub upraszczaniu kodu daje nam jednak dopiero zastosowanie klas i obiektów. Ta swoboda jest na tyle duża, że stworzono cały szereg zaleceń ograniczających jej nadużywanie. Na szczęście Python zen Pythona sprawia, że część z tych zasad nie ma większego znaczenia i nie musimy sobie nimi zaprzątać głowy.
Na początek zobaczmy jak można obiektowo rozwiązać opisane wcześniej zadanie generowania ruchów pionka (dbając równocześnie, aby pionek nie wypadł poza planszę).
Klasę Pionek można umieścić w odrębnym module i zaimportować go w dowolne miejsce w jakim będzie używana. Czy jednak projektując taką klasę nie warto by umieścić w niej innych atrybutów do wykorzystania w przyszłości? Na przykład czy pionek jest zbity, albo jego kolor? Nie!
Ta zasada postępowania została nazwana YAGNI (skrót od „You ain’t gonna need it”). Czyli: nie pisz kodu który aktualnie nie jest wymagany – bo może się przydać. Napiszesz gdy będzie potrzebny – wtedy na pewno będzie lepiej pasował do tych potrzeb. Zastosowanie ma tu też reguła KISS (od ang. Keep It Simple, Stupid), czyli „nie komplikuj, głuptasku”.
SOLID i abstrakcje
Zalecenia przejrzystego programowania obiektowego (nie tylko w Pythonie) określone zostały akronimem SOLID. Nazwa ta kryje 5 zasad (przypominamy: zasady a nie bezwzględnie obowiązujące reguły!):
- SRP - Single responsibility principle (zasada jednej odpowiedzialności)
- OCP - Open/closed principle (otwarty na rozszerzenia ale zamknięty na zmiany)
- LSP - Liskov substitution principle (zasada podstawienia Liskov)
- ISP - Interface segregation principle (zasada segregacji interfejsów)
- DIP - Dependency inversion principle (zasada odwrócenia zależności)
SRP
Weźmy banalny przykład:
Czy za każdym razem gdy chcemy przesunąć pionek, musimy ustawiać kolor? Unikniemy tego rozdzielając funkcje tak, aby każda miała jedną odpowiedzialność:
OCP
Obiekty powinny być tak napisane, aby łatwo było je rozszerzać bez konieczności grzebania w kodzie. Preferuje się zatem dziedziczenie obiektów i dekoratory. W Pythone dotyczy to głównie używania pakietów bibliotecznych. Rozważmy dwie strategie:
- zainstaluj, zaimportuj, stwórz obiekt pochodny
- skopiuj, zmodyfikuj, użyj
Zdecydowanie wybieramy pierwszą z nich. To tylko przykład zastosowania zasady OCP. W dużych projektach opartych na pracy zespołowej może mieć ona szersze zastosowanie.
Aby zilustrować tą zasadę zastosujmy drugą z powyższych strategii (dziedziczenie) dla prostego obiektu pionka.
LSP
W większości przypadków algorytm powinien działać bez względu na to, jak zaimplementowano szczegóły poszczególnych funkcji. Możemy zatem w miejsce obiektu wstawić obiekt pochodny i algorytm powinien działać. Oczywiście zachodzi to także w drugą stronę: program który działa dla określonych obiektów, zadziała również dla abstrakcji – czyli obiektów bez zaimplementowanych konkretów. To jest właśnie zasada podstawienia (LSP). W powyższym przykładzie z pionkami możemy zaimplementować losowe ruchy warcabów:
Przykładowe wyjście
Objaśnienie
Generowanie współrzędnych zadziała poprawnie niezależnie od tego, czy użyjemy obiektu klasy AbsPionek czy PionekWarabow. Dla AbsPionek wygeneruje pustą listę. Dla klasy PionekWarcabow wygeneruje ruchy pionka warcabów (drugi wiersz wyniku).
Warto zauważyć, że metoda ruch generuje w klasie abstrakcyjnej wyjątek. Nie da się jej więc użyć tak jak losowe_ruchy. Taka implementacja bez użycia (tylko generująca wyjątek) nie jest też zgodna z zasadami zen Pythona. Uzasadnieniem dla niej byłoby tylko użycie w innej metodzie (jak losowe_ruchy).
W tym miejscu dotykamy bardzo istotnej kwestii związanej z przemysłową produkcją oprogramowania. Załóżmy, że ktoś pisze funkcję drukowania faktury a w zaimportowanym obiekcie nie ma danych dla jednego z obowiązkowych pól. Co powinien zrobić? To zależy od organizacji pracy, a nie tylko ogólnych reguł takich jak SOLID.
ISP, DIP
Te zasady mają szersze zastosowanie w językach w których istnieje silniejsze rozróżnienie interfejsu i klasy. Najprościej rozumieć interfejs jako klasę bez implementacji (z samymi nagłówkami metod). Programista Pythona nie musi sobie tym zbytnio głowy zaprzątać. Warto jednak wiedzieć skąd się to wzięło.
ISP to zasada jest podobna do zasady jednej odpowiedzialności. Zaleca ona rozdrobnienie funkcjonalności tak, by użycie potrzebnego zbioru metod nie wiązało się z dostępem do reszty.
DIP z kolei zaleca korzystanie raczej z interfejsów niż konkretnych klas. W Pythonie można to zredukować do zalecenia,aby wspólne elementy dla klasy bazowej i pochodnej wyodrębnić w trzeciej klasie (interfejsu). Załóżmy, że chcemy mieć metodę losowego przesunięcia pionka. Poniżej przykladowa implementacja:
Przykładowy wynik
Objaśnienie
Wykorzystując możliwość dziedziczenia z kilku klas jednocześnie, stworzony została klasa (PionekWarcabow) dziedziczący z klas IPionek i AbsPionek. Pierwsza z nich nie ma żadnej implementacji, a jedynie zdefiniowaną metodę losuj (interfejs). Zastosowanie (zgodnie z DIP) interfejsu rzeczywiście upraszcza metodę losowe_ruchy, ale musimy dodatkowo zaimplementować funkcję losuj.
Jak widać daje nam to niewiele i dyskusyjną kwestią jest to, czy nasz kod jest prostszy. Inaczej wyglądałoby to, gdyby metody w interfejsach nie wymagały jakiejkolwiek implementacji (choćby wygenerowania wyjątku).
Kontekst
W obiektach często przechowuje się kontekst przetwarzania danych. Kontekstem może być otwarte połączenie do bazy danych, otwarty plik lub połączenie internetowe. W Pythonie istnieje standardowy sposób obsługi kontekstu. Jest to instrukcja with, która była już opisywana w tym podręczniku. Korzystanie z tej instrukcji czyni kod bardziej czytelnym, dlatego jest to zalcane. Na stronie http://book.pythontips.com/en/latest/context_managers.html znajdują się informacje pomocne w tworzeniu menadżerów kontekstu innych niż standardowe.
Pamiętaj, że wszystko jest obiektem
Powyższe zalecenia dotyczyły głównie obiektów jako składowych przy pomocy których budujemy funkcjonalność programu. Czyli obiekt jest traktowany podmiotowo, a dane jakie przetwarza przedmiotowo. W języku Python wszystko jest obiektem – także dane. To właśnie bogactwo środków manipulowania obiektami jest jedną z cech wyróżniających ten język.
Kiedy definiować własne obiekty i metody?
Słowniki, krotki i listy to kolekcje obiektów, które same są obiektami. Obiekty w programach tworzą często bardziej złożone hierarchie (nie tylko na zasadzie obiekt – kolekcja obiektów). Rodzą się więc pytania w rodzaju: kiedy tworzyć własne obiekty, a kiedy korzystać z wbudowanych? Można sformułować kilka zaleceń, które ilustruje poniższy przykład:
Sprawdzanie obecności klucza w słowniku
Obiekt słownika zawiera metodę sprawdzenia, czy zawiera wartość dla
danego klucza (has_key()
) . Jednak jej używanie nie jest
zalecane. Lepiej użyć metody get z wartością domyślną (drugi parametr):
W tym ostatnim przypadku wykorzystujemy fakt, że in w przypadku słowników odnosi się do kluczy.
Używaj in gdy to tylko możliwe
Operator in pozwala na sprawdzenie, czy obiekt znajduje się w kolekcji (liście, zbiorze, słowniku), z tym że w przypadku słowników sprawdza się klucze (zob. wyżej). Ale to nie jest jedyne zastosowanie operatora in. Najczęściej używa się go w pętli for. Takie rozwiązanie jest preferowane nawet wówczas, gdy na przykład potrzebujemy indeksu (numeru w liście).
Czasem potrzebujemy równocześnie dostępu do elementu listy i jego kolejności (indeksu).
Na przykład mamy nazwy pierwszych czterech cyfr i chcemy je drukować.
Wyjście (ostatni wiersz):
We wcześniejszym przykładzie z kolorami:
Operator in zastępuje także wyszukiwanie (find) w łańcuchach znaków:
Łączenie list w łańcuch
Teoretycznie napis (łańcuch) to lista znaków. Jednak to nie są tożsame typy danych. Chcąc zamienić listę na łańcuch znaków posługujemy się metodą join (połącz) obiektu string:
Teoretycznie można używać do łączenia łańcuchów operatora dodawania (+). Jednak nie jest to efektywne rozwiązanie, więc nie jest zalecane.
Funkcja (metoda) join ma dodatkowy parametr określający separator. Oto przykład jego zastosowania:
Oczywiście można też jako parametr funkcji jon użyć wyrażenia for:
Listy na słownik i z powrotem
Do łączenia dwóch list w słownik służy funkcja zip. Pierwszym parametrem jest lista kluczy, a drugim lista wartości:
Wynik:
Nie jest ani zalecane ani potrzebne używanie pętli:
Unikaj funkcji map i filter
Mamy dwie funkcje pozwalające tworzyć listy na podstawie innych list
- map()
- filter()
Ich użycie polega na tym, że dla każdego elementu wykonuje się funkcję wyliczającą sprawdzającą, czy element ma się znaleźć w nowej liście (filter) lub zwraca element nowej listy odpowiadający przetwarzanemu elementowi listy starej (map). Trochę to zawiłe, ale przykład wyjaśni wszystko:
Zarówno filter jak i map korzysta z funkcji lambda, które jak już zaznaczono nie są zalecanym elementem języka.
Można bez nich obejść się i tym razem. Zamiast funkcji map i filter można użyć wyrażenia for:
Unikaj magii
Programiści mówią o „magii”, gdy wykorzystane są mechanizmy inne niż zawarte w standardzie języka. W Pythonie na przykład mamy dostęp do różnych parametrów interpretera. Najczęściej korzystamy ze zmiennej __name__\, która zawiera nazwę modułu. Jest to wykorzystywane do tego aby uwzględnić uruchamianie skryptu zarówno jako odrębnego programu jak i modułu w innym programie. To akurat jest rodzaj „magii” powszechnie akceptowany (a nawet zalecany).
Uzględnij możliwość wykonania i importu skryptu
Uruchomienie programu (głównego skryptu) powoduje ustawienie w zmiennej __name_\ wartości ‘_\main__'.
Przykład:
Testujemy:
[v4]
Kilka ważnych kwestii na zakończenie…
Python 2.x czy Python 3.x ?
Którą wersję Pythona wybrać? Najczęściej jesteśmy uwarunkowani dostępnymi bibliotekami. Najpopularniejsze wersja to 2.7 oraz 3.x. Między nimi zachodzi kilka różnic1. Najważniejsze spośród nich2:
- Instrukcja print staje się funkcją. Nie można więc już w Pythonie 3 napisać: print 12 można natomiast – tak jak dotąd: print(12)
- Iterator range wykorzystywany w pętli for zwracał w Pythonie 2 listę. W Pythonie 3 jest to obiekt zwracający kolejne elementy (taką funkcję pełni w Pythonie 2 xrange). Podobnie jest z funkcją globalną zip, która w Pythonie 2 zwracała listę, a w Pythonie 3 zwraca iterator (generator).
- W Pythonie 2 do wprowadzania danych służyło input (gdy zachęta była w podwójnych cudzysłowach zwracał napis; inaczej – zależnie od tego wprowadzono). Natomiast raw_input zawsze zwracał łańcuch (string). W Pythonie 3, funkcja raw_input () jest przestarzała, a input zawsze zwraca ciąg znaków (string).
- Dzielenie liczb w Pythonie 2 zależy od tego, czy liczba
jest całkowita. Całkowite (integer) zaokrągla w dół.
5.0/2 == 2,5
5/2 == 2
W Pythonie 3 dzielenie bez reszty jest zmiennoprzecinkowe.
- Inaczej rozwiązano znaki narodowe (w Pythonie 3 napisy są w Unicode – tej kwestii poświęcimy odrębny podrozdział).
- W Pythonie 3 zyskujemy większe możliwości formatowania wyjścia na konsolę.
- Kilka konstrukcji syntaktycznych dopuszczalnych w Pythonie 2 stało się w Pythonie 3 obowiązkowych (zob. dalej).
Środowisko wirtualne
Możesz równocześnie mieć dwa środowiska pracy z dwoma różnymi wersjami języka Python. Środowisko definiuje się programem virtualenv, który musimy mieć zainstalowany.
Do instalowania może nam posłużyć program pip. Czyli z konsoli Linux wykonujemy (w Windows pip standardowo jest dostępny):
Sama instalacja wirtualnego środowiska przebiega następująco.
Gdzie ~/v27py podkatalog v27py katalogu domowego użytkownika.
Ostatni wiersz to aktywacja środowiska wirtualnego. Od tej pory konsola zgłasza się nazwą środowiska w nawiasach okrągłych. Na przykład:
Polecenie source jest wewnętrznym poleceniem powłoki Linux (czyli jej wykonanie nie wymaga uruchamiania dodatkowego programu poza interpreterem).
W systemie Windows wykonamy to samo nieco innymi komendami:
Mamy kompletne lokalne środowisko, z ścieżkami dostępu. Możemy doinstalować brakujące pakiety poleceniem pip.
Jeśli nazwy tych pakietów mamy przygotowane w pliku requirements.txt, to instrukcja instalacji ich wszystkich wygląda następująco:
Kompatybilność
Aby zachować kompatybilność (zgodność, program będzie działał zarówno w wersji 2.x jak i 3.x), używaj zawsze konstrukcji, które są obowiązkowe w Pythonie 3:
- Nie jest dłużej dopuszczalny zapis obsługi wyjątku bez
słowa kluczowego as. Piszemy więc
try: except Exception as e:
- Nie dopuszcza się stosowania porównania (relacji „różny”) <> jako synonim do !=. Stosuj więc zawsze zapis !=.
Ponadto używaj:
- print() jako funkcji do drukowania danych
- input() - z zachętą w podwójnych cudzysłowach do wprowadzania danych
- range a nie xrange
Możesz też użyć modułu __future__ lub six do zapewnienia kompatybilności do przodu (czyli dodać do Pythona 2 funkcjonalność funkcjonalność Pythona 3). Wówczas uzyskasz możliwość dzielenia i formatowania takiego jak w Python 3. Na przykład:
Poniżej przykład który uruchomiony w Pytonie 2 pokazuje różnice między wersjami:
Przykład:
Wyjście:
Objaśnienie:
Początek przykładu pokazuje wyjście instrukcji print oraz dzielenia w Pythonie 2. Dalszy fragment – po zaimportowaniu __future__ daje wynik taki, jakbyśmy używali Pythona 3.
Polskie znaki
Stosowanie tekstów zawierających inne litery niż łacińskie (w polskim języku ą, ę , ł itd….) bywa przyczyną wielu problemów. Dla programistów zaczynających przygodę z Pythonem może to być zaskoczenie: program przerywa się napotykając „polską” literę. Jednak bardzo restrykcyjne podejście do zasad kodowania takich znaków wymusza poprawne używanie napisów.
Przede wszystkim należy konsekwentnie stosować kodowanie UTF8 (Unicode). Ono nie jest domyślne i dlatego rozpoczynamy moduł od zapisu:
Dzięki temu możemy w stałych i komentarzach stosować literki z ogonkami…
W tym miejscu trzeba zwrócić uwagę na to, że w Pythonie3 łańcuchy znaków są domyślnie kodowane w Unicode, a w Pythonie 2 tylko wtedy, gdy poprzedzimy je literką u:
Poza użyciem konstrukcji u”” (unicode) widzimy w tym przykładzie użycie b”” - co oznacza zapis „binarny” łańcucha (taki jakim jest – bez konwertowania na Unicode). Widać też, że domyślnie napis jest binarny i użycie go jako unicode powoduje błąd. Aby uniknąć błędu – musimy zdekodować napis na napisu z polskimi znakami przy pomocy metody decode (parametrem jest sposób kodowania – gdyż nie jest zdefiniowane domyślne kodowanie). Dostępna jest także odwrotna metoda (encode).
W Pythonie 3 mamy odwrotną sytuację – domyślnie napisy nie są binarne:
Rozwiązanie z Pythona 3 jest dużo wygodniejsze – bo konsekwentne stosowanie Unicode pozwala zapomnieć o problemach z polskimi znakami. Można to rozwiązanie stosować w Pythonie 2 przy __future poznanego wcześniej:
Nawet po przejściu na Python3 trzeba jednak pamiętać o kodowaniu binarnym – na przykład przy tworzeniu szyfrów (identyczny napis da różny kod zależnie od szyfrowania) lub gdy napis w bazie danych jest z innym kodowaniem (wówczas trzeba odpowiednio skonfigurować połączenie do bazy i ewentualnie umiejętnie stosować przy odczycie / zapisie code i decode).
Uwagi dla osób programujących w innych językach
Łatwość programowania w języku Python jest związana między innymi z tym, że do rozwiązywania każdego problemu używa się możliwie najprostszych konstrukcji. Celem programisty jest precyzyjnie opisać co ma być zrobione w programie. Nic więcej. Stąd pomysł na odmienną niż w innych językach konstrukcję podstawowej pętli (for).
Chcąc wykonać jakąś operację na trzech elementach w większości języków napiszemy cos w rodzaju:
Czyli musimy użyć indeks i – nawet gdy nie jest nam potrzebny. W Pythonie piszemy:
Indeks i wprowadzamy dopiero, gdy jest niezbędny (przy pomocy generatora range).
Bardzo rzadko używa się pętli innych niż for. Ma to dodatkową zaletę: program możemy czytać sekwencyjnie. Bo przecież taka pętla to tylko zapis równoważny z „zrób coś dla wszystkich elementów zbioru”. Nie ma problemu „zapętlenia” (o ile zbiory nie są nieskończone).
Pozostałe różnice nie są tak znaczące, ale przechodząc do programowania w Pythonie, warto zdawać sobie z nich sprawę.
Typy danych
Jeśli chcemy dodać dwie zmienne, to muszą one być zgodnych typów. Błędne wyrażenie:
Taki sposób typowania (wiązania zmiennej z typem danych nazywa się silnym.
Poza wiązaniem silny/słaby rozróżniamy wiązanie statyczne (typ zmiennej się nie zmienia) i dynamiczne (zmienna przyjmuje typ wartości która do niej wstawiamy). W Pythonie mamy wiązanie dynamiczne. To się wykona poprawnie (ciąg dalszy poprzedniego przykładu):
Typowanie | Słabe | Silne |
---|---|---|
Statyczne | C, C++ | Java, Pascal |
Dynamiczne | Perl, VB | Python |
Typowanie statyczne odbywa się poprzez deklarację (jakiego typu zmienna ma być). Dynamiczne typowanie odbywa się poprzez użycie (zależy jaką daną wstawimy inicjując zmienną).
Wśród prostych typów trzeba zwrócić uwagę na:
- logiczne True i False - równoważne wartości 1 i 0 odpowiednio;
- napisy w wielu wierszach (różnią się trzema apostrofami zamiast jednym);
Typy złożone (listy, krotki) nie są płaskie ani jdnordne! Mogą w liście znajdować się elementy różnych typów – w tym inne listy!
Przekazywanie zmiennych do funkcji
Rozwiązanie Pythona jest tyleż intuicyjnie proste, co nietypowe. Jeśli typ zmiennej jest prosty – to jeśli użyjemy jej jako argumentu funkcji - przekazywana jest wartość (tak samo nazywająca się zmienna wewnątrz funkcji jest inną zmienną niż ta zewnętrzna). W przypadku danych złożonych przekazywany jest wskaźnik.
Pewnym „dziwactwem” języka jest inicjowanie parametrów domyślnych funkcji chwili definiowania funkcji, a nie jej wywołania Załóżmy, że potrzebujemy funkcji, która albo dodaje przekazany element do listy przekazanej w drugim parametrze, albo zwraca listę złożoną z jednego elementu:
Ten przykład jest nieco trudniejszy od innych w tym podręczniku – ale warto go przeanalizować, by uniknąć błędów i lepiej zrozumieć specyfikę języka. Przy pierwszym wywołaniu funkcji z jednym tylko parametrem użyta zostanie wartość domyślna (pusta lista). Tu nie ma zaskoczenia:
Jednak przy drugim wywołaniu domyślną wartość już mamy nadaną, a ponieważ jest to zmienna złożona mamy niespodziankę:
Właściwy sposób, aby otrzymać listę domyślną (tu: pustą) przy braku parametru jest stworzenie jej w czasie wykonywania - wewnątrz funkcji:
Przy typach prostych nie ma takiego problemu:
Sprawdzamy:
Instrukcje warunkowe
W Pythonie nie ma instrukcji przełączania switch (case). Można użyć w to miejsce
Klauzula else jest także dostępna w pętlach while, ale nie zaleca się używania takiej konstrukcji (innego użycia else niż jako części if).
Polecane linki
1 http://www.w3ii.com/pl/python3/python3_whatisnew.html http://www.diveintopython3.net/porting-code-to-python-3-with-2to3.html
2W zob. też http://python-future.org/compatible_idioms.html).
[v2]