<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Výjimky, aserce</title>
  <date>2011-03-24|2013-03-26</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->

<!--
  <p>
    Výjimky můžete:
  </p>
  <ul>
    <li>
      definovat vlastní (jako potomky třídy <code>Exception</code>)
    </li>
    <li>
      vyvolat uměle (nebo přeposlat dál) pomocí příkazu <code>raise VÝJIMKA</code>
    </li>
  </ul>
  <p>
    Některé objekty mají předdefinované vlastní „uklízecí“ akce (tj. typicky akce ve větvi <code>finally</code>), které se volají automaticky při použití konstrukce <code>with</code>:
  </p>
  <pre>
with open('soubor') as f:
    obsah = f.read()  </pre>
  <div class="notes">
    PS: Přidat dneska i <i>assert len(vector) == 3</i> a podobné ?
  </div>
  <div class="handout">
    Podrobněji: <i>with</i> na začátku bloku vždy zavolá metodu <i>file.__enter__</i> objektu a na konci bloku metodu <i>file.__exit__</i> .
  </div>
-->

<slide title="Úvod">

  <!--p>
    Zatímco v některých jazycích je typickým způsobem reportování chyb zahrnuto do návratových hodnot funkcí, Python patří k jazykům, kde je na to vyhrazena mašinérie <em>výjimek</em>.
  </p-->
  <p>
    Python patří k jazykům, kde je na reportování (a obsluhu) chyb, ke kterým může v kódu na nejrůznějších místech dojít, vyhrazena mašinérie <em>výjimek</em>. Pár typických příkladů (přímo podle dokumentace):
  </p>
  <example lang="python">
>>> 10 * (1/0)
Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
ZeroDivisionError: int division or modulo by zero

>>> 4 + spam*3
Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
NameError: name 'spam' is not defined

>>> '2' + 2
Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
TypeError: Can't convert 'int' object to str implicitly
  </example>
  
  <p>
    Výjimky standardně „probublávají“ postupně po hierarchii programu, ale každopádně na konci trasovacího výpisu (<em>stack traceback</em>) bude, kdo a jakou výjimku vyvolal.
  </p>

</slide>
<slide title="Ošetření výjimek">

  <p>
    Výjimky je možné ošetřovat několika různými způsoby. Ukažme si základní z nich:
  </p>

  <p class="enumerate">
    Základní použití – odchyť jednotlivé typy výjimek, ke kterým může dojít v <em>BLOKu-1</em>:
  </p>
  <example lang="python">
try:
  BLOK-1
except VÝJIMKA-1:
  BLOK-2
except VÝJIMKA-2:
  BLOK-3
  </example>

  <p class="enumerate">
    Odchycení výjimky a předání jejího objektu pod vybraným jménem:
  </p>
  <example lang="python">
try:
  BLOK-1
except VÝJIMKA as ex:
  BLOK-2   # zde je nyní možné použít objekt odchycené výjimky pod jménem 'ex'
  </example>

  <p class="enumerate">
    Nevyvolá-li kód v <em>BLOKu-1</em> žádnou výjimku, provede se kód v <em>BLOKu-3</em>:
  </p>
  <example lang="python">
try:
  BLOK-1
except:
  BLOK-2
else:
  BLOK-3
  </example>

  <p class="enumerate">
    Kód v <em>BLOKu-3</em> bude vykonán vždy při opouštění větve <em>try</em>, ať už vyvolala výjimku nebo ne:
  </p>
  <example lang="python">
try:
  BLOK-1
except:
  BLOK-2
finally:
  BLOK-3
  </example>
  <note>
    Větve <em>finally</em> se tak používají často pro „vyčišťovací“ kód, typicky pro uzavření otevřených zdrojů apod.
  </note>

</slide>
<slide title="Příklad">

  <p>
    Následující příklad ukazuje základní použití výjimek:
  </p>
  <example lang="python" src="_files/try_except.py" />
  <note>
    Upraveno podle <a href="http://www.swaroopch.com/notes/Python_en:Exceptions">http://www.swaroopch.com/notes/Python_en:Exceptions</a>
  </note>

  <ul>
    <li>
        Zadáme-li nějaký text a poté odentrujeme, obdržíme následující výsledek:
    </li>
  </ul>
  <example lang="text">
