Swift týden
Pouze tento týden sleva až 80 % na e-learning týkající se Swift
30 % bodů zdarma na online výuku díky naší Slevové akci!

Lekce 3 - Hrací kostka v Pythonu - Konstruktory a náhodná čísla

V předešlém cvičení, Řešené úlohy k 1.-2. lekci OOP v Pythonu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

V minulé lekci, Řešené úlohy k 1.-2. lekci OOP v Pythonu, jsme si naprogramovali první objektovou aplikaci. Již umíme tvořit nové třídy a vkládat do nich atributy a metody s parametry a návratovou hodnotou. Dnes v Python tutoriálu začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.

Založíme si nový soubor a pojmenujeme ho Arena. Vytvoříme novou třídu s názvem Kostka. Zamysleme se nad atributy, které kostce dáme. Jistě by se hodilo, kdybychom si mohli zvolit počet stěn kostky (klasicky 6 nebo 10 stěn, jak je zvykem u tohoto typu her). Dále bude kostka potřebovat tzv. generátor náhodných čísel. Ten nám samozřejmě poskytne modul random, který k těmto účelům obsahuje metodu randint(). Naše třída bude mít nyní atribut: pocet_sten. Minule jsme kvůli jednoduchosti nastavovali všechny atributy naší třídy jako veřejně přístupné. Většinou se však spíše nechce, aby se daly zvenčí modifikovat a proto se nastavují jako soukromé. Soukromé atributy začínají dvěma podtržítky. K atributu poté nelze normálně přistupovat. Při návrhu třídy tedy použijeme pro atributy podtržítka a v případě, že něco bude opravdu potřeba vystavit, je nepoužijeme. Naše třída zatím vypadá asi takto, atributy přidáme až později:

class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

Konstruktory

Až doposud jsme neuměli zvenčí nastavit jiné atributy než veřejné, protože soukromé atributy nejsou zvenčí viditelné. Tento problém se dá obejít pomocí komolení jmen (tzv. name mangling), ale vzhledem k zapouzdření to není dobrá praktika. Již jsme si říkali něco málo o konstruktoru objektu. Je to metoda, která se zavolá ve chvíli vytvoření instance objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a k provedení případné inicializace. Kostku nyní vytvoříme takto:

kostka = Kostka()

Právě Kostka() je konstruktor. Protože v naší třídě žádný není, Python si dogeneruje prázdnou metodu. My si však nyní konstruktor do třídy přidáme. Deklaruje se jako metoda. V Pythonu můžeme použít metody hned dvě. Metodu __new__() a metodu __init__(). Ta první se volá při vytváření objektu, ale většinou si vystačíme se druhou metodu, která se volá při inicializaci objektu. Metodu konstruktoru budeme mí prázdnou. Samozřejmě jako první parametr píšeme self. Do metody vložíme další klíčové slovo pass, které Pythonu říká, aby nic nedělal. Pokud by tam to slovo nebylo, Python by nám vyhodil chybu, že očekává blok příkazů.

def __init__(self):
    pass

Pokud v metodě __init__ jen tak vytvoříme nějakou proměnnou, tak ta po ukončení metody zaniká. Ale my potřebujeme vytvořit atribut pocet_sten. Atributy objektů se vytvářejí všemocným slůvkem self. Za self následuje tečka a název atributu. Vytvoříme tedy veřejný atribut pocet_sten.

def __init__(self):
    self.pocet_sten = 6

Pokud kostku nyní vytvoříme, bude mít atribut pocet_sten nastaven na 6. Vypišme si počet stěn do konzole, ať vidíme, že tam hodnota opravdu je. Celý kód:

class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self):
        self.pocet_sten = 6


kostka = Kostka()
print(kostka.pocet_sten)
input()

