1.10. Extra Lotek

Program „Duży Lotek” omówiony w poprzednim przykładzie można podzielić na części kodu realizujące pojedyncze zadania, np. pobranie danych wejściowych, wylosowanie liczb, pobranie typów użytkownika, sprawdzenie i wypisanie danych wyjściowych. Realizację takich zadań możemy umieścić w funkcjach, których będzie można używać w innych programach.

Informacja

Przykład „Extra Lotek” pokazuje, jak używać funkcji i modułów oraz jak zapisywać i odczytywać dane z plików tekstowych w różnych formatach.

1.10.1. Zadanie

Napisz program extra_lotek.py, który losuje n liczb naturalnych z podanego zakresu maks, a następnie pobiera z klawiatury n typów, sprawdza i wypisuje, ile z nich zostało trafionych. Pobieranie typów, sprawdzanie i wypisywanie wyników powtarza się tyle razy, ile poda użytkownik. Dodatkowo program powinien:

  • sprawdzać poprawność podawanych danych wejściowych,

  • zapisywać w pliku i odczytywać dane wejściowe: nick użytkownika, liczbę losowanych liczb, wartość maksymalną oraz liczbę typowań,

  • pozwalać na zmianę danych wejściowych.

Realizację kolejnych zadań zakoduj w funkcjach umieszczonych w module.

Dane:

  • nick – nick użytkownika, ciąg znaków pobierany z klawiatury,

  • n – liczba naturalna pobierana z klawiatury większa od n,

  • maks – liczba całkowita pobierana z klawiatury,

  • ile_typowan – liczba całkowita pobierana z klawiatury,

  • typ – liczba całkowita pobierana z klawiatury z zakresu <0; maks>.

Wynik

Komunikaty podczas pierwszego uruchomienia:

Podaj nick: adam
Podaj liczbę losowanych liczb: 3
Podaj maksymalną losowaną liczbę: 10
Podaj liczbę typowań: 1
Wytypuj 3 z 10 liczb:
Podaj typ: 1
Podaj typ: 3
Podaj typ: 5

Wylosowane liczby: [10, 3, 4]
Trafione liczby: {3}
Liczba trafień 1

Początkowe komunikaty po kolejnym uruchomieniu przez tego samego użytkownika:

Podaj nick: adam
Ustawienia:
Liczb: 3
Maks: 10
Typowań: 1
Zmieniasz (t/n)? n
Wytypuj 3 z 10 liczb:
...

1.10.2. Funkcja główna

Tam, gdzie w programie występują powtarzające się operacje lub zestaw poleceń realizujący wyodrębnione zadanie, wskazane jest używanie funkcji. Wyodrębnienie funkcji poprawia czytelność działania programu, ułatwia sprawdzanie i poprawianie kodu.

Jeżeli zadania realizowane przez program umieszczamy w funkcjach, to jedną z nich trzeba wywołać na początku, aby program zaczął działać. Taka funkcja zwyczajowo nazywana jest main() i zazwyczaj zawiera logikę działania programu, tj. wywołuje inne funkcje wykonujące osobne zadania.

W nowym pliku extra_lotek.py umieszczamy początkowy kod:

Plik extra_lotek.pyKod nr
def main(args):
    nick = input('Podaj nick: ')

    pass

    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))

Funkcja w Pythonie to wyodrębniony i nazwany blok kodu. Definicja funkcji def main(args): zawiera słowo kluczowe def, nazwę funkcji z okrągłymi nawiasami, np. main(), opcjonalne parametry, np. args oraz dwukropek.

Instrukcja warunkowa if __name__ == '__main__': sprawdza, czy nasz plik został uruchomiony bezpośrednio przez interpreter, który zmienną specjalną __name__ ustawia wtedy na __main__. Jeżeli plik zostałby zaimportowany, zmienna __name__ ustawiona zostałaby na nazwę pliku.

Jeżeli plik wykonywany jest bezpośrednio, uruchamiana jest funkcja główna main(args) jako argument metody sys.exit(), która kończy wykonywanie programu. Wartość zwracana przez funkcję główną informuje o tym, jak zakończył się program, np. zero (0) oznacza, że program zakończył się poprawnie.