Enter something --> ahoj
You entered "ahoj".
Completed.
  </example>

  <ul>
    <li>
        Přerušíme-li zadávání textu pomocí ^C, výstup se změní:
    </li>
  </ul>
  <example lang="text">
Enter something --> ahoj^C
You cancelled the operation.
Completed.
  </example>

  <ul>
    <li>
        Konečně zadáme-li místo vstupního textu rovnou EOF, uplatní se ještě jiná větev:
    </li>
  </ul>
  <example lang="text">
Enter something --> 
Why did you do an EOF on me?
Completed.
  </example>
  <handout>
    Na Win10 už nejde zreplikovat. Ani v Ubuntu pod WSX. Ještě zkusit čistý Linux.
  </handout>
  
  <p>
    Souhrnně tedy platí, že větev <em>finally</em> se provede vždy při opouštění větve <em>try</em>, zatímco větev <em>else</em> se provede pouze tehdy, nedojde-li ve větvi <em>try</em> k vyvolání výjimky.
  </p>

</slide>
<slide title="Více výjimek v jedné větvi">

  <p>
    Je též možné odchytit více výjimek najednou v podobě n-tice:
  </p>
  <example lang="python">
try:
  BLOK-1
except (VÝJIMKA-1, VÝJIMKA-2, ...):
  BLOK-2
  </example>
  <note>
    Tohle je v ostrém kontrastu s chováním v Python'u 2.x, kde uvedená konstrukce sloužila k zachycení výjimky pod jistým jménem (čili k tomu, co teď mnohem průhledněji a srozumitelněji dělá operátor <code>as</code>).
  </note>
  <p>
    Samozřejmě zachycení více různých výjimek jedním společným „řešícím“ (problémy) kódem je už speciální aplikace, častěji bude každý typ výjimky obsluhovat tomu určený kód.
  </p>

</slide>
<slide title="„Univerzální“ zachytávání výjimek">

  <p>
    Ve skutečnosti nemusíte typ zachycené výjimky ani předepisovat:
  </p>
  <example lang="python">
try:
  BLOK-1
except:
  BLOK-2
  </example>
  <p>
    Uvedený kód má ale dvě (navzájem spřažené) nevýhody:
  </p>
  <ul>
    <li>
        Dojde-li v <em>BLOKu-1</em> k <strong>jakékoli</strong> výjimce, bude zpracování předáno do <em>BLOKu-2</em>, který si s tím bude muset poradit (tj. rozhodnout se, co vlastně zhavarovalo, a podle toho se zařídit).
        <note>
            Pokud se tak někdy stane, použijte <code>sys.exc_info()</code>, které vrací trojci <code>(TypVýjimky, InstanceVýjimky, Traceback)</code>.
        </note>
    </li>
    <li>
        Kód se značně znečitelní, protože místo sice „armády“, ale jasně pojmenovaných výjimek, najednou budeme mít pouze jediný „opravný“ blok, který bude muset obsahovat sofistikovanou logiku rozpoznávání chyb.
    </li>
  </ul>
  <p>
    Samozřejmě pokud píšete rychlý skript sami pro sebe a potřebujete pouze chybu rozpoznat, je tohle použití zachytávání výjimek nejrychlejší i relativně rozumné. Pokud však budete psát kód <em>produkční</em>, tak do něj nic takového nepatří!
  </p>

</slide>
<slide title="Vyvolání výjimky">

  <p>
    Ač to může na první pohled vypadat nečekaně, poměrně často nebudete chtít ponechat vyvolávání výjimek pouze na vlastním virtuálním počítači, ale budete chtít na základě nějakých událostí vyvolávat výjimky sami (často dokonce i výjimky vlastní a ne pouze předdefinované). K tomu slouží příkaz <code>raise</code>, jehož použití je celkem standardní (přímo podle dokumentace):
  </p>

  <p class="enumerate">
    Konkrétní výjimku můžete uměle vyvolat:
  </p>
  <example lang="python">
>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
NameError: HiThere
  </example>

  <p class="enumerate">
    Výjimku můžete po jejím odchycení a částečném zpracování předat po hierarchii dále:
  </p>
  <example lang="python">
