1. diel - Výnimky v Pythone
V tomto Python kurze sa budeme venovať práci so súbormi. Než však môžeme začať zapisovať a čítať, mali by sme vyriešiť, ako ošetriť chybové stavy programu, ktorých pri práci so súbormi bude nastávať mnoho.
V našom programe môže často dôjsť k chybe. Tým nemyslím chybe z dôvodu, že bol program funkčne zle napísaný, takýchto chýb sa sme schopní dobre vyvarovať. Všeobecne ide najmä o chyby, ktoré zapríčinili tzv. vstupno/výstupné operácie. V anglickej literatúre sa hovorí o input/output alebo skrátene o IO. Ide napr. o vstup užívateľa z konzoly, zo súboru, výstup do súboru, na tlačiareň a podobne. V zásade platí, že tu figuruje užívateľ, ktorý nám môže zadať nezmyselný vstup, neexistujúci alebo nevalidný súbor, odpojiť tlačiareň a podobne. My však nenecháme program spadnúť s chybou, naopak budeme zraniteľné miesta v programe ošetrovať a na danú skutočnosť používateľa upozorníme.
Aktívne ošetrenie chýb
Prvú možnosť ošetrenia chýb nazývame ako aktívna. V programe zmapujeme
všetky zraniteľné miesta a ošetríme ich podmienkami. Ako učebnicový
príklad sa spravidla používa delenie nulou. Predstavme si program, ktorý
používa triedu Matematika
, ktorá má metódu
podil()
. Trieda by mohla vyzerať napr. takto:
class Matematika(): def podil(self, a, b): return a / b
Teraz triedu použijeme takýmto spôsobom:
print("Zadejte dělitele a dělence k výpočtu podílu:") a = int(input()) b = int(input()) print(Matematika().podil(a, b))
Ak teraz programu užívateľ zadá čísla 12
a 0
,
program spadne s chybou, pretože nulou nemožno deliť. Aktívne chybu
ošetríme jednoduchou podmienkou v programe:
print("Zadejte dělitele a dělence k výpočtu podílu:") a = int(input()) b = int(input()) if b != 0: print(Matematika().podil(a, b)) else: print("Nulou nelze dělit.")
Teraz si musíme pri každom použití metódy teda strážiť, či do druhého parametra nevkladáme nulu. Predstavte si, že by metóda brala parametrov 10 a používali sme ju v programe niekoľkokrát. Určite by bolo veľmi zložité ošetrovať všetky použitia tejto metódy.
Riešením by mohlo byť vložiť kontrolu priamo do metódy. Máme tu však
nový problém: Akú hodnotu vrátime, keď bude 2. parameter nulový?
Potrebujeme hodnotu, z ktorej spoznáme, že výpočet neprebehol korektne. To
je však problém, keď zvolíme napr. nulu, nepoznáme, či napr. 0/12 je
chybný výpočet alebo nie. Nevieme, či 0
značí výsledok alebo
chybu. Ani zápornými číslami si nepomôžeme. Parsovanie hodnôt je 2.
klasický príklad zraniteľného vstupu od užívateľa. Ďalšie sú
súborové operácie, kde súbor nemusí existovať, nemusíme naň mať práva,
môže s ním byť práve pracované a podobne.
Pasívne ošetrenie chýb
Najmä, keď je operácia zložitejšia a bolo by príliš náročné
ošetrovať všetky možné chybové stavy, nastupujú
výnimky, tzv. pasívne ošetrenie chýb. Nás totiž
vôbec nemusí zaujímať vnútorná logika v metóde, ktorú voláme. Pokúsime
sa nebezpečnú časť kódu spustiť v "chránenom režime". Tento
režim je nepatrne pomalší a líši sa tým, že pokiaľ dôjde k chybe, máme
možnosť ju odchytiť a zabrániť pádu programu. O chybe tu hovoríme ako o
výnimke. Využívame na to tzv. try
-
except
bloky:
try: pass except: pass
Do bloku try
umiestnime nebezpečnú časť
kódu. Pokiaľ nastane v bloku try
chyba, jeho
vykonávanie sa preruší a program prejde do bloku except
. Pokiaľ
všetko prebehne v poriadku, try
sa vykoná celý a
except
sa preskočí. Vyskúšajme si situáciu na našom
predchádzajúcom príklade:
try: print(Matematika().podil(a, b)) except: print("Při dělení nastala chyba.")
Kód je jednoduchší v tom, že nemusíme ošetrovať všetky zraniteľné
miesta a premýšľať, čo všetko by sa mohlo pokaziť. Nebezpečný kód iba
obalíme blokom try
a všetky chyby sa zachytia v
except
. Samozrejme do try
- except
bloku
umiestnime len to nevyhnutné, nie celý program:)
Teraz teda už vieme, ako ošetriť situácie, keď používateľ zadáva nejaký vstup, ktorý by mohol vyvolať chybu. Nemusí ísť len o súborové operácie, výnimky majú veľmi širokú oblasť použitia. Dokážeme náš program napísať tak, aby sa nedal jednoducho používateľom zhodiť.
Použitie výnimiek pri práci so súbormi
Ako už bolo povedané, súborové operácie môžu vyvolať mnoho výnimiek,
preto sa súbory vždy pracujeme v try
-
except
bloku. Existuje aj niekoľko ďalších
konštrukcií, ktoré pri výnimkách môžeme využívať.
Finally
Do try
- except
bloku môžeme pridať ešte 3.
blok a to finally
. Ten sa spustí vždy
nech k výnimke došlo alebo nie. Predstavte si nasledujúcu metódu na
uloženie nastavení:
def ulozNastaveni(): try: f = open("soubor.dat", "w") f.write("Nějaké nastavení...") f.close() # musíme file handler uzavřít except: print("Chyba při zápisu do souboru.")
Metóda sa súbor pokúsi otvoriť a zapísať doň nejaké nastavenia.
Otvorený súbor musíme opäť uzavrieť. Pri chybe vypíše hlášku do
konzoly. Vypisovať chyby priamo v metóde je však škaredé, to napokon už
vieme, metódy a objekty všeobecne by mali vykonávať len logiku a
komunikáciu s užívateľom obstaráva ten, kto ich volá. Vracajme teda
True
/ False
podľa toho, či sa operácia podarila
alebo nie:
def ulozNastaveni(): try: f = open("soubor.dat", "w") f.write("Nějaké nastavení...") return True except: return False f.close()
Na prvý pohľad to vyzerá, že sa súbor vždy uzavrie. Celý kód je však
v nejakej metóde, v ktorej voláme return
. Ako vieme,
return
ukončí metódu a nič za ním sa už nevykoná.
Súbor by tu vždy zostal otvorený a uzavretie by sa už nevykonalo. Ako
následok by to mohlo mať, že by bol potom súbor neprístupný.
Pokiaľ vložíme zatvorenie súboru do bloku finally
,
vykoná sa vždy. Python si pamätá, že blok try
-
except
obsahoval finally
a zavolá
finally
blok aj po opustení bloku except
alebo
try
:
def ulozNastaveni(): try: f = open("soubor.dat", "w") f.write("Nějaké nastavení...") return True except: return False finally: f.close()
Blok except
by bolo najlepšie úplne vynechať a nechať
metódu, aby výnimku pokojne vyvolala. Budeme počítať s tým, že sa s
výnimkou vysporiada ten, kto metódu zavolal, nie metóda sama. Je to tak
lepšie, ušetríme návratovú hodnotu metódy (ktorú je potom možné
použiť pre niečo iné) a kód sa nám zjednoduší:
def ulozNastaveni(): try: f = open("soubor.dat", "w") f.write("Nějaké nastavení...") finally: f.close() }
Súbor sa v kóde vyššie vždy zavrie a to aj keď sa pri zápise niečo nepodarí, možno dôjde miesto na médiu. Metódu by sme teraz volali takto:
try: ulozNastaveni() except: print("Nepodařilo se uložit nastavení.")
Teraz si ukážeme, ako celú situáciu ešte viac zjednodušiť. Použijeme
konštrukciu with
.
With
Python umožňuje značne zjednodušiť prácu s inštanciami tried na
čítanie a zápis do súborov alebo všeobecne tried, ktoré potrebujú
vykonávať akékoľvek upratovacie práce. Vyššie uvedený blok môžeme
zapísať pomocou notácie with
, ktorá
nahrádza bloky try
a finally
.
Obrovskou výhodou je, že blok finally
Python vygeneruje sám a
sám zaistí, aby daná inštancia súbor uzavrela. Metóda
UlozNastaveni()
by teda vyzerala s pomocou with
takto:
def ulozNastaveni(): with open("soubor.dat", "w") as f: f.write(objekt) }
Vidíme, že sa kód extrémne zjednodušil, aj keď robí v podstate to
isté. Pri volaní metódy opäť použijeme try
–
except
blok.
Nezabudnite, že with
nahrádza iba
try
- finally
, nie except
!.
Metódu, v ktorej sa používa with, musíme rovnako volať v try-except
bloku.
Teraz sme dospeli presne tam, kam som chcel. Na všetky manipulácie so
súbormi totiž budeme v nasledujúcich tutoriáloch používať konštrukciu
with
. Kód bude jednoduchší a nikdy sa nám nestane, že by sme
súbor zabudli zavrieť.
K výnimkám sa ešte raz vrátime, ukážeme si, ako odchytávať len niektoré typy výnimiek, ktoré hotové triedy výnimiek môžeme v našich programoch používať a tiež ako vytvoriť výnimku vlastnú. Teraz som však chcel vysvetliť len potrebné minimum pre prácu so súbormi a nie vám zbytočne pliesť hlavu zložitými konštrukciami:)
V nasledujúcej lekcii, Úvod do práce so súbormi v Pythone , sa pozrieme, ako to funguje s právami na zápis do súborov v systéme Windows a vyskúšame si niekoľko prvých súborových operácií.