Informacja

W prostych programach pisanych na własny użytek można zrezygnować z bloku if __name__ == '__main__':, a także usunąć argument args z definicji funkcji głównej. Trzeba jednak pamiętać o wywołaniu funkcji głównej, tj. umieszczeniu instrukcji main() bez wcięcia po definicji funkcji głównej.

Instrukcja pass nie jest wykonywana, to informacja, że w danym miejscu zostanie wstawiony jakiś kod. Pozwala również uniknąć błędu w miejscach, w których brak kodu jest niedozwolony, np. po instrukcji warunkowej lub pętli.

1.10.3. Moduł

Często używane funkcje umieszczamy w osobnych modułach (zob. moduł), z których importujemy je do różnych programów za pomocą instrukcji import lub from ... import.

Informacja

Jeżeli program korzysta z niewielu i/lub unikalnych funkcji, możemy umieścić je na początku programu w jednym pliku.

1.10.3.1. Wczytywanie ustawień

W katalogu ze skryptem naszego programu tworzymy nowy plik modul_lotek.py, dodajemy importy modułów wbudowanych oraz funkcji wczytującej zapisanie ustawienia:

Plik modul_lotek.pyKod nr
 1import random
 2import os
 3import json
 4
 5
 6def wczytaj_ustawienia(nick):
 7    nazwa_pliku = nick + '.txt'
 8    if os.path.isfile(nazwa_pliku):
 9        with open(nazwa_pliku) as plik:
10            wiersz = plik.readline()
11            dane = wiersz.strip().split(';')
12            return dane
13    return False
14

Funkcja wczytaj_ustawienia() otrzymuje parametr nick, który wykorzystujemy do zdefiniowania nazwy pliku z ustawieniami użytkownika. Za pomocą metody isfile() sprawdzamy, czy plik znajduje się w ścieżce przeszukiwania, czyli w praktyce w katalogu naszego programu głównego. Jeżeli tak, otwieramy go w domyślnym trybie „do odczytu” – with open(nazwa_pliku) as plik: – i udostępniamy w zmiennej plik.

Plik zawiera jeden wiersz w formacie nick;liczba losowanych liczb;maksymalna liczba;liczba typowań. Do jego odczytania używamy metody readline(). Z otrzymanego ciągu znaków usuwamy znaki końca wiersza przy użyciu metody strip(). Następnie dzielimy tekst za pomocą metody split() i znaku średnika na części, które zwracane są w postaci listy dane.

1.10.3.2. Pobieranie ustawień

Do naszego modułu dodajemy kolejną funkcję o nazwie pobierz_ustawienia(), której zadaniem będzie pobieranie danych wejściowych z klawiatury:

Plik modul_lotek.pyKod nr
16def pobierz_ustawienia(nick):
17    error = False
18    while True:
19        try:
20            n = int(input('Podaj liczbę losowanych liczb: '))
21            maks = int(input('Podaj maksymalną losowaną liczbę: '))
22            ile_typowan = int(input('Podaj liczbę typowań: '))
23            if n >= maks or ile_typowan < 1:
24                error = True
25        except ValueError:
26            error = True
27
28        if error:
29            print("Błędne dane!")
30            continue  # dane niepoprawne
31        else:
32            break  # dane poprawne
33
34    dane = [nick, n, maks, ile_typowan]
35    return dane
36

Funkcja pobierz_ustawienia() wykorzystuje nieskończoną pętlę warunkową while True, w której pobierane są kolejne dane wejściowe. W przypadku podania błędnych danych pętla zaczyna działanie od początku dzięki instrukcji continue, jeżeli dane są poprawne przerywamy działanie pętli instrukcją break i zwracamy je w postaci listy dane.

Informacja

Wyjaśnienie instrukcji try ... except znajdziesz w materiale Duży lotek w sekcji „Poprawność danych”.

1.10.3.3. Zapisywanie ustawień

Dodajemy do modułu funkcję zapisz_ustawienia(), która pobierać będzie dwa parametry zawierające nick użytkownika i dane do zapisania.

