Instant Hacking
Nauka programowania na przykładzie Pythona
Magnus Lie Hetland
tłum. Wojciech Warczakowski
Jest to krótkie wprowadzenie do sztuki programowania,
z wykorzystaniem przykładów napisanych w języku
Python. (Jeśli już wiesz jak programować, a potrzebujesz krótkiego
wprowadzenia do języka Python, możesz skorzystać z mojego artykułu Instant
Python.) Artykuł ten został przetłumaczony na język włoski i obecnie
trwa tłumaczenie wersji koreańskiej.
Strona ta nie traktuje o włamywaniu się do systemów komputerowych
innych ludzi. Jeśli jesteś zainteresowany tym tematem zobacz stronę Happy Hacker.
Uwaga: Aby zamieszczone przykłady działały poprawnie pisz je do
pliku tekstowego a następnie uruchamiaj interpreterem; nie próbuj
uruchamiać programów w trybie interaktywnym - nie wszystkie będą działać.
(Nie pytaj mnie dlaczego. Sprawdź
dokumentację).
(Pochwała
za artykuł. Main
Python page)
1. Środowisko
Aby móc programować w Pythonie musisz mieć zainstalowany interpreter.
Jest on dostępny na wielu platformach (włącznie z Macintoshem, Uniksem i
Windows). Więcej informacji na ten temat znajdziesz na stronie Pythona. Powinieneś również mieć
edytor tekstowy (np. emacs, notepad lub podobny).
2. Czym jest programowanie?
Programowanie komputera oznacza przekazywanie mu zestawu instrukcji
mówiących co powinien zrobić. Program komputerowy bardzo przypomina
przepis na ciasto. Przykład [1]:
Ciasto: 30 dag (1 1/2 szklanki) mąki 13 dag margaryny 5 dag (3 łyżki) cukru 4 żółtka
Masa serowa: 50 dag sera twarogowego tłustego 20 dag (niepełna szklanka) cukru 13 dag margaryny 4-5 jaj 2 dag (1 łyżka) kaszy manny cukier waniliowy lub esencja zapachowa
Margarynę posiekać nożem z mąką, a następnie połączyć z żółtkami i cukrem. Ciasto szybko wyrobić ręką i schłodzić w lodówce (co najmniej 30 min). Schłodzone ciasto rozwałkować na grubość 1/2 cm i przenieść wałkiem na blachę. Ciasto lekko podpiec w gorącym piekarniku (15-20 min). Przyrządzić masę serową: ser przepuścić przez maszynkę. Margarynę utrzeć z cukrem, żółtkami, kaszą manną i stopniowo dodawanym serem. Ubić sztywną pianę z białek i delikatnie połączyć z masą serową. Masę wyłożyć na podpieczony spód i piec 30-40 min w umiarkowanie gorącym piekarniku.
Oczywiście, żaden komputer tego nie zrozumie... A większość komputerów
nie byłaby zdolna do zrobienia sernika nawet, jeśli zrozumiałyby
ten przepis. Zatem, co musimy zrobić, aby przepis stał się bardziej
przyjazny dla komputera? Zasadniczo dwie rzeczy. Musimy (1) mówić w taki
sposób, aby komputer nas zrozumiał, i (2) mówić o rzeczach, które jest on w
stanie wykonać.
Pierwszy punkt oznacza, że musimy użyć języka - języka
programowania zrozumiałego dla naszego interpretera (tłum. programu
tłumaczącego), a drugi punkt oznacza, że nie możemy oczekiwać, aby
komputer zrobił sernik - ale możemy oczekiwać, aby dodawał
liczby i wypisywał różne teksty na ekranie.
3. Witaj...
Tradycją jest, aby w tutorialach o programowaniu zaczynać zawsze od
programu wypisującego na ekranie "Witaj, świecie!". W Pythonie jest to dość
proste:
print "Witaj, świecie!"
Zasadniczo program ten jest podobny do przedstawionego wcześniej przepisu
na sernik (oczywiście jest on znacznie krótszy!). Program mówi
komputerowi, co ma zrobić: wypisać na ekranie "Witaj, świecie!". Łatwizna.
A jeśli chcielibyśmy, aby komputer wykonał coś trudniejszego.
print "Witaj, świecie!" print "Żegnaj, świecie!"
Niezbyt trudne, nieprawdaż? I niezbyt interesujące... Chcemy zrobić
coś ze składnikami, tak jak w przepisie na sernik. No
dobrze - jakie mamy składniki? Mamy ciągi znaków, jak "Witaj,
świecie!", ale również mamy liczby. Zlećmy, więc
komputerowi, aby obliczył nam pole prostokąta. Musimy, więc dać mu przepis
jak powinien to wykonać:
szerokosc = 20 wysokosc = 30
pole = szerokosc*wysokosc print pole
Widzisz zapewne podobieństwo (choć niewielkie) do przepisu na sernik.
Ale w jaki sposób działa ten program? Po pierwsze, linie rozpoczynające się
nazywane są komentarzem
i są ignorowane przez komputer. Jednakże, użycie takich komentarzy jest
niezwykle ważne dla ludzi, którzy analizują twoje programy.
Linie takie jak foo = bar nazywane są
przypisaniami. Poprzez wyrażenie szerokosc = 20
mówimy komputerowi, że szerokosc od tej chwili będzie równe 20. Co znaczy, że
"szerokosc jest równe 20"? Znaczy to, że zostanie utworzona (bądź ponownie
wykorzystana, jeśli już istnieje) zmienna o nazwie "szerokosc" i
zostanie jej nadana wartość 20. Zatem, jeśli później użyjemy tej zmiennej
komputer będzie już znał jej wartość.
Dlatego
szerokosc*wysokosc
jest w zasadzie tym samym co
20*30
które z kolei równe jest 600. Wartość ta następnie zostaje przypisana
zmiennej "pole". Ostatnie wyrażenie programu wypisuje na ekran wartość
zmiennej "pole"
600
Uwaga: W niektórych językach już na początku programu
musisz powiedzieć komputerowi jakich zmiennych będziesz używał (jakich
składników użyjesz do przygotowania sernika) - w Pythonie zmienne
możesz wymyślać w trakcie pisania programu.
4. Komunikacja z komputerem
Teraz możesz już przeprowadzać zarówno proste, jak i dość zaawansowane
obliczenia. Na przykład, chcesz obliczyć pole koła zamiast prostokąta:
promien = 30
print promien*promien*3.14
Jednak program ten, podobnie jak poprzedni, jest mało interesujący -
przynajmniej moim zdaniem. Jest on mało elastyczny. No bo, co będzie jeśli
interesujące nas koło ma promień 31 stopni? Czy komputer może o
tym wiedzieć? Sytuacja przypomina trochę przepis na sernik: "Masę serową
wyłożyć na podpieczony spód i piec 30-40 min. w umiarkowanie gorącym
piekarniku." Skąd możemy wiedzieć czy po 30 minutach sernik jest już
upieczony? Musimy to sprawdzić.
W jaki sposób komputer dowie się o promieniu naszego koła? Musi nas o to
zapytać... My zaś musimy w programie powiedzieć mu, aby zapytał się o promień:
promien = input("Jaki jest promień?")
print promien*promien*3.14
input jest funkcją. (Niedługo nauczysz się
tworzyć własne funkcje. input jest funkcją wbudowaną w język
Python.) Zwykłe napisanie
input
nie zrobi wiele... Musisz wstawić parę nawiasów na końcu słowa input.
Zatem input() powinno zadziałać - po prostu funkcja będzie
czekać, aż użytkownik wpisze promień. Jeśli w nawiasy funkcji
wpiszemy pytanie "Jaki jest promień?" program stanie się przyjaźniejszy
dla użytkownika. To co wpisujemy w nawiasy funkcji nazywane jest
parametrami. Wpisanie pytania do funkcji input
powoduje wypisanie tegoż pytania na ekranie i oczekiwanie odpowiedzi od
użytkownika.
Ale w jaki sposób odpowiedź trafia do zmiennej promien?
Funkcja input, kiedy zostanie wywołana, zwraca
wartość (tak jak inne funkcje). Nie musisz wykorzystywać
tej wartości, jednak w naszym przypadku chcemy z niej skorzystać.
Zatem poniższe dwa wyrażenia mają całkiem różne znaczenie:
foo = input
bar = input()
foo zawiera funkcję input dosłownie ją samą
(zatem możesz użyć takiego wyrażenia foo("Ile masz lat?");
takie zjawisko nazywane jest dynamicznym wywołaniem funkcji), podczas
gdy bar zawiera to, co wpisze użytkownik jako odpowiedź.
5. Sterowanie
Możemy już pisać programy wykonujące proste działania (arytmetyczne i
drukujące) jak również komunikujące się z użytkownikiem. Jest to
użyteczne, jednak nadal jesteśmy ograniczeni do tzw. sekwencyjnego
wykonania programu - rozkazy w programie wykonywane są w określonym
porządku. Przepisy na ciasta są pisane właśnie w takim sekwencyjnym lub
liniowym porządku. A gdybyśmy chcieli powiedzieć komputerowi w jaki sposób
powinien sprawdzić, czy sernik jest już upieczony? Jeśli jest, powinien go
wyciągnąć z piekarnika, w przeciwnym razie powinien go pozostawić w
piekarniku na kolejne pięć minut. W jaki sposób to zrobimy?
To czego potrzebujemy nazywa się sterowaniem programem.
Program może wykonać dwa działania - albo wyciągnąć sernik z piekarnika,
albo pozostawić go. Możemy wybierać, a warunkiem jest, czy sernik
jest już upieczony, czy nie. Technika ta nazywana jest warunkowym
wykonaniem programu.
Program może wyglądać tak:
temperatura = input("Jaka jest temperatura sernika?")
if temperatura > 30: print "Sernik jest już upieczony." else: print "Musisz troszkę poczekać na sernik."
Znaczenie programu powinno być oczywiste: Jeśli temperatura jest wyższa
niż 30st.C, wówczas wypisz komunikat mówiący użytkownikowi, że sernik jest
już upieczony, w przeciwnym razie powiedz użytkownikowi, że musi jeszcze
troszkę poczekać.
Uwaga: Wcięcia w języku Python są ważne.
Bloki w wykonaniu warunkowym (i pętle oraz
definicje funkcji - patrz niżej) muszą mieć wcięcia (wcięcia z tą
samą liczbą białych znaków; jeden <tab> liczy się jako 8 spacji)
aby interpreter wiedział, w którym miejscu rozpoczynają się, a w którym
kończą. Wcięcią również czynią program bardziej czytelny dla ludzi.
Wróćmy do naszych obliczeń pola. Czy widzisz, co ten program robi?
print "Witamy w programie obliczającym pole" print "------------------------------------" print
print "Wybierz figurę:" print "1 Prostokąt" print "2 Koło"
figura = input("> ")
if figura == 1: wysokosc = input("Wpisz wysokość: ") szerokosc = input("Wpisz szerokość: ") pole = wysokosc*szerokosc print "Pole wynosi", pole else: promien = input("Wpisz promien: ") pole = 3.14*(promien**2) print "Pole wynosi", pole
Nowe rzeczy w tym przykładzie...
print użyte samo drukuje pustą linię
== sprawdza, czy dwie rzeczy są równe, w
przeciwieństwie do =, które przypisuje wartość z prawej
strony do zmiennej po lewej stronie. Jest to ważna różnica!
** jest pythonowskim operatorem potęgowania -
dlatego promień podniesiony do kwadratu jest zapisany jako
promien**2
print może wydrukować więcej niż jedną rzecz. Po
prostu oddziel te rzeczy przecinkami. (Na ekranie będą one
oddzielone pojedynczą spacją.)
Program ten jest dosyć prosty: Pyta o liczbę, która mówi mu, czy
użytkownik chce obliczyć pole prostokąta, czy koła. Następnie, korzysta z
if-wyrażenia (wykonanie warunkowe), aby zdecydować, który
blok wykorzystać do obliczenia pola. W zasadzie te dwa bloki są takie
same jak te, które używaliśmy w poprzednich przykładach. Zauważ, że
komentarze czynią kod bardziej czytelnym. Można powiedzieć, że pierwszym
przykazaniem programowania jest "Będziesz pisał komentarze!". W każdym
bądź razie - jest to pożądany nawyk.
Ćwiczenie:
Rozszerz program o opcję obliczania pola kwadratów, tak aby użytkownik podawał
długość jednego boku. Jest jedna rzecz, którą musisz wiedzieć, aby wykonać
to ćwiczenie - jeśli masz więcej niż dwa wybory, możesz zapisać to w taki
sposób:
if foo == 1: elif foo == 2: elif foo == 3: else:
elif jest tajemniczym kodem, który oznacza "else if"
:). Zatem, jeśli foo równa się jeden, zrób coś;
w przeciwnym razie, jeśli foo równa się dwa, zrób coś innego,
itd. Możesz chcieć dodać do programu również inne opcje - jak trójkąty lub
wielokąty. Wszystko zależy od ciebie.
6. Pętle
Sekwencyjne i warunkowe wykonania są tylko dwoma z
trzech fundamentalnych elementów budujących program. Trzecim jest
pętla. Proponowałem wcześniej rozwiązanie sprawdzające, czy
sernik upiekł się. Jednak nie było ono dosyć wystarczające. No bo, jeśli
sernik nie będzie gotowy, to jak następnym razem sprawdzimy czy już się
upiekł. Skąd będziemy wiedzieć ile razy musimy to sprawdzać? Prawda jest
taka, że nie będziemy wiedzieć. Powinniśmy móc zlecić komputerowi, aby
sprawdzał sernik do czasu, aż będzie on upieczony. W jaki sposób to
zrobić? Zgadłeś - użyjemy pętli, lub wykonania powtarzanego.
Python ma dwa rodzaje pętli: while-pętle i
for-pętle. For-pętle są chyba najprostsze. Na przykład:
for jedzenie in "ciasto", "jajka", "pomidory": print "Kocham", jedzenie
Co oznacza: dla każdego elementu w liście "ciasto", "jajka",
"pomidory" wypisz tekst, że go kochasz. Block wewnątrz pętli jest
wykonywany raz dla każdego elementu, i za każdym razem, bieżący element
jest podstawiany do zmiennej jedzenie (w tym przypadku). Inny
przykład:
for liczba in range(1,100): print "Witaj, świecie!" print "Tylko", 100 - liczba, "pozostało..."
print "Witaj, świecie" print "To był ostatni... Fju!"
Funkcja range zwraca listę liczb w podanym przedziale
(włącznie z pierwszą, bez ostatniej... W tym przypadku, [1..99]). Zatem,
parafrazując:
Zawartość pętli jest wykonywana dla (for) każdej liczby w
(in) przedziale (range) od (włącznie) 1 do
(bez) 100. (Wyjaśnienie tego, co robi ciało pętli i następne
wyrażenia pozostawiam jako ćwiczenie dla ciebie.)
Jednak to nam nie rozwiązuje naszego problemu z pieczeniem. Jeśli
chcemy sprawdzić sernik sto razy, wówczas to rozwiązanie byłoby na
miejscu, ale my nie wiemy czy sto razy wystarczy, czy może będzie to za dużo.
Po prostu chcemy sprawdzać sernik, kiedy nie jest on wystarczająco
gorący (lub do czasu, kiedy stanie się wystarczająco
gorący). Dlatego użyjemy while:
from time import sleep
print "Rozpocznij pieczenie sernika. (Wrócę za 3 minuty.)"
sleep(180)
print "Wróciłem :)"
wystarczajaco_goracy = 30
temperatura = input("Jak gorący jest sernik? ") while temperatura < wystarczajaco_goracy: print "Niewystarczająco gorący... Piecz jeszcze chwilę..." sleep(30) temperatura = input("OK. Jak gorący jest teraz? ")
print "Wystarczająco gorący - gotowe!"
Nowe rzeczy w tym przykładzie...
- Niektóre użyteczne funkcje przechowywane są w modułach i
mogą być importowane. W tym przykładzie importujemy funkcję
sleep (która zasypia na podaną liczbę sekund) z modułu
time dostarczanego razem z Pythonem. (Możliwe jest
również tworzenie swoich własnych modułów...)
Ćwiczenie 1
Napisz program sumujący liczby podawane przez użytkownika, do momentu aż
suma tych liczb osiągnie 100. Napisz inny program sumujący sto podanych
przez użytkownika liczb i wypisujący ich sumę.
7. Większe programy - abstrakcja
Jeśli chcesz przejrzeć zawartość książki, nie kartkujesz wszystkich
stron - zaglądasz tylko do spisu treści, prawda? Spis treści pokazuje główne
tematy książki. Teraz wyobraź sobie pisanie książki kucharskiej. Wiele
przepisów takich jak "Sernik na kruchym cieście" i "Sernik po wiedeńsku"
może zawierać podobne rzeczy, tak jak twaróg w tym przypadu - zapewne nie
chciałbyś w każdym przepisie powtarzać jak należy robić twaróg. (Ok...
Nie musisz robić żadnego twarogu... Bądź dla mnie wyrozumiały dla
dobra przykładu :)). Umieszczasz przepis na twaróg w
oddzielnym rozdziale, i po prostu odsyłasz do niego w pozostałuch
przepisach. Zatem, zamiast za każdym razem pisać ciągle cały przepis,
musisz tylko skorzystać z nazwy rozdziału. W programowaniu nazywamy to
abstrakcją.
Czy już mieliśmy z czymś takim do czynienia? Tak. Zamiast dokładnie
mówić komputerowi w jaki sposób powinien odebrać odpowiedź od użytkownika (OK -
rzeczywiście nie umielibyśmy tego zrobić... Ale nie umielibyśmy
również zrobić twarogu, zatem... :)) po prostu skorzystaliśmy
z funkcji input. Właściwie możemy również tworzyć nasze
własne funkcje, aby wykorzystać je w tego rodzaju abstrakcji.
Powiedzmy, że chcemy znaleźć największą liczbę całkowitą, która jest
mniejsza od podanej liczby dodatniej. Na przykład, dla liczby 2.7 byłoby
to 2. Takie coś nazywane jest "floor" danej liczby. (Właściwie można by
skorzystać z wbudowanej w Python funkcji int, ale znów, bądź
wyrozumiały dla mnie...) W jaki sposób zrobilibyśmy to? Prostym
rozwiązaniem byłoby sprawdzić wszystkie możliwości począwszy od zera:
liczba = input("Jaka jest liczba? ")
floor = 0 while floor < liczba: floor = floor+1 floor = floor-1
print "Floor liczby", liczba, "wynosi", floor
Zauważ, że pętla kończy się, kiedy floor już nie jest
mniejsze od liczba, a my dodajemy o jeden za dużo do niego.
Dlatego później musimy odjąć to jeden. A jeśli chcemy wykorzystać "floor"
w złożonym matematycznym wyrażeniu? Musielibyśmy pisać całą pętlę dla
każdej liczby, która potrzebuje być "sfloorowana". Niezbyt przyjemne...
Zapewne zgadłeś, co zamiast tego zrobimy - umieścimy wszystko w naszej
własnej funkcji, którą nazwiemy "floor":
def floor(liczba): wynik = 0 while wynik < liczba: wynik = wynik+1 wynik = wynik-1 return wynik
Nowe rzeczy w tym przykładzie...
- Funkcje definiujemy słowem kluczowym
def,
po czym podajemy nazwę funkcji i w nawiasach jej parametry.
- Jeśli funkcja powinna zwracać wartość, wykorzystujemy do tego słowo
kluczowe
return, które również automatycznie kończy
funkcję.
Jeśli mamy już zdefiniowaną funkcję, możemy ją wykorzystać w ten oto
sposób:
x = 2.7 y = floor(2.7)
Po wykonaniu naszej funkcji y powinno zawierać wartość 2.
Możliwe jest również definiowanie funkcji z więcej niż jednym parametrem:
def suma(x,y): return x+y
Ćwiczenie 2
Napisz funkcję wykorzystującą metodę Euklidesa znajdującą wspólny czynnik
dwóch liczb. Powinna ona działać w taki sposób:
- Masz dwie liczby, a i b, gdzie a jest większe od b
- Powtarzaj poniższe działania do momentu, aż b będzie równe zero:
- przypisz do a wartość b
- przypisz do b resztę z dzielenia a (przed zmianą) przez
b (przed zmianą)
- Zwróć ostatnią wartość a
Wskazówki:
- Użyj a i b jako parametrów do funkcji
- Załóż, że a jest większe od b
- Reszta z podziału
x przez z jest
obliczana wyrażeniem x % z
- Możesz jednocześnie dwóm zmiennym nadać wartość w taki oto sposób:
x, y = y, y+1. W tym przypadku x otrzymuje
wartość y (wartość przed przypisaniem y+1 do y), a
następnie y jest zwiększane o jeden
8. Więcej o funkcjach
I co z ćwiczeniem? Trudne? Nadal funkcje onieśmielają cię? Nie
martw się - tematu tego jeszcze nie zamknąłem.
Ten rodzaj abstrakcji, którą używaliśmy podczas tworzenia fukcji często
nazywany jest abstrakcją proceduralną, w wielu językach obok
słowa function używa się również słowa procedure.
Właściwie oba pojęcia są różne, jednak w Pythonie oba nazywane
są funkcjami (ponieważ są one definiowane i używane mniej więcej w ten sam
sposób.)
Jaka jest różnica (w innych językach) między funkcjami a procedurami?
Jak zauważyłeś w pierwszej części rozważań o funkcjach, funkcje mogą
zwracać wartość. Różnica leży w procedurach, które nie
zwracają takiej wartości. W wielu przypadkach, taki podział funkcji na dwa
typy - te, które zwracają i te, które nie zwracają
wartości - może byc użyteczny.
Funkcja, która nie zwraca wartości ("procedura")
wykorzystywana jest jako "podprogram". Wywołujemy taką funkcję, a program
wykonuje pewne zadanie, takie jak ubijanie piany z białek lub cokolwiek.
Możemy użyć tę funkcję w wielu miejscach bez przepisywania kodu. (Technikę
tę nazywamy ponownym wykorzystaniem kodu (code reuse) - więcej o
tym później.)
Użyteczność takiej funkcji (lub procedury) leży w jej efektach
ubocznych - funkcja zmienia swoje środowisko (poprzez na przykład
mieszanie cukru z białkiem i ubijanie go). Spójrzmy na przykład:
def czesc(kto): print "Cześć,", kto
czesc("świat")
Wypisanie tekstu jest uważane za efekt uboczny i ponieważ jest
to wszystko, co ta funkcja wykonuje jest ona uważana za tzw. procedurę.
Ale... W rzeczywistości nie zmieniła ona swojego środowiska, nieprawdaż?
Jak może to zrobić? Spróbujmy:
wiek = 0
def ustawWiek(a): wiek = a
ustawWiek(100) print wiek
Co tu jest źle? Problemem jest funkcja ustawWiek, która
tworzy swoją własną, lokalną zmienną, również nazwaną
wiek, która to zmienna widziana jest tylko wewnątrz
ustawWiek. Jak możemy to ominąć? Możemy użyć zmiennych
globalnych.
Uwaga: Zmienne globalne rzadko są wykorzystywane w Pythonie.
Prowadzą one do złej struktury kodu, lub do tzw. kodu spaghetti.
Ja użyję je jako wprowadzenie do bardziej skomplikowanych technik - ty,
jeśli możesz, unikaj takich zmiennych.
Poprzez poinformowanie interpretera, że zmienna jest globalna (poprzez
wyrażenie global wiek) faktycznie mówimy mu, aby użył
zmienną utworzoną na zewnątrz funkcji zamiast tworzyć nową,
lokalną zmienną. (Zatem, zmienna globalna jest przeciwna do
lokalnej.) Nasz nowy program może tak wyglądać:
wiek = 0
def ustawWiek(a): global wiek wiek = a
ustawWiek(100) print wiek
Gdy poznasz objekty (poniżej) zobaczysz, że lepszym sposobem wykonania
naszego zadania byłoby użycie objektu z właściwością wiek
i metodą ustawWiek. W części o strukturach danych zauważysz
również lepsze przykłady funkcji, które zmieniają swoje środowisko.
A co z prawdziwymi funkcjami? Czym jest w rzeczywistości
funkcja? Matematyczne funkcje są jak "maszyny", które otrzymują jakieś
dane i obliczają z nich wynik. Za każdym razem zwracany będzie ten sam
wynik jeśli funkcja otrzyma takie same dane. Przykład:
def kwadrat(x): return x*x
Powyższe jest tym samym co funkcja matematyczna
f(x)=x2. Zachowuje się ja funkcja dzięki
temu, że opiera się tylko na danych wejściowych i nie
zmienia w żaden sposób swojego środowiska.
Zarysowałem dwa sposoby tworzenia funkcji: pierwszy typ jest podobny do
procedury i nie zwraca wyniku; drugi jest podobny do funkcji matematycznej
i nie robi nic (prawie) oprócz zwracania wyniku. Kiedy funkcja
zwraca coś powinno być jasne, że to robi. Powinieneś zatem signalizować
to poprzez jej nazwę, np. używając tylko rzeczownika "czystej" funkcji tak
jak kwadrat i trybu rozkazującego dla funkcji-procedur tak
jak ustawWiek.
9. Więcej składników - struktury danych
Wiele już umiesz: jak wprowadzać dane i otrzymywać wyniki, jak tworzyć
skomplikowane algorytmy (programy) i przeprowadzać obliczenia
arytmetyczne; ale najlepsze jest jeszcze przed tobą.
Jakie składniki do tej pory używaliśmy w naszych programach? Liczby i
łańcuchy znaków. Prawda? Troszke nudne... Wprowadźmy kilka nowych
składników, aby rzeczy stały się bardziej ciekawsze.
Struktury danych są składnikami, które zawierają dane. (Niespodzianka,
niespodzianka....) Pojedyncza liczba w rzeczywistości nie posiada żadnej
struktury, zgadza się? Załóżmy jednak, że chcemy kilka liczb połączyć w
jeden składnik, który mógłby mieć jąkąś strukturę. Na przykład,
chcielibyśmy mieć listę liczb. Proszę bardzo:
[3,6,78,93]
O listach wspomniałem w części o pętlach, ale niewiele o nich
powiedziałem. Zatem, w taki sposób możesz je tworzyć. Po prostu wypisz
elementy listy, oddziel je przecinkami i zamknij w nawiasach kwadratowych.
Napiszmy teraz przykład, który oblicza liczby pierwsze (liczby, które
dzielą się tylko przez siebie lub 1):
wynik = [] kandydatki = range(3,1000) baza = 2 produkt = baza
while kandydatki: while produkt < 1000: if produkt in kandydatki: kandydatki.remove(produkt) produkt = produkt+baza wynik.append(baza) baza = kandydatki[0] produkt = baza del kandydatki[0]
wynik.append(baza) print wynik
Nowe rzeczy w tym przykładzie...
- Wbudowana funkcja
range zwraca listę, która może być
wykorzystywana jak inne listy. (Lista ta zawiera pierwszy indeks
funkcji, ale nie ostatni.)
- Lista może być użyta jako zmienna logiczna. Jeśli nie jest ona
pusta, wówczas zwraca true - jeśli jest pusta,
wówczas daje false. Dlatego,
while kandydatki
oznacza "kiedy lista o nazwie kandydatki nie jest pusta" lub
prościej "kiedy nadal są w niej kandydatki".
- Możesz napisać
if jakiśElement in jakiejśLiście, aby
sprawdzić, czy dany element znajduje się w tej liście.
- Możesz napisać
jakaśLista.remove(jakiśElement), aby
usunąć jakiśElement z jakiejśListy.
- Możesz dołączyć listę do innej listy pisząc
jakaśLista.append(innaLista). Właściwie, możesz
również użyć + (skoro jakaśLista =
jakaśLista+innaLista) jednak taki zapis jest mniej
efektywny.
- Możesz dostać się do wybranego elementu z listy poprzez podanie jego
pozycji jako liczby (gdzie pierwszym elementem, trochę dziwnie, jest
element
0) w nawiasach kwadratowych po nazwie listy.
Stąd jakaśLista[3] jest czwartym elementem listy
jakaśLista. (Więcej na ten temat niżej.)
- Możesz usuwać zmienne wykorzystując słowo kluczowe
del.
Słowo to może być również wykorzystane do usuwania elementów
z listy. Dlatego del jakaśLista[0] usuwa pierwszy
element jakiejśListy. Jeśli listą byłby ciąg
[1,2,3], po usunięciu pierwszego elementu
otrzymalibyśmy [2,3].
Przed przystąpieniem do wyjaśnienia tajemnic indeksowania elementów
listy krótko objaśnię przykład.
Jest to wersja starożytnego algorytmu zwanego "Sitem Erastotenesa". (lub
coś blisko tego). Bierze on pod uwagę zestaw (lub w tym przypadku listę)
kandydujących liczb, a następnie systematycznie usuwa liczby, o których
wiadomo, że nie są liczbami pierwszymi. Skąd o tym wiadomo?
Ponieważ są one produktem dwóch innych liczb.
Rozpoczynamy listą kandydatek zawierającą liczby [2..999] - wiemy, że 1
jest liczbą pierwszą (właściwie, może być lub nie, różne są na ten temat
opinie), i chcemy otrzymać wszystkie liczby pierwsze poniżej
1000. (Właściwie, nasza lista kandydatek zawiera liczby w przedziale
[3..999], jednak 2 jest również kandydatką, ponieważ jest naszą pierwszą
bazą). Mamy również listę nazwaną wynik, która
przez cały czas zawiera uaktualnione wyniki. Mamy również zmienną nazwaną
baza. Podczas każdego wykonania algorytmu (rozpoczętego
pobraniem kolejnej liczby z "round"), usuwamy
wszystkie liczby, które są wielokrotnością liczby bazowej (która jest
zawsze najmniejszą z kandydatek). Po każdym wykonaniu algorytmu, wiemy, że
najmniejsza liczba, która pozostała jest liczbą pierwszą (ponieważ
wszystkie liczby, które były produktami mniejszych liczb zostały usunięte
- rozumiesz?). Dlatego, dodajemy ją do wyniku, ustawiamy tę liczbę jako
bazę i wyrzucamy ją z listy kandydatek, aby nie powtarzać tego procesu
ponownie. Kiedy lista kandydatek będzie pusta, lista wyniku będzie
zawierać wszystkie liczby pierwsze. Sprytne, nie?
Rzeczy do przemyślenia: Co wyjątkowego jest podczas pierwszego
wykonania tego algorytmu? Czy 2, która jest tutaj bazą, jest również
usuwana w procesie "przesiewania"?
Dlaczego? Dlaczego coś takiego nie dzieje się z innymi liczbami bazowymi?
Czy możemy być pewni, że produkt jest
zawsze w liście kandydatek, kiedy chcemy go usunąć? Dlaczego?
Teraz - no właśnie co dalej? Ach tak, indeksowanie. I podział list.
Są to sposoby, aby dostać się do indywidualnych elementów listy. Widziałeś już
w akcji zwykłe indeksowanie. Jest ono dość proste. Właściwie powiedziałem ci
już wszystko, co powinieneś wiedzieć o tym, z wyjątkiem jednej rzeczy:
Ujemne indeksy zliczają elementy od końca listy. Zatem,
jakaśLista[-1] jest ostatnim elementem
jakiejśListy, jakaśLista[-2] jest elementem
przedostatnim, i tak dalej.
Podział, jednakże, jest nowy dla ciebie. Jest on podobny do
indeksowania z tym wyjątkiem, że otrzymujesz część listy, a nie
tylko pojedynczy element. Jak można to zrobić? Np. tak:
jedzenie = ["ciasto","ciasto","jajka","kiełbasa","ciasto"]
print jedzenie[2:4]
[Więcej o tym później...]
10. Więcej o abstrakcji - obiekty i programowanie orientowane
obiektowo
Oto najbardziej bezsensowne wyrażenie jakie kiedykolwiek powstało:
"Programowanie orientowane obiektowo (OOP)."
Jak sugeruje tytuł części, programowanie orientowane obiektowo jest po
prostu kolejnym sposobem skupienia szczegółów. Procedury łączą proste
wyrażenia w bardziej skomplikowane operacje nadając im nazwę. W OOP,
w taki sposób postępujemy z obiektami. (I co, zaskoczyło cię
to?) Na przykład, jeśli stworzyliśmy program piekący sernik,
zamiast pisania mnóstwa procedur odpowiedzialnych za temperaturę, czas,
składniki itd., mogliśmy to wszystko skupić w obiekcie-serniku.
Mogliśmy również stworzyć obiekt-piekarnik i obiekt-zegar... Rzeczy
takie jak temperatura mogłyby być atrybutami obiektu-sernika, podczas gdy
czas mógłby być odczytywany z obiektu-zegara. Aby nasz program
robił coś, moglibyśmy nauczyć nasze obiekty kilku metod;
na przykład, piekarnik mógłby wiedzieć jak piec sernik itd.
Zatem - jak to zrobimy w Pythonie? Nie możemy bezpośrednio utworzyć
obiektu. Zamiast budowania piekarnika, tworzymy przepis
opisujący jakie są piekarniki. Ten przepis opisuje klasę
(class) obiektu, który nazywamy piekarnikiem. Bardzo prosta klasa
piekarnika może wyglądać tak:
class Piekarnik: def wlozSernik(self, sernik): self.sernik = sernik
def wyjmijSernik(self): return self.sernik
Wygląda to dziwacznie, czy nie?
Nowe rzeczy w tym przykładzie...
- Klasy obiektów definiuje się słowem kluczowym
class.
- Nazwa klasy zwykle rozpoczyna się z dużej litery, zaś funkcje i
zmienne (jak również metody i atrybuty) zaczynają się z małych
liter.
- Metody (tzn. funkcje i operacje, które wykonuje obiekt) definiuje
się w zwykły sposób, ale wewnątrz bloku klasy.
- Wszystkie metody obiektu powinny zawierać pierwszy parametr o nazwie
self (lub coś podobnego...) Powód (mam nadzieję)
powinien wyjaśnić się za chwilę.
- Atrybuty i metody obiektu dostępne są w taki sposób:
mojSernik.temperatura = 2, lub
piekarnik.wyjmijSernik().
Domyślam się, że niektóre rzeczy są nadal niejasne w tym przykładzie.
Na przykład, czym jest to self? I, skoro mamy ten przepis na
obiekt (tzn. class), to jak właściwie stworzymy obiekt?
Weźmy się najpierw za ostatni punkt. Obiekt tworzy się przez wywołanie
nazwy klasy, tak jakby była funkcją:
mojPiekarnik = Piekarnik()
mojPiekarnik zawiera teraz obiekt Piekarnik i
nazywany jest instancją klasy Piekarnik. Załóżmy, że
utworzyliśmy klasę Sernik; wówczas moglibyśmy napisać coś
takiego:
mojSernik = Sernik() mojPiekarnik.wlozSernik(mojSernik)
mojPiekarnik.sernik zawierałby teraz
mojSernik. Jak to możliwe? Ponieważ, kiedy wywołujemy jedną z
metod obiektu, pierwszy parametr, zwykle nazywany self,
zawsze zawiera obiekt samego siebie. (Sprytne, nie?) Dlatego, linia
self.sernik = sernik ustawia atrybut
sernik bieżącego obiektu Piekarnika na wartość
parametru sernika. Zauważ, że są to dwie różne
rzeczy, nawet jeśli w tym przykładzie obie nazywają się
sernik.
[Ciąg dalszy nastąpi...]
Odpowiedź do ćwiczenia 2
Poniżej podaję bardzo zwięzłą wersję algorytmu:
def euklides(a,b): while b: a,b = b,a % b return a
Przypisy
[1]
Wirtualna Kuchnia
Polska - sernik na kruchym cieście Z.Wodecki
(wodecki@kki.net.pl)
[Main python
page]
Copyright © Magnus
Lie Hetland
(mlh@idi.ntnu.no)
Translation into Polish Wojciech Warczakowski
(wowar@poczta.onet.pl)
Original Instant
Hacking. Learn how to program with Python.
Last modified: Tue Nov 21 19:27:51 CET DST 2000
|