Není dobré atribut nastavit jako veřejný, protože nebudeme chtít, aby nám někdo mohl již u vytvořené kostky měnit počet stěn. Přidáme do třídy tedy metodu vrat_pocet_sten(), která nám vrátí hodnotu atributu pocet_sten a tento atribut upravíme na neveřejný. Docílíme tím v podstatě toho, že je atribut read-only (atribut není viditelný a lze ho pouze číst metodou, změnit ho nelze). Python má k tomuto účelu ještě další konstrukce, ale tím se zatím nebudeme zabývat. Upravená verze třídy i metodou:

class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self):
        self.__pocet_sten = 6

    def vrat_pocet_sten(self):
        """
        Vrátí počet stěn kostky.
        """
        return self.__pocet_sten


kostka = Kostka()
print(kostka.vrat_pocet_sten())
input()

Atribut se stal soukromým přidáním dvou podtržítek. Navíc jsme změnili vypisování, jelikož hodnotu atributu zjistíme pouze zavoláním metody.

Výstup:

6
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme potřebovat. Dáme tedy konstruktoru parametr:

def __init__(self, pocet_sten):
    self.__pocet_sten = pocet_sten

Všimněte si, že názvy atributu a argumentu jsou skoro stejné. Pokud bychom měli počet stěn jako veřejný atribut, tak stejný název nevadí. Pomocí self specifikujeme, že levá proměnná pocet_sten náleží instanci, pravou Python chápe jako z parametru (argumentu). S veřejným atributem by situace vypadala takto:

def __init__(self, pocet_sten):
    self.pocet_sten = pocet_sten

Ale vraťme se k původnímu kódu a zkusme si zadat parametr do konstruktoru.

kostka = Kostka(10) # v tuto chvíli se zavolá konstruktor s par. 10
print(kostka.vrat_pocet_sten())
input()

Výstup:

10

Vše funguje, jak jsme očekávali. Python nám již v tuto chvíli nevygeneruje prázdný (tzv. bezparametrický konstruktor), takže kostku bez parametru již vytvořit nelze. My to však můžeme umožnit pomocí uvedení výchozí hodnoty argumentu pocet_sten v definici konstruktoru. Nastavíme ji na 6, protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako výchozí:

def __init__(self, pocet_sten=6):
    self.__pocet_sten = pocet_sten

Zkusme si nyní vytvořit 2 instance kostky, jednu bez udání počtu stěn a jednu s ním:

sestistenna = Kostka()
desetistenna = Kostka(10) #nebo můžeme mít Kostka(pocet_sten=10)
print(sestistenna.vrat_pocet_sten())
print(desetistenna.vrat_pocet_sten())
input()

Výstup:

6
10

Díky klíčovému argumentu nemusíme počet stěn zadávat. Toho můžeme využívat i u všech dalších metod, nejen u konstruktorů. Mnoho funkcí a metod v Pythonu má klíčové argumenty, např. vestavěná funkce print(). Je dobré si u metod projít jejich klíčové argumenty, abyste neprogramovali něco, co již někdo udělal před vámi. Máme tedy konstruktor, který nám umožňuje tvořit různé hrací kostky. Přejděme dál.

Náhodná čísla

Definujme na kostce metodu hod(), která nám vrátí náhodné číslo od 1 do počtu stěn. Metoda nebude mít žádný parametr. Náhodné číslo získáme tak, za pomoci modulu random. Modul si naimportujeme vnitřně (použijeme jedno podtržítko). A použijeme metodu randint().

def hod(self):
    """
    Vykoná hod kostkou a vrátí číslo od 1 do
    počtu stěn.
    """
    import random as _random
    return _random.randint(1, self.__pocet_sten)

Při importování modulů se Python podívá, jestli byl již modul importován, takže pokud modul importoval dříve, tak ho Python znovu neimportuje. Tudíž se první řádek v metodě provede jen jednou.

Překrývání metody __str__