Plik modul_lotek.pyKod nr
38def zapisz_ustawienia(nick, dane):
39    nazwa_pliku = nick + '.txt'
40    with open(nazwa_pliku, 'w') as plik:
41        plik.write(';'.join(dane) + '\n')
42

Początek funkcji jest prawie identyczny jak w funkcji wczytaj_ustawienia() z tą tylko różnicą, że plik otwierany jest w trybie do zapisu, tj, podajemy drugi argument w funkcji open() – znak w. W efekcie, jeżeli pliku nie ma na dysku – zostanie zapisany, jeżeli jest – zostanie nadpisany. Parametr dane jest 4-elementową listą zawierająca pobrane od użytkownika lub odczytane z dysku dane wejściowe. Dane te łączymy za pomocą metody join() i znaku średnika. Otrzymany ciąg znaków uzupełniamy znakiem końca wiersza \n i zapisujemy w pliku za pomocą metody .write().

1.10.4. Dane wejściowe

Wykorzystajmy przygotowane funkcje w programie głównym. Na początku pliku extra_lotek.py importujemy dodane funkcje:

Plik extra_lotek.pyKod nr
1from modul_lotek import wczytaj_ustawienia, pobierz_ustawienia, zapisz_ustawienia

Następnie instrukcję pass zastępujemy poniższym kodem:

Plik extra_lotek.pyKod nr
 7    dane = wczytaj_ustawienia(nick)
 8    print(dane)  # tę instrukcję można zakomentować
 9    if dane:
10        print(f'Ustawienia:\nLiczb: {dane[1]}\nMaks: {dane[2]}\nTypowań: {dane[3]}')
11
12    if not dane or input('Zmieniasz (t/n)? ').lower() == 't':
13        dane = pobierz_ustawienia(nick)
14        zapisz_ustawienia(nick, dane)
15
16    mick, n, maks, ile_typowan = dane[0:1] + [int(x) for x in dane[1:4]]
17    print(mick, n, maks, ile_typowan)  # tę instrukcję można zakomentować
18

Po pobraniu nicka z klawiatury wywołujemy funkcję wczytaj_ustawienia(), która zwróci listę z odczytanymi danymi lub wartość False. W instrukcji warunkowej, jeżeli lista nie jest pusta (if dane:), wypisujemy dane oprócz nicka.

Jeżeli nie udało się odczytać danych (if not dane) lub jeżeli użytkownik chce zmienić odczytane dane (or input('Zmieniasz (t/n)? ').lower() == 't'), wywołujemy funkcję pobierz_dane(), a następnie funkcję zapisz_ustawienia(), która zapisuje listę dane zawierającą nick i pozostałe informacje.

W kolejnej instrukcji przypisujemy elementy listy dane do osobnych zmiennych. Do wskazania poszczególnych elementów listy używamy notacji wycinkowej. Instrukcja [int(x) for x in dane[1:4]] to wyrażenie listowe. Można je rozumieć jako skrócony zapis pętli for, która odczytuje trzy ostatnie elementy listy dane[1:4], zamienia je na liczby całkowite int(x) i tworzy nową listę. Jej elementy to liczby przypisywane do zmiennych m, maks i ile_typowan.

1.10.5. Losowanie i sprawdzanie

Funkcje, które losują liczby do odgadnięcia, pobierają typy użytkownika, sprawdzają i wypisują wyniki umieszczamy w module modul_lotek.py:

Plik modul_lotek.pyKod nr
44def losuj_liczby(n, maks):
45    """Funkcja losuje i zwraca listę n unikalnych liczb całkowitych w zakresie <0;maks>"""
46    liczby = []
47    while len(liczby) < n:
48        liczba = random.randint(0, maks)
49        if liczby.count(liczba) == 0:
50            liczby.append(liczba)
51    return liczby
52
53
54def pobierz_typy(n, maks):
55    """Funkcja pobiera od użytkownika typy wylosowanych liczb"""
56    print(f'Wytypuj {n} z {maks} liczb: ')
57    typy = set()
58    error = False
59    while len(typy) < n:
60        try:
61            typ = int(input('Podaj typ: '))
62            if typ < 0 or typ > maks or typ in typy:
63                error = True
64        except ValueError:
65            error = True
66
67        if error:
68            print('Błędne dane!')
69            continue
70        typy.add(typ)
71    return typy
72
73def wypisz_wyniki(liczby, typy):
74    print()
75    trafione = set(liczby) & typy
76    if trafione:
77        print(f'Trafione liczby: {trafione}')
78        print(f'Liczba trafień {len(trafione)}')
79    else:
80        print('Brak trafień. Spróbuj jeszcze raz!')
81
82    print()
83    return len(trafione)
84

