3. diel - Hracia kocka v Pythone - Konštruktory a náhodné čísla
V predchádzajúcom cvičení, Riešené úlohy k 1.-2. lekciu OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 1.-2. lekciu OOP v Pythone , sme si naprogramovali prvú objektovú aplikáciu. Už vieme tvoriť nové triedy a vkladať do nich atribúty a metódy s parametrami a návratovú hodnotou. Dnes v Python tutoriálu začneme pracovať na sľúbené aréne, v ktorej budú proti sebe bojovať dvaja bojovníci. Boj bude ťahový (na preskáčku) a bojovník vždy druhému uberie život na základe sily jeho útoku a obrany druhého bojovníka. Simulujeme v podstate stolný hru, budeme teda simulovať aj hraciu kocku, ktorá dodá hre prvok náhodnosti. Začnime zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme ako definovať vlastné konštruktor.
Založíme si nový súbor a pomenujeme ho Arena. Vytvoríme novú triedu s
názvom Kocka. Zamyslime sa nad atribúty, ktoré kocke dáme. Iste by sa
hodilo, keby sme si mohli zvoliť počet stien kocky (klasicky 6 alebo 10 stien,
ako je zvykom u tohto typu hier). Ďalej bude kocka potrebovať tzv. Generátor
náhodných čísel. Ten nám samozrejme poskytne modul random, ktorý na tieto
účely obsahuje metódu randint()
. Naša trieda bude mať teraz
atribút: pocet_sten. Minule sme kvôli jednoduchosti nastavovali
všetky atribúty našej triedy ako verejne prístupné. Väčšinou sa však
skôr nechce, aby sa dali zvonku modifikovať a preto sa nastavujú ako
súkromné. Súkromné atribúty začínajú dvoma podtržníkmi. K atribútu
potom nedá normálne pristupovať. Pri návrhu triedy teda použijeme pre
atribúty podčiarknutia a v prípade, že niečo bude naozaj potrebné
vystaviť, je nepoužijeme. Naša trieda zatiaľ vyzerá asi takto, atribúty
pridáme až neskôr:
class Kostka: """ Třída reprezentuje hrací kostku. """
Konštruktory
Až doteraz sme nevedeli zvonku nastaviť iné atribúty ako verejné, pretože súkromné atribúty nie sú zvonku viditeľné. Tento problém sa dá obísť pomocou komolenie mien (tzv. Name mangling), ale vzhľadom na zapuzdrenie to nie je dobrá praktika. Už sme si hovorili niečo málo o konstruktoru objektu. Je to metóda, ktorá sa zavolá vo chvíli vytvorenia inštancie objektu. Slúži samozrejme k nastavenie vnútorného stavu objektu a na vykonanie prípadnej inicializácia. Kocku teraz vytvoríme takto:
kostka = Kostka()
Práve Kocka () je konštruktor. Pretože v našej triede žiadny nie je, Python si dogeneruje prázdnu metódu. My si však teraz konštruktor do triedy pridáme. Deklaruje sa ako metóda. V Pythone môžeme použiť metódy hneď dve. Metódu __new __ () a metódu __init __ (). Tá prvá sa volá pri vytváraní objektu, ale väčšinou si vystačíme so druhú metódu, ktorá sa volá pri inicializácii objektu. Metódu konstruktoru budeme moji prázdnu. Samozrejme ako prvý parameter píšeme self. Do metódy vložíme ďalšie kľúčové slovo pass, ktoré Pythone hovorí, aby nič nerobil. Ak by tam to slovo nebolo, Python by nám vyhodil chybu, že očakáva blok príkazov.
def __init__(self): pass
Ak v metóde __init__ len tak vytvoríme nejakú premennú, tak tá po ukončení metódy zaniká. Ale my potrebujeme vytvoriť atribút pocet_sten. Atribúty objektov sa vytvárajú všemocným slovkom self. Za self nasleduje bodka a názov atribútu. Vytvoríme teda verejný atribút pocet_sten.
def __init__(self): self.pocet_sten = 6
Ak kocku teraz vytvoríme, bude mať atribút pocet_sten nastavený na 6. Vypíšme si počet stien do konzoly, nech vidíme, že tam hodnota naozaj 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()
Nie je dobré atribút nastaviť ako verejný, pretože nebudeme chcieť, aby nám niekto mohol už u vytvorenej kocky meniť počet stien. Pridáme do triedy teda metódu vrat_pocet_sten (), ktorá nám vráti hodnotu atribútu pocet_sten a tento atribút upravíme na neverejný. Docielime tým v podstate toho, že je atribút read-only (atribút nie je viditeľný a možno ho len čítať metódou, zmeniť ho nemožno). Python má na tento účel ešte ďalšie konštrukcie, ale tým sa zatiaľ nebudeme zaoberať. Upravená verzia triedy i metódou:
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()
Atribút sa stal súkromným pridaním dvoch podčiarkovníkov. Navyše sme zmenili vypisovanie, pretože hodnotu atribútu zistíme len zavolaním metódy.
výstup:
6
Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme mohli pri každej kocky pri vytvorení špecifikovať, koľko stien budeme potrebovať. Dáme teda konstruktoru parameter:
def __init__(self, pocet_sten): self.__pocet_sten = pocet_sten
Všimnite si, že názvy atribútu a argumentu sú skoro rovnaké. Ak by sme mali počet stien ako verejný atribút, tak rovnaký názov nevadí. Pomocou self špecifikujeme, že ľavá premenná pocet_sten náleží inštanciu, pravú Python chápe ako z parametra (argumentu). S verejným atribútom by situácia vyzerala takto:
def __init__(self, pocet_sten): self.pocet_sten = pocet_sten
Ale vráťme sa k pôvodnému kódu a skúsme si zadať parameter 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šetko funguje, ako sme očakávali. Python nám už v tejto chvíli
nevygeneruje prázdny (tzv. Bezparametrický konštruktor), takže kocku bez
parametra už vytvoriť nedá. My to však môžeme umožniť pomocou uvedenia
predvolené hodnoty argumentu pocet_sten
v definícii konstruktoru.
Nastavíme ju na 6, pretože takú hodnotu asi užívateľ našej triedy u kocky
očakáva ako predvolený:
def __init__(self, pocet_sten=6): self.__pocet_sten = pocet_sten
Skúsme si teraz vytvoriť 2 inštancie kocky, jednu bez udania počtu stien 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
Vďaka kľúčovému argumentu nemusíme počet stien zadávať. Toho môžeme využívať aj u všetkých ďalších metód, nielen u konstruktoru. Mnoho funkcií a metód v Pythone má kľúčové argumenty, napr. Vstavaná funkcia print (). Je dobré si u metód prejsť ich kľúčové argumenty, aby ste neprogramoval niečo, čo už niekto urobil pred vami. Máme teda konštruktor, ktorý nám umožňuje tvoriť rôzne hracie kocky. Prejdime ďalej.
Náhodné čísla
Definujme na kocke metódu hod (), ktorá nám vráti náhodné číslo od 1 do počtu stien. Metóda nebude mať žiadny parameter. Náhodné číslo získame tak, za pomoci modulu random. Modul si importujete vnútorne (použijeme jedno znak podčiarknutia). A použijeme metódu 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)
Pri importovaní modulov sa Python pozrie, či bol už modul importovaný, takže ak modul importoval skôr, tak ho Python znova neimportujete. Teda sa prvý riadok v metóde vykoná len raz.
Prekrývanie metódy __str__
Kocka je takmer hotová, ukážme si ešte jednu užitočnú metódu, ktorú si pridáme a ktorú budeme hojne používať aj vo väčšine našich ďalších objektov. Reč je o metóde __str__, o ktorej sme sa zatiaľ nezmienili a ktorú obsahuje každý objekt, teda aj teraz naše kocka. Metóda je určená na to, aby vrátila tzv. Textovú reprezentáciu inštancie. Hodí sa vo všetkých prípadoch, kedy si inštanciu potrebujeme vypísať alebo s ňou pracovať ako s textom. Túto metódu majú napr. Aj čísla. V Pythone funguje implicitné konverzie, akonáhle teda budeme chcieť do konzoly vypísať číslo alebo ktorýkoľvek iný objekt, Python na ňom zavolá metódu __str__ a vypíše jej výstup. Ak si robíme vlastný triedu, mali by sme zvážiť, či sa nám takáto metóda nehodí. Nikdy by sme si nemali robiť vlastnú metódu, napr. Niečo ako vypis (), keď máme v Pythone pripravenú cestu, ako toto riešiť. U kocky nemá __str__ vyšší zmysel, ale u bojovníka bude určite vracať jeho meno. My si ju ku kocke rovnako pridáme, bude vypisovať, že sa jedná o kocku a vráti aj počet stien. Najprv si skúsme vypísať do konzoly našu inštanciu kocky:
print(sestistenna)
Do konzoly sa vypíše iba cesta k našej triede. Hoci je metóda už definovaná, môžeme ju jednoducho definovať znova a tým ju prekryjeme.
def __str__(self): """ Vrací textovou reprezentaci kostky. """ return str("Kostka s {0} stěnami".format(self.__pocet_sten))
Teraz opäť skúsime do konzoly vypísať priamo inštanciu kocky.
výstup:
Kostka s 6 stěnami
Ešte si naše kocky vyskúšame. Skúsime si v programe s našimi dvoma kockami v cykloch hádzať a pozrieme sa, či fungujú tak, ako sa očakáva. Upravíme koniec súboru:
# 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 nasleduje podčiarkovník, pretože robiť niečo s premennou v cykle nepotrebujeme.
Výstup môže vyzerať nejako takto:
Máme hotovú celkom peknú a nastaviteľnou triedu, ktorá reprezentuje hraciu kocku. Bude sa nám hodiť v našej aréne, ale môžete ju použiť aj kdekoľvek inde. Vidíme, ako OOP umožňuje znovu používať komponenty. V budúcej lekcii, Riešené úlohy k 3. lekcii OOP v Pythone , sa zameriame na to, ako sa s objektmi pracuje v pamäti.
V nasledujúcom cvičení, Riešené úlohy k 3. lekcii OOP v Pythone, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.