Kostka je téměř hotová, ukažme si ještě jednu užitečnou metodu, kterou si přidáme a kterou budeme hojně používat i ve většině našich dalších objektů. Řeč je o metodě __str__, o které jsme se zatím nezmínili a kterou obsahuje každý objekt, tedy i nyní naše kostka. Metoda je určena k tomu, aby vrátila tzv. textovou reprezentaci instance. Hodí se ve všech případech, kdy si instanci potřebujeme vypsat nebo s ní pracovat jako s textem. Tuto metodu mají např. i čísla. V Pythonu funguje implicitní konverze, jakmile tedy budeme chtít do konzole vypsat číslo nebo kterýkoli jiný objekt, Python na něm zavolá metodu __str__ a vypíše její výstup. Pokud si děláme vlastní třídu, měli bychom zvážit, zda se nám takováto metoda nehodí. Nikdy bychom si neměli dělat vlastní metodu, např. něco jako vypis(), když máme v Pythonu připravenou cestu, jak toto řešit. U kostky nemá __str__ vyšší smysl, ale u bojovníka bude jistě vracet jeho jméno. My si ji ke kostce stejně přidáme, bude vypisovat, že se jedná o kostku a vrátí i počet stěn. Nejprve si zkusme vypsat do konzole naši instanci kostky:

print(sestistenna)

Do konzole se vypíše pouze cesta k naší třídě. Ačkoli je metoda již definována, můžeme ji jednoduše definovat znovu a tím ji překryjeme.

def __str__(self):
    """
    Vrací textovou reprezentaci kostky.
    """
    return str("Kostka s {0} stěnami".format(self.__pocet_sten))

Nyní opět zkusíme do konzole vypsat přímo instanci kostky.

Výstup:

Kostka s 6 stěnami

Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našima dvěma kostkami v cyklech házet a podíváme se, jestli fungují tak, jak se očekává. Upravíme konec souboru:

# vytvoření kostek
sestistenna = Kostka()
desetistenna = Kostka(10)

#hod šestistěnnou
print(sestistenna)
for _ in range(10):
    print(sestistenna.hod(), end=" ")

#hod desetistěnnou
print("\n", desetistenna, sep="")
for _ in range(10):
    print(desetistenna.hod(), end=" ")

input()

Za for následuje podtržítko, jelikož dělat něco s proměnnou v cyklu nepotřebujeme.

Výstup může vypadat nějak takto:

Objektová hrací kostka v C#

Máme hotovou docela pěknou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovu používat komponenty. V příští lekci, Řešené úlohy k 3. lekci OOP v Pythonu, se zaměříme na to, jak se s objekty pracuje v paměti.

V následujícím cvičení, Řešené úlohy k 3. lekci OOP v Pythonu, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Stáhnout

Staženo 489x (940 B)
Aplikace je včetně zdrojových kódů v jazyce python

 

Předchozí článek
Řešené úlohy k 1.-2. lekci OOP v Pythonu
Všechny články v sekci
Objektově orientované programování v Pythonu
Článek pro vás napsal gcx11
Avatar
Jak se ti líbí článek?
16 hlasů
(^_^)
Aktivity (6)

 

 

Komentáře
Zobrazit starší komentáře (30)

Avatar
mixxy
Člen
Avatar
mixxy:8.8.2019 23:01

Ahoj,
jakou mas verzi Pythonu? Ja jsem to zkousel na telefonu a bez problemu. Muzes si zkusit nadefinovat ve tride tu promennou __pocet_sten.

Odpovědět
8.8.2019 23:01
Neni dulezite mnoho vedet a znat. Dulezite je vedet, co je treba.
Avatar
Odpovídá na mixxy
Jan Koloničný:10.8.2019 17:07

Mám Python 3.7.2. Myslíš, že v tom může být problém?

class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self, pocet_sten):
        self.__pocet_sten = pocet_sten

    def vrat_pocet_sten(self):
        return self.__pocet_sten


kostka = Kostka(10)
print(kostka.vrat_pocet_sten())
input()

