Výjimky a chyby
Chyby a výjimky
K chybě programu může dojít ze dvou důvodů. Prvním z nich je chyba syntaxe a druhým výjimka (anglicky exception).
Chyby syntaxe
Chyby syntaxe můžou být spůsobeny například špatným odsazením v bloku, zapomenutím dvojtečky na konci příkazů jako je for a zmnoha dalších důvodů. Na chybu syntaxe vás Python upozorní nějak takto (neříkejte, že se Vám to ještě nestalo :-):
File "<stdin>", line 1
print('hello world')
^
Dozvíte se, v jakém souboru na jaké řádkce je chyba syntaxe a řádka s chybou je vypsána. (stdin je „soubor“ standardní vstup – anglicky standard input). O chybách syntaxe jsem už psal v kaptiole Chyby.
Výjimky
Pokud dojde k chybě během interpretace kódu, vyvolá se tzv. výjimka
(anglicky Exception). Existuje mnoho druhů výjimek. Výjimky jsou instance
tříd, které dědí od třídy BaseException
.
Python má mnoho předdefinovaných výjimek, které vyvolává při různých příležitostech.
Například při dělení nulou se vyvolá výjimka ZeroDivsionError
,
pokud se pokusíte přistoupit k prvku seznamu přes index, který přesahuje
rozsah seznamu, dojde k výjimce IndexError
.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> seznam = [0,1,2]
>>> seznam[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
Všechny výjimky si můžete vypsat příkazem dir() nebo lépe, podívat se do dokumentace, kde i uvidíte jak se výjimky dědí jedna od druhé.
V Pythonu 2.7 najdete Všechny Pythonem definované výjimky v modulu exceptions,
zatímco v Pythonu 3.0 už jen v jmenném prostoru __builtins__
.
>>> import exceptions
>>> dir(exceptions)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError',
'EnvironmentError', 'Exception', 'FloatingPointError', 'FutureWarning',
'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning',
'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
'LookupError', 'MemoryError', 'NameError', 'NotImplementedError',
'OSError', 'OverflowError', 'PendingDeprecationWarning',
'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError',
'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError',
'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError',
'Warning', 'ZeroDivisionError', '__doc__', '__name__', '__package__']
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
'MemoryError', 'NameError', 'None', 'NotADirectoryError',
'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning',
'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError',
'_', ... ]
Odchytávání výjimek
Pokud dojde k chybě syntaxe nebo výjimce, provádění programu skončí
chybovým hlášením. Vyvolané výjimky však lze „odchytit“.
Používá se k tomu následující konstrukce:
blok, kde může dojít k výjimce
...
except:
blok, který se provede, když dojde k výjimce
...
else:
blok, který se provede, když nedojde k výjimce
...
Nejdříve se provádějí příkazy v bloku za try:
, pokud
dojde k jakékoliv výjimce, další příkazy se v bloku try:
již
neprovedou a začnou se provádět příkazy v bloku za except:
.
Pokud nedojde k žádné výjimce, provedou se příkazy v bloku za
else:
. Blok else
je nepovinný (nemusí se uvádět).
print(3 + 3)
print('x' + 3)
print(2 + 2)
except:
'Doslo k chybe'
else:
'Nedoslo k chybe'
6
'Doslo k chybe'
Pokud si myslíte, že může dojít k výjimce během provádění bloku za
except:
nebo else:
, můžete uvnitř těchto bloků použít
další, vnořený, příkaz try:
(a tak dál, až do zblbnutí …).
Pokud dojde k výjimce uvnitř funkce a není tam zachycena pomocí try
,
je zachycena pomocí bloku try
, ve kterém byla funkce volána.
Pokud takový blok neexistuje, pak je výjimka předána v zásobníku volání výše,
tj. k funkci ve které byla funkce volána a tam může být znovu zachycena
blokem try
. A tak to pokračuje, dokud není výjimka někde
zachycena, nebo program skončí chybovým hlášením.
Vyvolání výjimky
Výjimku je možno uměle vyvolat příkazem
raise nazev_tridy_vyjimky, argumenty výjimky
,
nebo raise nazev_tridy_vyjimky(argumenty výjimky)
.
(Argumenty výjimky nejsou vždy povinné)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: Ahoj!
>>> raise NameError('Ahoj!') # volani po novu
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: Ahoj!
>>> raise NameError # volani bez argumentu
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError
Odchytávání konkrétních výjimek
Návěstí except:
může mít jako argument
jméno výjimky, kterou má zachytit. Takových návěstí může být více.
Provede se první návěstí, které odpovídá vzniklé výjimce
(kvůli vytváření výjímek děděním se může stát, že jedna výjimka
vyhovuje více excpet
návěstím. Každá výjimka vyhovuje návěstí
se stejnou třídou, ale i třídám všech svých předků!).
Pokud nastala výjimka, pro které neexistuje
návěstí s jejím jménem, provede se blok za except:
bez
argumentu (které musí být vždy na konci try
bloku).
Pokud takové návěstí neuvedete, vyjímka není blokem try
zachycena
a zachytí se buď nadřazeným blokem try
, nebo skončí program
s chybovým hlášením.
raise NameError
except IOError:
print('Vyjimka vstupu/vystupu')
except NameError:
print('Nexistujici jmeno objektu')
except:
print('Vyjimka, netusim jaka')
else:
print('Alles ist gut')
Nexistujici jmeno objektu
Návěstí except:
může odchytávat i více výjimek najednou.
Více výjimek se píše za except
do závorky, oddělené od sebe čárkou.
except (Vyjimka1, Vyjimka2 , Vyjimka3):
...
Aby toho nebylo dost, může návěstí except
odchytávat argumenty výjimky do proměnných. V příkladu
níže se odchytává číslo chyby a textový popis výjimky IOError
do proměnných errno a stderrror.
Následující způsob ukazuje, jak se to provádí v Pythonu verze 2.7 a starším.
try:
f = open('myfile.txt')
s = f.readline()
i = int(string.strip(s))
except IOError, (errno, strerror):
print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise # znovu vyvolá výjimku zachycenou "except:" návěstím
K příkazu import
se dostaneme v kapitole o modulech.
V Pythonu 3.0 a novějším se k argumentům výjimky dostanete přes
instanci zachycené výjimky, kterou získáte pomocí klíčového slova
as
.
try:
f = open('myfile.txt')
s = f.readline()
i = int(string.strip(s))
except IOError as inst:
print("I/O error({}): {}".format(inst.args[0],inst.args[1]))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise # znovu vyvolá výjimku zachycenou "except:" návěstím
Návěstí finally
V části o Odchytávání výjimek jsem vám zatajil
důležité návěstí finally
. To se provede
vždy, ať už k výjimce dojde, nebo ne.
"""Pomocná funkce pro vyvolání výjimky.
Zavolání vyvolejVýjimku(Vyjimka) vlastně provede totéž co
raise Vyjimka, takže se zdá být tato funkce úplně zbytečná.
Ale ukáže nám, jak se výjímka nezachycená v této funkci
"propaguje" do místa jejího volání, kde bude zachycena pomocí
try - except konstrukce."""
if vyjimka == None:
return
if not issubclass(vyjimka, BaseException):
raise TypeError
raise vyjimka
print("Začínáme")
vyvolejVyjimku(IndexError)
print("Toto se už neprovede")
except IndexError:
print("Zachycen IndexError")
finally:
print("Provede se vždy")
Začínáme
Zachycen IndexError
Provede se vždy
print("Začínáme")
vyvolejVyjimku(None)
print("Výjimka nevyvolána")
except IndexError:
print("Zachycen IndexError")
finally:
print("Provede se vždy")
Začínáme
Výjimka nevyvolána
Provede se vždy
print("Začínáme")
vyvolejVyjimku(0)
print("Výjimka nevyvolána")
except IndexError:
print("Zachycen IndexError")
finally:
print("Provede se vždy")
Začínáme
Provede se vždy
Traceback (most recent call last):
File "<pyshell#31>", line 3, in <module>
vyvolejVyjimku(0)
File "<pyshell#25>", line 11, in vyvolejVyjimku
if not issubclass(vyjimka, BaseException):
TypeError: issubclass() arg 1 must be a class
V následujícím příkladu si všiměte, že se finally
provede i v případě, že dojde k výjimce uvnitř nějakého
bloku except
. Pokud by však došlo k výjimce
uvnitř bloku finally
, zbylé příkazy (za
místem kde došlo k výjimce) se už neprovedou.
print("Začínáme")
vyvolejVyjimku(IndexError)
print("Toto se už neprovede")
except IndexError:
print("Zachycen IndexError")
raise # znovu vyvolám zachycenou výjimku
finally:
print("Provede se vždy")
Začínáme
Zachycen IndexError
Provede se vždy
Traceback (most recent call last):
File "<pyshell#33>", line 3, in <module>
vyvolejVyjimku(IndexError)
File "<pyshell#25>", line 13, in vyvolejVyjimku
raise vyjimka
IndexError
Finally se typicky používá pro úklidové práce. Zavření
descriptorů soborů, připojení k databázi atd. Kdykoliv
získáváte nějaký „zdroj“, například to připojení k databázi,
měli byste jej vracet (uzavírat) v bloku finally
, aby
jste měli jistotu, že k jeho vrácení dojde i v případě chyby (jinak by si mohl
program rychle všechny zdroje vyčerpat).
Předdefinované Clean-up akce
Protože se tvůrcům Pythonu zdálo finally
příliš ukecané,
vytvořili příkaz with
.
Ten pracuje s objekty, které mají definovány magické metody
__enter__
a __exit__
.
Například standardní funkce open()
vrací takový objekt.
Ten v metodě __enter__
získá od OS zdroj pro přístup k práci se souborem
a v metodě __exit__
tento zdroj zase uvolní.
Použít se dá takto:
for line in f:
print(line, end="")
To co vrací mtoda __enter__
se přiřadí do proměnné za klíčovým
slovem as
a to co je v __exit__
se provede
jako by bylo ve finally (tj. ať už dojde k chybě nebo ne).
Pokud vás to zaujalo, můžete si přečíst podrobnosti na stránce Understanding Python's "with" statement.
Vytváření vlastních výjimek
Můžete si vytvořit vlastní výjimky, pokud by vám ty definované Pythonem nestačili.
V případě, že už nějakou vlastní sadu výjimek vytváříte, doporučuje se, aby dědila od nějakého společného předka. Ideálně každý váš modul bude mít jednu rodičovskou třídu pro všechny výjimky definované modulem.
V Pythonu verze 2.7 a starším mohla být výjimka jakákoliv třída.
V Pythonu 3.0 musí být každá výjimka potomkem třídy
BaseException
, ale pro uživatelem vytvářené třídy
se doporučuje dědit od Exception
.
Následující příklad funguje v Pythonu 2.7.
def __init__(self, cislo = 0, text=""):
self.cislo = cislo
self.text = text
def __str__(self):
return str(self.__class__) + "(" + str(self.cislo) + "): " + self.text
>>> try:
raise MojeVyjimka(1, "Nejaka chyba")
except MojeVyjimka as vyjimka:
print(vyjimka)
__main__.MojeVyjimka(1): Nejaka chyba
Pro zprovoznění předchozího příkladu v Pythonu 3.0 stačí na první řádce dědit od
Exception
:
class MojeVyjimka(Exception):
…
if – else
a podobné řídící konstrukce, ne výjimky.
Jednak jsou výjimky „drahé“ (interpret Pythonu si musí pamatovat
všechny vnořené try bloky pro všechny vnořené volání funkce atp.), ale
hlavně by to mohlo šíleně znepřehlednit zdrojový kód.