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

Python Objektově orientované programování Hrací kostka v Pythonu - Konstruktory a náhodná čísla

Unicorn College ONEbit hosting 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 třídu Random. 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

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). Příklad:

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í klíčového argumentu. V něm nastavíme počet stěn 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, každou jiným konstruktorem:

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 294x (940 B)
Aplikace je včetně zdrojových kódů v jazyce python

 

 

Článek pro vás napsal gcx11
Avatar
Jak se ti líbí článek?
11 hlasů
(^_^)
Aktivity (2)

 

 

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

Avatar
hanpari
Redaktor
Avatar
hanpari:3.2.2015 12:23

Metoda bude dělat to, co jí řekneš, aby dělala. V tomto případě využívá znalost vnitřního stavu kostky, tj. kolik má kostka stěn.

pro 10-stěnnou kostku bude vědět, že má generovat od 1 do 10, pro šestistěnnou zase 1 do 6.

 
Odpovědět 3.2.2015 12:23
Avatar
theeyeball
Člen
Avatar
theeyeball:16.8.2017 15:15

Ahoj,
jak prosím zjistím při vytváření objektu, jaké má třída atributy?
Používám defaultní pythonovské IDLE, ale když např. vytvářím tu desetičlennou kostku, jak mám vědět, že je tam atribut počet stěn?
Jsem zvyklý z Visual studia u c#, že se tam objevil našeptávač.

Děkuji, za případnou odpověď.

 
Odpovědět 16.8.2017 15:15
Avatar
hanpari
Redaktor
Avatar
Odpovídá na theeyeball
hanpari:16.8.2017 22:05

Idle není Visual Studio a Python není C#. Idle je rychlé a jednoduché. Nejrychlejší způsob, jak se v Idle dostat k nápovědě, je F5. Idle potřebuje kód alespoň jednou spustit. Jinak se v nastavení podívej, jakou klávesovou zkratku máš u force-open-calltips. Většinou to je Ctrl+\, což je třeba na mojí klávesnici problém, protože mám zdvojený backspace. Proto je lepší si toto předefinovat.

Popřípadě můžeš zkusit plnohodnodnotné IDE jako je PyCharm Community nebo VS Code s vhodným rozšířením. Ale Idle je fajn na zkoušení.

Editováno 16.8.2017 22:06
 
Odpovědět  +2 16.8.2017 22:05
Avatar
brevnovak
Člen
Avatar
brevnovak:22. ledna 15:33

Samozřejmě jako první parametr píšeme self

to mi samozrejmy neprijde. ja doted delal v jave a tohle tam není. proc je tam ten parametr nutny? dik :)

 
Odpovědět 22. ledna 15:33
Avatar
Martin Petrovaj
Překladatel
Avatar
Odpovídá na brevnovak
Martin Petrovaj:22. ledna 17:47

Z predchádzajúceho dielu:

První povinný poziční argument je self. Do něj se vloží "odkaz" na objekt, do kterého metoda náleží. Tento argument tam vloží sám objekt.

Jednoducho povedané, v Pythone je volanie metódy na inštancii v podstate syntactic sugar pre volanie metódy na triede, kde prvým parametrom je inštancia. Viem, zložité, ale na tomto príklade si to snáď predstavíš lepšie:

class Test:

        def vypis_triedou(text):
                print(text)

        def vypis_instanciou(self, text):
                print(text)

        def vypis_vsetko(self, text):
                print(f"{self}\n{text}")


Test.vypis_triedou("Ahoj")
Test.vypis_instanciou("Ahoj")   // vyhodí TypeError, čaká 2 argumenty (self, text), dostane 1 (text)

inst = Test()
inst.vypis_triedou("Hey")       // vyhodí TypeError, čaká 1 arg (text), dostane 2 (self - odkaz na objekt 'inst', text)
inst.vypis_instanciou("Hey")


// zápisy Test.vypis_instanciou(inst, "Ahoj") a inst.vypis_instanciou("Ahoj") sú ekvivalentné

inst.vypis_vsetko("Hello!")     // pre zaujímavosť môžeš ešte vyskúšať toto

Pre viac informácií si skús prečítať napr. toto vlákno:
https://stackoverflow.com/…pose-of-self

Editováno 22. ledna 17:48
Odpovědět  +1 22. ledna 17:47
if (this.motto == "") { throw new NotImplementedException(); }
Avatar
Martin Petrovaj
Překladatel
Avatar
Odpovídá na Martin Petrovaj
Martin Petrovaj:22. ledna 19:43

Musím sa ešte ospravedlniť za použitie lomítiek namiesto hashtagov pre komentáre, Python používam len tu-tam a nevšimol som si to včas O:-)

Odpovědět 22. ledna 19:43
if (this.motto == "") { throw new NotImplementedException(); }
Avatar
Rudolf Kov
Člen
Avatar
Rudolf Kov:30. ledna 21:16

Nevíte někdo prosím, co tu mám špatně? Dělal jsem to přesně podle toho návodu, ale pokaždý to napíše chybu:
AttributeError: 'Kostka' object has no attribute 'pocet_sten'
můj kód:
class Kostka:
def __init__(self, pocet_sten=6):
self.__pocet_sten=po­cet_sten
def vrat_pocet_sten(sel­f):
return self.pocet_sten

sestistenna=Kos­tka()
desetistenna=Kos­tka(10)
print(sestisten­na.vrat_pocet_sten())
print(desetis­tenna.vrat_po­cet_sten())
input()

Předem děkuji za odpověd.

 
Odpovědět 30. ledna 21:16
Avatar
gcx11
Redaktor
Avatar
Odpovídá na Rudolf Kov
gcx11:2. února 19:19

Ahoj, máš tam

return self.pocet_sten

namísto

return self.__pocet_sten
 
Odpovědět 2. února 19:19
Avatar
Jiří Forst
Člen
Avatar
Jiří Forst:27. července 17:56

Čauky
Prosím poraďte mi nevyznám se co znamená {0}
Dík ,
return str("Kostka s {0} stěnami".format(self

 
Odpovědět 27. července 17:56
Avatar
Martin Petrovaj
Překladatel
Avatar
Odpovídá na Jiří Forst
Martin Petrovaj:27. července 18:11

Formátování textu nám umožňuje vkládat do něj jednoduše proměnné pomocí zástupných znaků. Docílíme tak přehlednějšího kódu, než kdybychom řetězec nastavovali.

Formátovat text lze pomocí metody format(), …

Formátování probíhá tak, že jako argumenty dáme do metody jednotlivé řetězce, nebo odkazy na řetězce. První argument má index 0, další má index 1, atd.

Příklad:

>>> prvni_retezec = "Python"
>>> retezec = "{0} je nejlepší!".format(prvni_retezec)
>>> retezec
'Python je nejlepší!'

https://www.itnetwork.cz/…-pokracovani

Odpovědět 27. července 18:11
if (this.motto == "") { throw new NotImplementedException(); }
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 21. Zobrazit vše