A píše mi to: AttributeError: 'Kostka' object has no attribute '_Kostka__pocet_sten'

 
Odpovědět
10.8.2019 17:07
Avatar
Odpovídá na Jan Koloničný
Jan Koloničný:10.8.2019 17:39

Chyba byla u mě :D spouštěl jsem úplně něco jiného, než jsem psal :D

 
Odpovědět
10.8.2019 17:39
Avatar
josef rajmon
Člen
Avatar
josef rajmon:22.9.2019 20:44

ahoj mam problem kdyz se dostanu k definovaní vrat_pocet_sten tak mi to zatim hodí
syntax error expected of indented block a absolutne nevim co s tím muze mi nekdo poradit?

class Kostka:
"""
Třída reprezentuje hrací kostku.
"""

def __init__(self):
self.__pocet_sten = 6

def vrat_pocet_sten(sel­f):
"""
Vrátí počet stěn kostky.
"""
return self.__pocet_sten

kostka = Kostka()
print(kostka.vrat_po­cet_sten())
input()

 
Odpovědět
22.9.2019 20:44
Avatar
hanpari
Redaktor
Avatar
Odpovídá na josef rajmon
hanpari:23.9.2019 17:14

Neocekavane odsazeni.
Python od tebe ceka, ze budes odsazovat kod, protoze jeho bloky se neoddeluji zavorkami, ale bilymi znaky na zacatku radky, mezery nebo tabulatory.
Pokud tvuj kod vypada tak, jak jsi ho poslal, tak si s nim interpreter neporadi.
Zkus vlozit svuj kod jako kod. Druhe tlacitko zleva.

 
Odpovědět
23.9.2019 17:14
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
josef rajmon
Člen
Avatar
Odpovídá na hanpari
josef rajmon:23.9.2019 17:47

je to ten samí kod co nahoře jen mi to vzdy vyhodí tabulku s tou chybou a označí tohle a radek nad tím: kostka = Kostka()

class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self):
        self.__pocet_sten = 6

    def vrat_pocet_sten(self):
    """
    Vrátí počet stěn kostky.
    """
    return self.__pocet_sten


kostka = Kostka()
print(kostka.vrat_pocet_sten())
input()
 
Odpovědět
23.9.2019 17:47
Avatar
hanpari
Redaktor
Avatar
Odpovídá na josef rajmon
hanpari:24.9.2019 8:01
class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self):
        self.__pocet_sten = 6

    def vrat_pocet_sten(self):
        """CHYBA V ODSAZENI
        Vrátí počet stěn kostky.
        """
        return self.__pocet_sten


kostka = Kostka()
print(kostka.vrat_pocet_sten())
input()

Chyba je v celém odsazení metody vrat_pocet_sten

A ano, v článku je chyba.

 
Odpovědět
24.9.2019 8:01
Avatar
josef rajmon
Člen
Avatar
Odpovídá na hanpari
josef rajmon:24.9.2019 10:50

Aha dekuji moc

 
Odpovědět
24.9.2019 10:50
Avatar
skamos
Člen
Avatar
skamos:19.10.2019 15:48

Ahoj. Proč se píše

return str("Kostka s {0} stěnami".format(self.__pocet_sten))

když

return "Kostka s {0} stěnami".format(self.__pocet_sten)

vrátí stejný výsledek? Proč je použita metoda str , chápu, že to pak vrátí string, ale to snad i bez té metody, alespoň v tomhle případě.

Editováno 19.10.2019 15:50
 
Odpovědět
19.10.2019 15:48
Avatar
Marty
Člen
Avatar
Marty:16. března 5:25

Modul si naimportujeme vnitřně (použijeme jedno podtržítko).
Při importování modulů se Python podívá, jestli byl již modul importován, takže pokud modul importoval dříve, tak ho Python znovu neimportuje.

Stále nechápu, proč tam je to začáteční podtržítko. Nejde to bez vytváření aliasu _random?

Díky.

 
Odpovědět
16. března 5:25
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 40. Zobrazit vše