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 :-):

>>>  print('hello world')
  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.

>>> 3/0
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__.

# Python 2.7
>>> 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__']
# Python 2.7 i 3.3
>>> 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:

try:
    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).

>>> try:
        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é)

První způsob volání výjimky, tj. s předáváním argumentů za čárkou za jménem třídy výjimky, je dostupný jen v Pythonu verze 2.7 a starších. V Pythonu 3.0 a novějších dojde k syntaktické chybě.
>>> raise NameError, 'Ahoj!'     # volani vyjimky po staru
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.

>>> try:
        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.

import string, sys

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.

import string, sys

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.

def vyvolejVyjimku(vyjimka):
    """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
>>> try:
        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
>>> try:
        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
>>> try:
        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.

>>> try:
        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:

with open("myfile.txt") as f:
    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.

>>> class MojeVyjimka:
        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):

Nepoužívejte výjimky pro řízení chodu programu. K tomu slouží 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.
Komentář Hlášení chyby
Created: 11.9.2005
Last updated: 31.8.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..