1.4. Extra Lotek¶
Kod Toto Lotka wypracowany w dwóch poprzednich częściach wprowadził podstawy programowania w Pythonie: podstawowe typy danych (napisy, liczby, listy, zbiory), instrukcje sterujące (warunkową i pętlę) oraz operacje wejścia-wyjścia w konsoli. Uzyskany skrypt wygląda następująco:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import random
try:
ileliczb = int(raw_input("Podaj ilość typowanych liczb: "))
maksliczba = int(raw_input("Podaj maksymalną losowaną liczbę: "))
if ileliczb > maksliczba:
print "Błędne dane!"
exit()
except:
print "Błędne dane!"
exit()
liczby = []
i = 0
while i < ileliczb:
liczba = random.randint(1, maksliczba)
if liczby.count(liczba) == 0:
liczby.append(liczba)
i = i + 1
for i in range(3):
print "Wytypuj %s z %s liczb: " % (ileliczb, maksliczba)
typy = set()
i = 0
while i < ileliczb:
try:
typ = int(raw_input("Podaj liczbę %s: " % (i + 1)))
except ValueError:
print "Błędne dane!"
continue
if 0 < typ <= maksliczba and typ not in typy:
typy.add(typ)
i = i + 1
trafione = set(liczby) & typy
if trafione:
print "\nIlość trafień: %s" % len(trafione)
print "Trafione liczby: ", trafione
else:
print "Brak trafień. Spróbuj jeszcze raz!"
print "\n" + "x" * 40 + "\n" # wydrukuj 40 znaków x
print "Wylosowane liczby:", liczby
1.4.1. Funkcje i moduły¶
Tam, gdzie w programie występują powtarzające się operacje lub zestaw poleceń
realizujący wyodrębnione zadanie, wskazane jest używanie funkcji.
Są to nazwane bloki kodu, które można grupować w ramach modułów (zob. funkcja, moduł).
Funkcje zawarte w modułach można importować do różnych programów.
Do tej pory korzystaliśmy np. z funkcji randit()
zawartej w module random
.
Wyodrębnienie funkcji ułatwia sprawdzanie i poprawianie kodu, ponieważ wymusza podział programu na logicznie uporządkowane kroki. Jeżeli program korzysta z niewielu funkcji, można umieszczać je na początku pliku programu głównego.
Tworzymy więc nowy plik totomodul.py
i umieszczamy w nim następujący kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
import random
def ustawienia():
"""Funkcja pobiera ilość losowanych liczb, maksymalną losowaną wartość
oraz ilość prób. Pozwala określić stopień trudności gry."""
while True:
try:
ile = int(raw_input("Podaj ilość typowanych liczb: "))
maks = int(raw_input("Podaj maksymalną losowaną liczbę: "))
if ile > maks:
print "Błędne dane!"
continue
ilelos = int(raw_input("Ile losowań: "))
return (ile, maks, ilelos)
except:
print "Błędne dane!"
continue
def losujliczby(ile, maks):
"""Funkcja losuje ile unikalnych liczb całkowitych od 1 do maks"""
liczby = []
i = 0
while i < ile:
liczba = random.randint(1, maks)
if liczby.count(liczba) == 0:
liczby.append(liczba)
i = i + 1
return liczby
def pobierztypy(ile, maks):
"""Funkcja pobiera od użytkownika jego typy wylosowanych liczb"""
print "Wytypuj %s z %s liczb: " % (ile, maks)
typy = set()
i = 0
while i < ile:
try:
typ = int(raw_input("Podaj liczbę %s: " % (i + 1)))
except ValueError:
print "Błędne dane!"
continue
if 0 < typ <= maks and typ not in typy:
typy.add(typ)
i = i + 1
return typy
|
Funkcja w Pythonie składa się ze słowa kluczowego def
, nazwy, obowiązkowych nawiasów
okrągłych i opcjonalnych parametrów. Funkcje zazwyczaj zwracają jakieś dane
za pomocą instrukcji return
.
Warto zauważyć, że można zwracać więcej niż jedną wartość naraz,
np. w postaci tupli return (ile, maks, ilelos)
. Tupla to rodzaj listy,
w której nie możemy zmieniać wartości (zob. tupla). Jest często stosowana
do przechowywania i przekazywania stałych danych.
Nazwy zmiennych lokalnych w funkcjach są niezależne od nazw zmiennych w programie
głównym, ponieważ definiowane są w różnych zasięgach, a więc w różnych przestrzeniach nazw.
Możliwe jest modyfikowanie zmiennych globalnych dostępnych w całym programie,
o ile wskażemy je instrukcją typu: global nazwa_zmiennej
.
Program główny po zmianach przedstawia się następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
from totomodul import ustawienia, losujliczby, pobierztypy
def main(args):
# ustawienia gry
ileliczb, maksliczba, ilerazy = ustawienia()
# losujemy liczby
liczby = losujliczby(ileliczb, maksliczba)
# pobieramy typy użytkownika i sprawdzamy, ile liczb trafił
for i in range(ilerazy):
typy = pobierztypy(ileliczb, maksliczba)
trafione = set(liczby) & typy
if trafione:
print "\nIlość trafień: %s" % len(trafione)
print "Trafione liczby: %s" % trafione
else:
print "Brak trafień. Spróbuj jeszcze raz!"
print "\n" + "x" * 40 + "\n" # wydrukuj 40 znaków x
print "Wylosowane liczby:", liczby
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))
|
Na początku z modułu totomodul
, którego nazwa jest taka sama jak nazwa pliku,
importujemy potrzebne funkcje. Następnie w funkcji głównej main()
wywołujemy je podając nazwę i ewentualne argumenty.
Zwracane przez nie wartości zostają przypisane podanym zmiennym.
Wiele wartości zwracanych w tupli można jednocześnie przypisać
kilku zmiennym dzięki operacji tzw. rozpakowania tupli:
ileliczb, maksliczba, ilerazy = ustawienia()
. Należy jednak
pamiętać, aby ilość zmiennych z lewej strony wyrażenia odpowiadała ilości
elementów w tupli.
Konstrukcja while True
oznacza nieskończoną pętlę. Stosujemy ją w funkcji
ustawienia()
, aby wymusić na użytkowniku podanie poprawnych danych.
Funkcja główna main()
zostaje wywołana, o ile warunek if __name__ == '__main__':
jest prawdziwy.
Jest on prawdziwy wtedy, kiedy nasz skrypt zostanie uruchomiony
jako główny, wtedy nazwa specjalna __name__
ustawiana jest na __main__
.
Jeżeli korzystamy ze skryptu jako modułu, importując go, __main__
ustawiane jest na nazwę pliku.
Note
W rozbudowanych programach dobrą praktyką ułatwiającą późniejsze przeglądanie
i poprawianie kodu jest opatrywanie jego fragmentów komentarzami. Można je
umieszczać po znaku #
. Z kolei funkcje opatruje się krótkim opisem
działania i/lub wymaganych argumentów, ograniczanym potrójnymi cudzysłowami.
Notacja """..."""
lub '''...'''
pozwala zamieszczać teksty wielowierszowe.
1.4.1.1. Ćwiczenie¶
Przenieś kod powtarzany w pętli
for
(linie 17-24) do funkcji zapisanej w module programu i nazwanej np.wyniki()
. Zdefiniuj listę argumentów, zadbaj, aby funkcja zwracała ilość trafionych liczb. Wywołanie funkcji:iletraf = wyniki(set(liczby), typy)
umieść w linii 17.Przy okazji popraw wyświetlanie listy trafionych liczb. Przed instrukcją
print "Trafione liczby: %s" % trafione
wstaw linię:trafione = ", ".join(map(str, trafione))
.Funkcja
map()
(zob. mapowanie funkcji) pozwala na zastosowanie jakiejś innej funkcji, w tym wypadkustr
(czyli konwersji na napis), do każdego elementu sekwencji, w tym wypadku zbiorutrafione
.Metoda napisów
join()
pozwala połączyć elementy listy (muszą być typu string) podanymi znakami, np. przecinkami (", "
).
1.4.2. Zapis/odczyt plików¶
Uruchamiając wielokrotnie program, musimy podawać wiele danych, aby zadziałał.
Dodamy więc możliwość zapamiętywania ustawień i ich zmiany. Dane zapisywać
będziemy w zwykłym pliku tekstowym. W pliku toto2.py
dodajemy
tylko jedną zmienną nick
:
8 9 | # ustawienia gry
nick, ileliczb, maksliczba, ilerazy = ustawienia()
|
W pliku totomodul.py
zmieniamy funkcję ustawienia()
oraz dodajemy
dwie nowe: czytaj_ust()
i zapisz_ust()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #! /usr/bin/env python
# -*- coding: utf-8 -*-
import random
import os
def ustawienia():
"""Funkcja pobiera nick użytkownika, ilość losowanych liczb, maksymalną
losowaną wartość oraz ilość typowań. Ustawienia zapisuje."""
nick = raw_input("Podaj nick: ")
nazwapliku = nick + ".ini"
gracz = czytaj_ust(nazwapliku)
odp = None
if gracz:
print "Twoje ustawienia:"
print "Liczb:", gracz[1]
print "Z Maks:", gracz[2]
print "Losowań:", gracz[3]
odp = raw_input("Zmieniasz (t/n)? ")
if not gracz or odp.lower() == "t":
while True:
try:
ile = int(raw_input("Podaj ilość typowanych liczb: "))
maks = int(raw_input("Podaj maksymalną losowaną liczbę: "))
if ile > maks:
print "Błędne dane!"
continue
ilelos = int(raw_input("Ile losowań: "))
break
except:
print "Błędne dane!"
continue
gracz = zapisz_ust(nazwapliku,
[nick, str(ile), str(maks), str(ilelos)])
return gracz[0:1] + map(int, gracz[1:4])
def czytaj_ust(nazwapliku):
if os.path.isfile(nazwapliku):
plik = open(nazwapliku, "r")
linia = plik.readline()
if linia:
return linia.split(";")
return False
def zapisz_ust(nazwapliku, gracz):
plik = open(nazwapliku, "w")
plik.write(";".join(gracz))
plik.close()
return gracz
|
W funkcji ustawienia()
pobieramy nick użytkownika i tworzymy nazwę pliku
z ustawieniami, następnie próbujemy je odczytać wywołując funkcję czytaj_ust()
.
Funkcja ta sprawdza, czy podany plik istnieje na dysku i otwiera go do odczytu:
plik = open(nazwapliku, "r")
. Plik powinien zawierać 1 linię, która przechowuje
ustawienia w formacie: nick;ile_liczb;maks_liczba;ile_prób
. Po jej
odczytaniu za pomocą metody .readline()
i rozbiciu na elementy
zwracamy ją jako listę gracz
.
Jeżeli uda się odczytać zapisane ustawienia, drukujemy je, a następnie
pytamy, czy użytkownik chce je zmienić. Jeżeli nie znaleźliśmy zapisanych
ustawień lub użytkownik nacisnął klawisz “t” lub “T”, wykonujemy poprzedni
kod. Na koniec zmiennej gracz
przypisujemy listę ustawień przekazaną
do zapisu funkcji zapisz_ust()
. Funkcja ta zapisuje dane złączone za
pomocą średnika w jedną linię do pliku: plik.write(";".join(gracz))
.
W powyższym kodzie widać, jakie operacje można wykonywać na tekstach, tj.:
- operator
+
: łączenie tekstów, linia.split(";")
– rozbijanie tekstu wg podanego znaku na elementy listy,";".join(gracz)
– wspomniane już złączanie elementów listy za pomocą podanego znaku,odp.lower()
– zmiana wszystkich znaków na małe litery,str(arg)
– przekształcanie podanego argumentu na typ tekstowy.
Zwróćmy uwagę na konstrukcję return gracz[0:1] + map(int, gracz[1:4])
,
której używamy, aby zwrócić odczytane/zapisane ustawienia do programu głównego.
Dane w pliku przechowujemy, a także pobieramy od użytkownika jako znaki.
Natomiast program główny oczekuje 4 wartości typu: znak, liczba, liczba, liczba.
Stosujemy więc notację wycinkową (ang. slice), aby wydobyć nick użytkownika:
gracz[0:1]
. Pierwsza wartość mówi od którego elementu, a druga do którego
elementu wycinamy wartości z listy (przećwicz w konsoli Pythona!).
Wspominana już funkcja map()
pozwala zastosować
do pozostałych 3 elementów (gracz[1:4]
) funkcję int()
,
która zamienia je w wartości liczbowe.
1.4.3. Słowniki¶
Skoro umiemy już zapamiętywać wstępne ustawienia programu, możemy również zapamiętywać losowania użytkownika, tworząc rejestr do celów informacyjnych i/lub statystycznych. Zadanie wymaga po pierwsze zdefiniowania jakieś struktury, w której będziemy przechowywali dane, po drugie zapisu danych albo w plikach, albo w bazie danych.
Na początku dopiszemy kod w programie głównym toto2.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #! /usr/bin/env python
# -*- coding: utf-8 -*-
from totomodul import ustawienia, losujliczby, pobierztypy, wyniki
from totomodul import czytaj_json, zapisz_json
import time
def main(args):
# ustawienia gry
nick, ileliczb, maksliczba, ilerazy = ustawienia()
# losujemy liczby
liczby = losujliczby(ileliczb, maksliczba)
# pobieramy typy użytkownika i sprawdzamy, ile liczb trafił
for i in range(ilerazy):
typy = pobierztypy(ileliczb, maksliczba)
iletraf = wyniki(set(liczby), typy)
nazwapliku = nick + ".json"
losowania = czytaj_json(nazwapliku)
losowania.append({
"czas": time.time(),
"dane": (ileliczb, maksliczba),
"wylosowane": liczby,
"ile": iletraf
})
zapisz_json(nazwapliku, losowania)
print "\nLosowania:", liczby
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))
|
Dane graczy zapisywać będziemy w plikach nazwanych nickiem użytkownika
z rozszerzeniem ”.json”: nazwapliku = nick + ".json"
.
Informacje o grach umieścimy w liście losowania
, którą na początku
zainicjujemy danymi o grach zapisanymi wcześniej: losowania = czytaj(nazwapliku)
.
Każda gra w liście losowania
to słownik. Struktura ta pozwala
przechowywać dane w parach “klucz: wartość”, przy czym indeksami mogą być napisy:
"czas"
– będzie indeksem daty gry (potrzebny import modułutime
!),"dane"
– będzie wskazywał tuplę z ustawieniami,"wylosowane"
– listę wylosowanych liczb,"ile"
– ilość trafień.
Na koniec dane ostatniej gry dopiszemy do listy (losowania.append()
),
a całą listę zapiszemy do pliku: zapisz(nazwapliku, losowania)
.
Teraz zobaczmy, jak wyglądają funkcje czytaj_json()
i zapisz_json()
w module
totomodul.py
:
103 104 105 106 107 108 109 110 111 112 113 114 115 | def czytaj_json(nazwapliku):
"""Funkcja odczytuje dane w formacie json z pliku"""
dane = []
if os.path.isfile(nazwapliku):
with open(nazwapliku, "r") as plik:
dane = json.load(plik)
return dane
def zapisz_json(nazwapliku, dane):
"""Funkcja zapisuje dane w formacie json do pliku"""
with open(nazwapliku, "w") as plik:
json.dump(dane, plik)
|
Kiedy czytamy i zapisujemy dane, ważną sprawą staje się ich format. Najprościej zapisywać dane jako znaki, tak jak zrobiliśmy to z ustawieniami, jednak często programy użytkowe potrzebują zapisywać złożone struktury danych, np. listy, zbiory czy słowniki. Znakowy zapis wymagałby wtedy wielu dodatkowych manipulacji, aby możliwe było poprawne odtworzenie informacji. Prościej jest skorzystać z serializacji, czyli zapisu danych obiektowych (zob. serializacja). Często stosowany jest prosty format tekstowy JSON.
W funkcji czytaj()
zawartość podanego pliki dekodujemy do listy: dane = json.load(plik)
.
Funkcja zapisz()
oprócz nazwy pliku wymaga listy danych. Po otwarciu
pliku w trybie zapisu "w"
, co powoduje wyczyszczenie jego zawartości,
dane są serializowane i zapisywane formacie JSON: json.dump(dane, plik)
.
Dobrą praktyką jest zwalnianie uchwytu do otwartego pliku i przydzielonych mu zasobów
poprzez jego zamknięcie: plik.close()
. Tak robiliśmy w funkcjach
czytających i zapisujących ustawienia. Teraz jednak pliki otworzyliśmy przy
użyciu konstrukcji typu with open(nazwapliku, "r") as plik:
, która zadba
o ich właściwe zamknięcie.
Przetestuj, przynajmniej kilkukrotnie, działanie programu.
1.4.3.1. Ćwiczenie¶
Załóżmy, że jednak chcielibyśmy zapisywać historię losowań w pliku tekstowym,
którego poszczególne linie zawierałyby dane jednego losowania, np.:
wylosowane:[4, 5, 7];dane:(3, 10);ile:0;czas:1434482711.67
Funkcja zapisująca dane mogłaby wyglądać np. tak:
def zapisz_str(nazwapliku, dane):
"""Funkcja zapisuje dane w formacie txt do pliku"""
with open(nazwapliku, "w") as plik:
for slownik in dane:
linia = [k + ":" + str(w) for k, w in slownik.iteritems()]
linia = ";".join(linia)
# plik.write(linia+"\n") – zamiast tak, można:
print >>plik, linia
Napisz funkcję czytaj_str()
odczytującą tak zapisane dane. Funkcja
powinna zwrócić listę słowników.
1.4.4. Materiały¶
Źródła:
Materiały Python 101
udostępniane przez
Centrum Edukacji Obywatelskiej na licencji
Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.
Utworzony: | 2017-09-08 o 19:38 w Sphinx 1.4.5 |
---|---|
Autorzy: | Patrz plik “Autorzy” |