Dodane instrukcje to kod omówiony w scenariuszu Duży lotek przekształcony na funkcje. Warto zwrócić uwagę, że zwracają one wartości, tj. listę wylosowanych liczb liczby, zbiór typów użytkownika typy oraz liczbę trafień len(trafione).

Następnie w programie głównym uzupełniamy importy na początku pliku:

Plik extra_lotek.pyKod nr
1from modul_lotek import wczytaj_ustawienia, pobierz_ustawienia, zapisz_ustawienia
2from modul_lotek import losuj_liczby, pobierz_typy, wypisz_wyniki

Następnie dopisujemy kod w funkcji głównej przed instrukcją return 0:

Plik extra_lotek.pyKod nr
21    liczby = losuj_liczby(n, maks)
22
23    for i in range(ile_typowan):
24        typy = pobierz_typy(n, maks)
25        ile_trafionych = wypisz_wyniki(liczby, typy)
26        if ile_trafionych == n:
27            break
28
29    print('Wylosowane liczby:', liczby)
30

Po wylosowaniu liczb wykorzystujemy pętlę for i in range(ile_typowan), aby użytkownik mógł kilka razy typować liczby. Wewnątrz pętli pobieramy typy, sprawdzamy i wypisujemy wyniki. Jeżeli liczba odgadniętych liczb, czyli wartość zwrócona przez funkcję wypisz_wyniki() równa jest liczbie losowanych liczb, kończymy pętlę instrukcją break.

1.10.6. Historia typowań

Skoro umiemy już zapamiętywać wstępne ustawienia programu, możemy również zapamiętywać losowania i typowania użytkownika, tworząc rejestr do celów informacyjnych i/lub statystycznych. Przyjmijmy, że będziemy zapisywali dane ostatniego typowania i będą one zawierały:

  • czas – data typowania,

  • n – liczba losowanych liczb,

  • maks – maksymalna losowana liczba,

  • ile_typowan – liczba typowań,

  • ustawienia – krotka z liczbą losowanych liczb, liczbą maksymalną i liczbą typowań,

  • wylosowane – krotka z wylosowanymi liczbami,

  • ile_trafionych – liczba trafionych liczb.

Do przechowywania tych informacji wykorzystamy słownik, w którym kluczami będą nazwy danych.

Zaczniemy od dodania dwóch funkcji w pliku modułu:

Plik modul_lotek.pyKod nr
 86def wczytaj_dane(nick):
 87    """Funkcja odczytuje dane w formacie json z pliku"""
 88    nazwa_pliku = nick + '.json'
 89    dane = []
 90    if os.path.isfile(nazwa_pliku):
 91        with open(nazwa_pliku, 'r') as plik:
 92            dane = json.load(plik)
 93    return dane
 94
 95
 96def zapisz_dane(nick, dane):
 97    """Funkcja zapisuje dane w formacie json do pliku"""
 98    nazwa_pliku = nick + '.json'
 99    with open(nazwa_pliku, 'w') as plik:
100        json.dump(dane, plik)

Kiedy czytamy i zapisujemy dane w plikach, 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 wtedy prosty format tekstowy JSON.

W funkcji wczytaj_dane() zawartość pliku o podanej nazwie dekoduje do listy: dane = json.load(plik), która jest zwracana. Funkcja zapisz_dane() otwiera plik o podanej nazwie w trybie do zapisu i zapisuje podaną listę danych w formacie JSON: json.dump(dane, plik). Ewentualne dane znajdujące się w pliku są nadpisywane.