>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
An exception flew by!
Traceback (most recent call last):
  File "&lt;pyshell#23>", line 2, in &lt;module>
    raise NameError('HiThere')
NameError: HiThere
  </example>
  <note>
    PS: V praxi se ukázalo, že odchytávání výjimek v metodách vlastních objektů a předávání jich dále se bohužel nechová vždy zcela očekávaným způsobem…
  </note>

</slide>
<slide title="Objekt výjimky">

  <p>
    Jelikož odchycená výjimka je objekt, má jistou vnitřní strukturu (ať už výchozí nebo uživatelem předefinovanou). Ukažme si několik příkladů:
  </p>

  <p class="enumerate">
    Standardní výjimky mají nadefinované chování při pokusu o jejich výpis:
  </p>
  <example lang="python">
>>> try:
...     raise NameError('HiThere')
... except NameError as ex:
...     print(ex)
HiThere
  </example>
  <note>
    To zajišťuje přítomnost „magické“ metody <em>__str__()</em>.
  </note>

  <p class="enumerate">
    Struktura objektu výjimek je ale mnohem složitější:
  </p>
  <example lang="python">
>>> try:
...     raise NameError('HiThere')
... except NameError as ex:
...     print( dir(ex) )
['__cause__', '__class__', '__context__', '__delattr__', '__dict__', '__doc__',
 '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
 '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__',
 '__str__', '__subclasshook__', '__traceback__', 'args', 'with_traceback']
  </example>
  <p>
    Především vidíme, že (standardní) výjimky mohou dostávat argumenty:
  </p>
  <example lang="python">
>>> try:
...    raise Exception('spam', 'eggs')
... except Exception as inst:
...    print(type(inst))    # instance výjimky
...    print(inst.args)     # argumenty výjimky jsou uloženy v parametru '.args'
...    print(inst)          # díky '__str__' můžeme argumenty vytisknout přímo
...    x, y = inst.args     # n-ticové rozbalení argumentů
...    print('x =', x)
...    print('y =', y)
&lt;class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
  </example>

  <!--p class="enumerate">
    Někdy se nám může hodit vynutit si výpis trasovacího záznamu nebo alespoň typ vyvolané výjimky:
  </p>
  <example lang="python">
XXX
  </example-->

</slide>
<slide title="Hierarchie standardních výjimek">

  <note>
    Viz <a href="http://docs.python.org/py3k/library/exceptions.html">http://docs.python.org/py3k/library/exceptions.html</a>
  </note>
  <example src="_history/Python310" lang="txt" />
  
  <p>
    PS: Struktura standardních výjimek se čas od času mění (výše uvedená je pro verzi 3.10). Například Python 3.2 postrádal celou větev <em>OSError</em> a měl místo ní k dispozici naopak následující větev <em>EnvironmentError</em>:
  </p>
  <pre>      +-- EnvironmentError
           +-- IOError
           +-- OSError
                +-- WindowsError (Windows)
                +-- VMSError (VMS)
  </pre>

</slide>
<slide title="Pozor na hierarchii! I">

    <p>
        Spousta výjimek je zjevně vyrobena jako potomci jiných výjimek, například:
    </p>
    <example lang="python">
        >>> issubclass(ZeroDivisionError, ArithmeticError)
        True
        >>> issubclass(ArithmeticError, ZeroDivisionError)
        False
    </example>
    <p>
        Vypadá to zdánlivě nevinně, ale jen do té chvíle, než budete v rámci jednoho <em>try</em>-bloku zachytávat výjimky, které jsou na sobě závislé – rodičovská totiž zvítězí, pokud bude uvedena dříve:
    </p>
    <example layout="vertical">
        <program src="_files/hierarchy.py" lang="python"/>
        <out src="_files/hierarchy.out" lang="text"/>
    </example>

</slide>
<slide title="Pozor na hierarchii! II">

    <p>
        V čem je problém? Kdyby náhodou každá z výjimek definovala jinou sadu atributů – pak se vám totiž může stát, že budete chtít pracovat s něčím, co vůbec nedostanete. Standardní výjimky se takhle naštěstí nechovají – potomci funkčnost rodičů jen rozšiřují<sup>*</sup>:
    </p>
    <example lang="python">
>>> from urllib.error import HTTPError, URLError

