21. diel - Hracia kocka v Pythone - Random a prekrývanie metód
V predchádzajúcom kvíze, Kvíz - Dátum a čas v Pythone, sme si overili nadobudnuté skúsenosti z predchádzajúcich lekcií.
V tomto tutoriále objektovo orientovaného programovania v Pythone budeme pokračovať v tvorbe našej objektovej hracej kocky. Začneme s metódou pre hod. Vysvetlíme si na nej bližšie vnútorný import a potom dôkladne preskúmame princípy prekrývania metód. Vrátime sa tiež k dôležitým detailom týkajúcich sa zapuzdrenia.
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 za pomoci modulu random
. Modul si
naimportujeme vnútorne (použijeme jedno
podčiarknutie) a použijeme metódu randint()
:
Použitie jedného podčiarkovníka pred názvom importovaného modulu (alebo akejkoľvek inej premennej) je - nám už známou - konvenciou v Pythone, ktorá signalizuje, že táto premenná by mala byť považovaná za privátnu a nemala by byť priamo pristupovaná mimo triedy alebo modulu.
Na čo slúži vnútorný import
Výraz "vnútorný import" sa vzťahuje na prax importovania modulu priamo vo vnútri funkcie alebo metódy, namiesto toho, aby bol importovaný na začiatku súboru. V niektorých prípadoch sa to môže hodiť, najmä ak:
- modul potrebujeme len v určitých špecifických situáciách,
- import modulu je nákladný na pamäť a čas a nechceme, aby spomaľoval štart aplikácie.
Nadmerné používanie vnútorných importov kód komplikuje a znižuje jeho čitateľnosť. Dobrou praxou je teda používať ich uvážene a len v situáciách, keď prinášajú skutočné výhody.
Prekrývanie metódy
__str__()
Kocka je takmer hotová. Ukážme si teraz ešte jednu užitočnú metódu,
ktorú budeme hojne používať aj vo väčšine našich ďalších objektov.
Reč je o metóde __str__()
. Túto metódu obsahuje
každý objekt, teda aj teraz naša 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, keď 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á konverzia. 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ď už máme v Pythone pripravenú cestu, ako toto
riešiť. Pri kocke nemá __str__()
vyšší zmysel, ale u
bojovníka bude určite vracať jeho meno. My si ju ku kocke aj tak pridáme.
Bude vypisovať, že sa jedná o kocku a vráti aj počet stien. Najprv si
vypíšme do konzoly našu inštanciu kocky:
Do konzoly sa vypíše iba cesta k našej triede:
Hoci je metóda už
definovaná a poskytuje nám určitý výstup, chceme o našej kocke získať
lepšie informácie. Najlepšie také, o ktorých sami rozhodneme, že je
vhodné ich zobraziť. To docielime tým, že vstavanú metódu
__str__()
jednoducho definujeme sami znova a tým ju
prekryjeme:
V konzole uvidíme:
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:
Za
for
nasleduje podtržítko. Divné, že? V kontexte slučky
for
sa jednoduché podtržítko _
používa ako
dočasná premenná, keď nás skutočná hodnota v každom priechode slučkou
nezaujíma. Je to konvenčný spôsob, ako naznačiť, že hodnota tejto
premennej nebude v danom kontexte využitá.
Máme hotovú celkom peknú a nastaviteľnú triedu, ktorá reprezentuje hraciu kocku. Bude sa nám hodiť v našej aréne, ale k dispozícii ju máme na využitie aj kdekoľvek inde. Vidíme, ako OOP umožňuje znovu používať komponenty.
Zapuzdrenie v Pythone detailne
Teraz už máme dostatok znalostí, aby sme sa dôkladne zahĺbali nad možnosťami zapuzdrenia. Povedali sme si, že aj keď nám Python umožňuje nastavovať atribúty privátne, nie sú od chtivých rúk programátora úplne oddelené.
Uveďme si príklad s našou kockou. Upravíme atribút
pocet_sten
na privátne pomocou dvoch
podčiarkovníkov a skúsme k nemu pristúpiť v kóde, zmeniť jeho hodnotu a
tú vypísať:
V konzole uvidíme výstup:
To je veľmi zaujímavé správanie, že? Zdá sa, že Python nám umožňuje bez vyvolania chyby zmeniť privátny atribút, ale na túto zmenu potom neberie ohľad. Bohužiaľ, je to trochu komplikovanejšie.
Už sme si povedali, že v Pythone neexistuje striktná privátnosť, ako ju
poznáme z iných jazykov (ako je Java alebo C++). Namiesto toho Python
používa konvenciu "name mangling". Keď deklarujeme atribút s dvojitým
podčiarkovníkom, napr. __pocet_sten
, Python tento názov
automaticky zmení na _NazevTridy__pocet_sten
. V prípade nášho
kódu sa __pocet_sten
interne stáva atribútom s názvom
_Kostka__pocet_sten
.
Náš pokus o priradenie novej hodnoty tomuto atribútu
(kostka.__pocet_sten = 365
) tak vôbec neupravil pôvodný
privátny atribút __pocet_sten
triedy Kostka
.
Vytvoril totiž atribút úplne nový. Pôvodný privátny
atribút, ktorý sa vďaka dvojitému podtržítku premenoval na
_Kostka__pocet_sten
, zostáva nezmenený. Preto posledný výpis v
našom pokuse potvrdil, že kocka je stále desaťstenná.
Prístup k privátnemu atribútu
Pokiaľ by sme naozaj chceli počet stien kocky zmeniť, museli by sme použiť tento kód:
V konzole potom uvidíme:
Prečo ide o zlú praktiku
Hlavným dôvodom, prečo sú niektoré atribúty označené ako privátne, je zapuzdrenie. Zapúzdrenie je jedným z kľúčových princípov OOP. Priamy prístup k privátnym atribútom túto koncepciu narúša. Predstavme si, že sa autor triedy neskôr rozhodne zmeniť implementáciu alebo štruktúru interného atribútu a my mu v inej časti kódu "natvrdo" meníme jeho hodnotu. Máločo je lepší spôsob, ako do kódu zaniesť neočakávané chyby. Prístup k privátnym atribútom tiež výrazne zhorší čitateľnosť kódu.
Skrátka, aj keď môžeme technicky pristupovať k privátnym atribútom cez „name mangling“, mali by sme to robiť len v naozaj výnimočných situáciách, a radšej vôbec nie. Takmer vždy je lepšie rešpektovať zapuzdrenie a pracovať s objektmi tak, ako boli navrhnuté.
Vytváranie atribútov za behu
Povedali sme si, že pokus o priame prepísanie privátneho atribútu skončil tak, že Python namiesto toho vytvoril nový atribút. V Pythone je povolená veľká miera dynamiky, a to zahŕňa schopnosť pridávať atribúty k objektom za behu. Toto je často považované za jednu z flexibilných vlastností jazyka, ale veľmi ľahko to spôsobí zmätky, pokiaľ si nie sme tejto vlastnosti vedomí.
Triedy v Pythone sú modifikovateľné za behu. Keď vytvoríme inštanciu triedy, Python nám umožňuje tejto inštancii pridať nové atribúty, aj keď neboli definované v pôvodnej triede.
Ukážme si príklad: Vo výstupe konzoly uvidíme:
Je zrejmé, že táto vlastnosť Pythona vedie do nebezpečných vôd:-) Vymazlíme si triedu k dokonalosti, len aby nám do nej kolega napchal svoje vlastné atribúty, ktoré nemusia byť úplne konzistentné s naším návrhom. Potenciálne more problémov je jasné. Našťastie pre to má Python riešenie.
Využitie atribútu
__slots__
V Pythone je __slots__
špeciálny atribút triedy, ktorý
definuje pevný zoznam atribútov, ktoré môže mať inštancia tejto triedy.
Použitím __slots__
vylúčime možnosť vytvárať nové
atribúty mimo definovaného zoznamu:
Pokus o vytvorenie
atribut3
vyvolá chybu:
To je pre dnešnú lekciu všetko. Kompletný kód kocky je k dispozícii v archíve.
V budúcej lekcii, Dekorátory druhýkrát - Parametrické a triedne dekorátory , budeme v téme dekorátorov pokračovať.