Pozostaje nam wykorzystać przygotowane funkcje w programie głównym. Na początku dodajemy importy:

Plik extra_lotek.pyKod nr
1from modul_lotek import wczytaj_ustawienia, pobierz_ustawienia, zapisz_ustawienia
2from modul_lotek import losuj_liczby, pobierz_typy, wypisz_wyniki
3from modul_lotek import wczytaj_dane, zapisz_dane

Dalej na końcu funkcji main(), ale przed instrukcją return 0 dopisujemy:

Plik extra_lotek.pyKod nr
31    losowania = wczytaj_dane(nick)
32    losowanie = {
33        'czas': datetime.strftime(datetime.now(), '%d.%m.%Y %H:%M:%S'),
34        'n': n,
35        'maks': maks,
36        'ile_typowan': ile_typowan,
37        'wylosowane': tuple(liczby),
38        'trafionych': ile_trafionych
39    }
40    losowania.append(losowanie)
41    zapisz_dane(nick, losowania)
42    print(losowania)  # tę instrukcję można zakomentować

Do zmiennej losowania przypisujemy listę zwróconą przez funkcję wczytaj_dane(). Następnie tworzymy słownik losowanie, zawierający wszystkie omówione wyżej dane aktualnej „gry”. Na uwagę zasługuje obsługa daty i czasu przy użyciu metod z podmodułu datetime:

  • now() – metoda zwraca obiekt reprezentujący aktualną datę i czas,

  • strftime() – metoda pozwala przekształcić obiekt w ciąg znaków według ciągu formatującego %d.%m.%Y %H:%M:%S, który oznacza zapis w postaci dzień.miesiąc.rok godzina:minuta:sekunda, np.: 21.10.2025 16:07:23.

Informacja

Serializacja JSON nie obsługuje obiektów typu datetime, dlatego trzeba je zamieniać na ciąg znaków. Alternatywnie do pokazanego wyżej sposobu można użyć metody zwracającej datę w formacie ISO 8601, np. datetime.now().isoformat().

Przygotowany słownik dodajemy do listy słowników: losowania.append(losowanie), którą wypisujemy w celach poglądowych ;-) i zapisujemy za pomocą funkcji zapisz_dane().

1.10.7. Ćwiczenia

  1. Przećwicz w trybie interaktywnym notację wycinkową, wyrażenia listowe i łączenie list:

~$ python3
>>> dane = ['a', 'b', 'c', '1', '2', '3']
>>> dane[0:3]
>>> dane[3:6]
>>> duze = [x.upper() for x in dane[0:3]]
>>> kwadraty = [int(x)**2 for x in dane[3:6]]
>>> duze + kwadraty
  1. Sprawdź w trybie interaktywnym, jak wygląda znakowa reprezentacja aktualnej daty i czasu w formacie ISO 8601.

  2. Załóżmy, że jednak chcielibyśmy zapisywać historię losowań w pliku tekstowym, którego poszczególne linie zawierałyby dane jednego losowania, np.:

    czas:2025-10-28T15:31:27.047438;n:2;maks:10;ile_typowan:2;wylosowane:(5, 7);trafionych:2

    Funkcja zapisująca dane mogłaby wyglądać np. tak:

    Kod nr
    def zapisz_txt(nick, dane):
        """Funkcja zapisuje dane w formacie txt do pliku"""
        nazwa_pliku = nick + "_losowania.txt"
        with open(nazwapliku, "w") as plik:
            for slownik in dane:
                wiersz = [k + ":" + str(w) for k, w in slownik.iteritems()]
                wiersz = ";".join(wiersz)
                print >>plik, wiersz + '\n'
    

    Napisz funkcję czytaj_txt() odczytującą tak zapisane dane, która zwraca listę słowników.

    Użyj obydwu funkcji w programie extra_lotek.py tak, aby wypisywane były dane zwracane przez funkcję zapisz_txt().

    Wskazówka

    Nie musisz zastępować funkcji zapisujących dane w formacie JSON.


Licencja Creative Commons 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:

2026-05-30 o 19:12 w Sphinx 7.3.7

Autorzy:

Robert Bednarz