>>> issubclass(HTTPError, URLError)
True
>>> issubclass(URLError, HTTPError)
False

>>> hs, us = set(dir(HTTPError)), set(dir(URLError))
>>> hs - us
{'headers', '__enter__', 'close', '__iter__', '__getattr__', '__exit__',
'getcode', 'reason', 'geturl', '_HTTPError__super_init', 'info'}
>>> us - hs
set()
    </example>
    <handout>
>>> sorted(hs - us)
['_HTTPError__super_init', '__enter__', '__exit__', '__getattr__',
'__iter__', 'close', 'getcode', 'geturl', 'headers', 'info', 'reason']
    </handout>
    <note>
        <sup>*</sup> Aspoň doufám ^_^'
    </note>

</slide>
<slide title="Další standardní výjimky">

    <p>
        Kromě výjimek ve standardní hierarchii je další hromada výjimek poschovávaná u svých knihovních modulů, které musíte nejdříve naimportovat, abyste je mohli používat. Jedním příkladem za všechny budižtež právě výjimky modulu <a href="/materialy/python/net/overview.xml"><code>urllib</code></a> z předchozího slajdu:
    </p>
    <example lang="python">
>>> import urllib.error
>>> dir(urllib.error)
['ContentTooShortError', 'HTTPError', 'URLError', '__all__',
 '__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
 '__name__', '__package__', '__spec__', 'urllib']
    </example>
    <p>
        PS: Jich se upozornění na hierarchii <a href="/materialy/python/net/overview.xml?slajd=7">týkalo v historii také</a> – zde je rodičem (a tedy obecnější výjimkou) <em>URLError</em> a potomkem (specifičtější výjimkou) s jinými atributy <em>HTTPError</em>.
    </p>

</slide>
<slide title="Vlastní výjimky">

  <p>
    Samozřejmě kromě výše uvedených předdefinovaných výjimek si můžete nadefinovat výjimky vlastní. Typicky to budou potomci třídy <em>Exception</em> a není-li třeba, postačí velmi jednoduché, např.:
  </p>
  <example lang="python">
class MyError(Exception):
    pass
  </example>
  
  <p>
    Ve složitějších případech můžete nadefinovat vlastní hierarchii výjimek a i předefinovat jejich výchozí chování. Konstruktor základní výjimky <code>Exception</code> má totiž zcela generický tvar <code>__init__(self, /, *args, **kwargs)</code>, přičemž <strong>výchozí chování metody <code>__str__()</code> je přepsat na výstup vstupní parametry instance výjimky</strong> (jako n-tici).
  </p>
  <note>
    Ukázka <a href="https://docs.python.org/3.9/tutorial/errors.html#user-defined-exceptions" class="external">přímo z dokumentace</a>:
    <example lang="python">
      class Error(Exception):
          """Base class for exceptions in this module."""
          pass

      class InputError(Error):
          """Exception raised for errors in the input.

          Attributes:
              expression -- input expression in which the error occurred
              message -- explanation of the error
          """

          def __init__(self, expression, message):
              self.expression = expression
              self.message = message

      class TransitionError(Error):
          """Raised when an operation attempts a state transition that's not
          allowed.

          Attributes:
              previous -- state at beginning of transition
              next -- attempted new state
              message -- explanation of why the specific transition is not allowed
          """

          def __init__(self, previous, next, message):
              self.previous = previous
              self.next = next
              self.message = message
    </example>
  </note>

</slide>
<slide title="„assert“">

    <p>
        Kromě výjimek, které slouží především k odchytávání chyb v běžících a již nasazených programech, máme v Python'u k dispozici i standardní ladicí prostředek – příkaz <code>assert</code>. Kromě plného použití v rámci testování nachází uplatnění i při rychlém odchytávání chyb při ladění programu.
    </p>
    <note>
        Při spuštění interpretru s parametrem <code>-O</code> jsou příkazy <em>assert</em> „odstraněny“.
    </note>
    <p>
        Struktura příkazu <em>assert</em> je..
    </p>
    <blockquote lang="python">
        assert VÝRAZ [, ARGUMENT]
    </blockquote>
    <p>
        ..a v podstatě odpovídá následujícímu kódu:
    </p>
    <example lang="python">
        if not VÝRAZ:
            raise AssertionError(ARGUMENT)
    </example>
    <note>
        Ve skutečnosti jeho vykonání řídí ještě obsah proměnné <code>__debug__</code> – při spuštění interpretru s parametrem <code>-O</code> je do ní uloženo <code>False</code> a uvedené asertovací části kódu jsou pak vynechány.
    </note>

