
Python: Efektívny lovec / 7. časť
MVýnimky
Interpreter Pythonu pri vzniku chyby, ktorá nastane počas vykonávania programu (runtime error), vytvorí objekt nazývaný výnimka (exception). Interpreter ukončí vykonávanie programu a informuje o vzniku výnimky cestou oznamu (traceback), ktorý obsahuje dve časti:
1. typ chyby,
2. špecifikácia chyby.
V prípade, ak chceme pripraviť kód, o ktorom vieme, že by mohol generovať výnimku, no nechceme, aby došlo k ukončeniu vykonávania programu, môžeme použiť výkonný výraz try a uzavrieť problematický kód do jeho tela, ako napr.:
subor = input("Zadaj názov súboru:")
try:
# Kód, ktorý by mohol generovať výnimku
f = open(subor,"r")
except FileNotFoundError:
# Ak nastala výnimka typu
FileNotFoundError
print("Súbor neexistuje!")
else:
# Ak nenastala výnimka
print("Súbor bol otvorený na čítanie")
finally:
# Vykoná sa v každom prípade
print("Hotovo")
Kód uvádzaný v tele poslednej klauzuly finally môžeme využiť na riadne uzavretie rozpracovaných, resp. čiastočne rozpracovaných činností, a to v oboch prípadoch, teda aj v prípade, ak výnimka nastane, ale aj v prípade, ak nenastane.
Používateľské výnimky
Okrem výnimiek, ktoré generuje interpreter, môžeme vytvárať vlastné výnimky. Robíme tak pomocou objektu ValueError, ktorému špecifikujeme rozsah informácií týkajúcich sa našej výnimky. Používateľom definovanú výnimku generujeme pomocou výkonného výrazu raise. Ako príklad môžeme uviesť klasický problém delenia nulou:
def delenie_cisiel(a,b):
if (b == 0):
raise ValueError("Nastala výnimka: nemožno deliť nulou!")
else:
return (a/b)
Príklad č. 10
PyGame
Napriek tomu, že kód písaný v jazyku Python nemožno považovať za najefektívnejší a najrýchlejší, najmä čo sa týka programovania real-time počítačových hier, jeho využitie stále stúpa. Hry, o ktorých vieme, že využijú všetok počítačový výkon, budeme musieť programovať v assembleri či jazyku C. Týka sa to najmä ich herného enginu. Herná logika sa však môže realizovať Pythonom rovnako ako mnohé hry, ktoré nebudú prehnane náročné na počítačový výkon.
Knižnica pygame (pygame.org) je jedna z často používaných knižníc určených na tvorbu hier a herného používateľského rozhrania. Je založená na multiplatformovej knižnici SDL (Simple DirectMedia Layer – libsdl.org), pričom zahŕňa funkcie na prácu s obrázkami, videom, hudbou, zvukmi a ďalšími multimediálnymi a vstupno-výstupnými komponentmi počítača. Okrem úzkej spolupráce s knižnicou SDL má možnosť vytvárať rozhranie (interface) pre knižnicu PyOpenGL.
Objektovo orientované programovanie
V ranej ére programovania mala štruktúra programového kódu procedurálny charakter. Znamená to, že pri programovaní sa hlavné (main) telo programu odvolávalo na funkcie, resp. procedúry, ktoré spracúvali zadané údaje a výsledky odovzdávali späť hlavnému telu. Tento spôsob programovania fungoval spoľahlivo až do okamihu masívneho rozšírenia počítačových sietí a takisto dovtedy, kým sa malé programy nezväčšili na objemné balíky.
So zväčšovaním sa programových balíkov a s tým súvisiacim nabaľovaním kódu, ktorý už nepripravoval jediný programátor, ale veľká skupina ľudí, začalo programovanie nadobúdať omnoho väčšie rozmery. Údaje a im prislúchajúce funkcie sa preto začali zoskupovať do celkov – objektov s cieľom systematizácie práce a čo najjednoduchšej orientácie v rozsiahlom programovom kóde. Vzniklo tak objektovo orientované programovanie (OOP).
Triedy
Programovacie jazyky v súlade s OOP ponúkajú štruktúry, ktoré zoskupujú (zapuzdrujú) údaje a funkcie patriace spoločnej entite. Python tento prístup povyšuje až na úroveň, keď všetky svoje konštrukcie reprezentuje objektmi, ktoré majú im vlastné údaje (atribúty) a funkcie (metódy). V zmysle OOP implementuje tzv. triedy (classes). Príklad jednoduchej triedy je nasledujúci:
class Osoba:
""" Práca s osobou """
def __init__(self,meno,priezvisko,
rok_narodenia):
self.meno = meno
self.priezvisko = priezvisko
self.rok_narodenia = rok_narodenia
def getmeno(self,co):
if (co == "meno"):
return (self.meno)
elif (co == "priezvisko"):
return (self.priezvisko)
else:
return (self.meno,self.priezvisko)
def getrok_narodenia(self):
return (self.rok_narodenia)
Triedy zoskupujú atribúty = členov a metódy, ktoré sú určené na manipuláciu s členmi. Manipuláciou rozumieme zmenu hodnôt atribútov, ktorej výsledkom je zmena vnútorného stavu (state) objektu triedy. Základná metóda každej triedy je tzv. inicializačná metóda __init__(), ktorá je volaná automaticky pri vytvorení novej inštancie (nového objektu) triedy. Na novo vytvorený objekt triedy ukazuje špeciálny parameter self, pomocou ktorého sprístupňujeme jednotlivé členy. Objekty tried môžeme okrem vytvárania takisto mazať, a to pomocou kľúčového slova del.
Objekty tried možno vytvárať aj pri volaní funkcií ako ich argumenty a takisto aj priamo v rámci návratovej hodnoty výrazu return. Navyše sa dajú vytvárať záznamy objektov tried, to značí, že objekty tried môžu byť napr. položkami typu list:
studenti = [Student("Ján","Prvý",1980,1),
Student("Peter","Druhý",1979,2),
Student("Erik","Tretí",1978,3)]
Atribúty a rovnosť tried
Atribúty tried sú meniteľné (mutable). Ich modifikácia je možná pomocou klasického zápisu s bodkou. Takýto prístup však nie je z pohľadu OOP najvhodnejší, pričom manipulácia s členmi tried by sa mala vykonávať prioritne cestou ich metód.
Pri úvahách o rovnosti dvoch inštancií tried musíme rozlišovať medzi dvoma prípadmi:
1. rovnosť atribútov,
2. rovnosť odkazov.
Pri rovnosti odkazov sú porovnávané inštancie ukazujúce na objekty tried, ktoré môžu/nemusia byť totožné. Dve inštancie ukazujúce na dva rozdielne objekty tried však môžu mať rovnaké atribúty. Ak chceme porovnávať obsah dvoch objektov tried, nemôžeme porovnávať inštancie, ale ich vnútorný stav. Zvlášť dôležité je poznať odlišnosť v porovnávaní pomocou operátora ‘==‘. Ten v prípade typu list porovnáva položky záznamu, ale v prípade tried porovnáva inštancie (odkazy na objekty tried).
Kopírovanie tried
Pokiaľ v programovom kóde použijeme na tvorbu inštancie triedy priraďovací operátor ‘=‘, vytvoríme alias, čo znamená, že akákoľvek zmena obsahu jednej inštancie automaticky zmení obsah druhej. Na vytvorenie samostatnej kópie objektu triedy je nevyhnutné použiť funkciu copy() modulu copy. Aj v tomto prípade však musíme pozorne skontrolovať obsah kopírovanej triedy. Pokiaľ sa v ňom nachádzajú inštancie iných tried, ich kópia sa nevykoná, ale vytvoria sa aliasy. Atribút skopírovanej triedy, ktorý bol v origináli inštanciou inej triedy, bude ukazovať na rovnaký objekt triedy ako atribút triedy, ktorej kópiu sme vytvorili. Na tzv. hĺbkové kopírovanie objektu triedy vrátane vytvorenia kópie atribútov, ktoré sú inštanciami iných tried, používame doplnkovú funkciu deepcopy().
Prepisovanie operátorov
Programovací jazyk Python má rovnako ako mnohé iné jazyky schopnosť tzv. prepisovania operátorov (operators overloading). Ide o jednoduchý princíp, pri ktorom jeden a ten istý operátor vykonáva svoju úlohu rozdielne pre dva rozdielne argumenty.
Prepisovanie operátorov v rámci tried realizujeme pomocou špeciálnych metód, ako napr.:
__add__
operátor sčítania +
__mul__ operátor násobenia *
(v prípade dvoch vektorov)
__rmul__ operátor násobenia *
(násobenie vektora číslom)
__eq_, __le__, __ge__, __gt__, __lt__, __ne__ operátory ==, <=, >=, >, <, !=
Polymorfizmus a dedičnosť tried
Polymorfická funkcia je taká funkcia, ktorá dokáže spracovať argumenty rôznych typov. Na rozhodnutie, či daná funkcia je aplikovateľná na konkrétny typ argumentov, Python uplatňuje tzv. pravidlo kačice (duck typing rule). Ide o veľmi jednoduché pravidlo, ktoré v počítačovej terminológii znie: „Funkcia môže byť pre daný typ argumentov aplikovaná práve vtedy, ak sú všetky operácie, ktoré sú jej obsahom, s daným typom vykonateľné.“
Dedičnosť
Pod pojmom dedičnosť (inheritance) tried rozumieme definíciu novej triedy, ktorá je odvodenou verziou existujúcej triedy. Hlavným cieľom dedičnosti je rozširovanie funkcionalít existujúcich tried bez ich zmeny. Existujúce triedy sa zvyknú nazývať rodičmi (parents) a triedy, ktoré od nich dedia atribúty a metódy, tzv. potomkami = deťmi (children).
class Student(Osoba):
""" Práca so študentom """
def __init__(self,meno,priezvisko,
rok_narodenia,rocnik):
Osoba.__init__(self,meno,
priezvisko,rok_narodenia)
self.rocnik = rocnik
def getrocnik(self):
return (self.rocnik)
Ak v potomkovi triedy definujeme metódu __init__(), prepíšeme túto inicializačnú metódu v jej rodičovi. Ak chceme zachovať volanie tejto metódy aj z rodičovskej triedy, musíme ju explicitne uviesť v rámci __init__() potomka. V prípade, ak chceme, aby potomok zdedil všetky atribúty a metódy rodičovskej triedy, používame na to funkciu super().
Komplexný príklad obsahujúci opísanú dedičnosť tried je nasledujúci:
class Osoba:
""" Práca s osobou """
def __init__(self,meno,priezvisko,
rok_narodenia):
self.meno = meno
self.priezvisko = priezvisko
self.rok_narodenia = rok_narodenia
def getmeno(self,co):
if (co == "meno"):
return (self.meno)
elif (co == "priezvisko"):
return (self.priezvisko)
else:
return (self.meno,self.priezvisko)
def getrok_narodenia(self):
return (self.rok_narodenia)
class Student(Osoba):
""" Práca so študentom """
def __init__(self,meno,priezvisko,
rok_narodenia,rocnik):
Osoba.__init__(self,meno,
priezvisko,rok_narodenia)
self.rocnik = rocnik
def getrocnik(self):
return (self.rocnik)
studenti = [Student("Ján","Prvý",1980,1),
Student("Peter","Druhý",1979,2),
Student("Erik","Tretí",1978,3)]
print("Počet študentov:",len(studenti))
print("---------------------------------------------")
for student in studenti:
print("{0} {1}, roka narodenia: {2},
ročník: {3}.".format(
student.getmeno("meno"),
student.getmeno("priezvisko"),
student.getrok_narodenia(),
student.getrocnik()))
Zobrazit Galériu