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

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, První objektová aplikace v Pythonu - Hello object world, 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, Odkazy na objekty, jejich kopírování a Garbage collector, se zaměříme na to, jak se s objekty pracuje v paměti.


 

Stáhnout

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

 

 

Aktivity (6)

 

 

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

Avatar
Erik Šťastný:10. dubna 8:36

V použití žádný rozdíl není, importuješ ten stejný modul jen si pro něj vytvoříš alias (vlastní pojmenování)

Z mého pohledu to nemám rád a nikdy to nepoužívám, akorát pořád zjišťuju co je daný alias zač při čtení kódu.

 
Odpovědět  +1 10. dubna 8:36
Avatar
Jan Koloničný:8. srpna 19:50

Ahoj,
I když dělám vše podle vzoru

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()

píše mi to při výstupu:
AttributeError: 'Kostka' object has no attribute '_Kostka__pocet_sten'

Nevím, v čem je problém? Nevíte náhodou někdo?

 
Odpovědět 8. srpna 19:50
Avatar
mixxy
Člen
Avatar
Odpovídá na Jan Koloničný
mixxy:8. srpna 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. srpna 23:01
Neni dulezite mnoho vedet a znat. Dulezite je vedet, co je treba.
Avatar
Odpovídá na mixxy
Jan Koloničný:10. srpna 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. srpna 17:07
Avatar
Odpovídá na Jan Koloničný
Jan Koloničný:10. srpna 17:39

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

 
Odpovědět 10. srpna 17:39
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
josef rajmon:22. září 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. září 20:44
Avatar
hanpari
Redaktor
Avatar
Odpovídá na josef rajmon
hanpari:23. září 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. září 17:14
Avatar
josef rajmon
Člen
Avatar
Odpovídá na hanpari
josef rajmon:23. září 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. září 17:47
Avatar
hanpari
Redaktor
Avatar
Odpovídá na josef rajmon
hanpari:24. září 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  +1 24. září 8:01
Avatar
josef rajmon
Člen
Avatar
Odpovídá na hanpari
josef rajmon:24. září 10:50

Aha dekuji moc

 
Odpovědět  +1 24. září 10:50
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 38. Zobrazit vše