</slide>
<slide title="„assert“ – použití">

    <p>
        Použití ilustruje následující příklad:
    </p>
    <example lang="python">
>>> xs = [1, 2, 3]

# A) Tohle je pravda, není co řešit:
>>> assert len(xs) == 3

# B1) Tohle pravda ale není:
>>> assert len(xs) == 2
Traceback (most recent call last):
  File "&lt;pyshell#31>", line 2, in &lt;module>
    assert len(xs) == 2
AssertionError

# B2) Tohle také není pravda. Navíc přidáme komentář:
>>> assert len(xs) == 2, 'Špatný počet argumentů!'
Traceback (most recent call last):
  File "&lt;pyshell#32>", line 2, in &lt;module>
    assert len(xs) == 2, 'Špatný počet argumentů!'
AssertionError: Špatný počet argumentů!

# B2) Nebo ještě víc údajů:
>>> assert len(xs) == 2, ('Špatný počet argumentů!', len(xs) )
Traceback (most recent call last):
  File "&lt;pyshell#33>", line 2, in &lt;module>
    assert len(xs) == 2, ('Špatný počet argumentů!', len(xs) )
AssertionError: ('Špatný počet argumentů!', 3)
    </example>

</slide>
<slide title="„assert“ – příklad">

    <p>
        Příklad – kontrola vstupních parametrů:
    </p>
    <example layout="horizontal">
        <program src="_files/debug_assert.py" lang="python"/>
        <out src="_files/debug_assert.out" lang="text"/>
    </example>

</slide>
<slide title="„assert“ jako hlídač typů">

    <p>
        Obecně jsou funkce v Python'u psány tak, aby příslušný kód vykonaly pro jakýkoliv vstup, pro který dává smysl (aka <em>„duck-typing“</em>). Ale někdy můžete potřebovat vstupní typy omezit na úzkou podmnožinu. Ve staticky typovaném jazyce by tato kontrola byla automatická, ale v Python'u ji musíme nějak dopsat. S výhodou k tomu můžeme využít právě příkaz <code>assert</code>.
    </p>
    <note>
        Samozřejmě před pádem programu ve chvíli, kdy interpretr při vykonávání programu narazí na špatný vstup, nás to nezachrání. Ale usnadní nám to ladění nebo alespoň dá uživateli srozumitelnější formou najevo, kde a proč došlo k chybě.
    </note>
    
    <p>
        Pár příkladů:
    </p>
    <example lang="python">
        # vracet druhou mocninu má smysl pouze číselné typy
        def druhá_mocnina(x):
            assert type(x) == int or type(x) == float or type(x) == complex
            return x*x
    </example>
    <br/>
    <example lang="python">
        # 'fn' musí být volatelný typ (typicky funkce)
        # 'xs' musí být iterovatelný typ (typicky seznam)
        def mymap(fn, xs):
            assert callable(fn) == True     # isinstance(fn, collections.Callable)
            assert isinstance(xs, collections.Iterable)
            return [fn(x) for x in xs]
    </example>
    <br/>
    <example lang="python">
        # vektor ve 3D-prostoru musí mít právě tři souřadnice
        def délka_3D_vektoru(vektor):
            assert len(vektor) == 3
            x, y, z = vektor
            return math.sqrt( x**2 + y**2 + z**2 )
    </example>

</slide>
<slide title="Varování">

    <p>
        Kromě výjimek obsahuje Python i jejich „vyměkčenou“ verzi v podobě <strong>varování</strong>. Ta v základním nastavení nezpůsobí havárii programu, pouze na výstup (typicky chybový) nahlásí příslušnou informaci.
    </p>
    <p>
        Protože jejich obsluha je dosti obsáhlá a z principu z větší části jiná než u výjimek, je jim věnována <a href="warnings.xml">samostatná kapitola</a>.
    </p>

</slide>


</lecture>
