<?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>Kontext „with“</title>
  <date>2015-03-31</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
-->


<!--
DIP3 11.6 - sys.stdin, sys.stdout, vicenasobny kontext

with:
    http://docs.python.org/3/reference/compound_stmts.html#with
    http://docs.python.org/3/reference/datamodel.html#context-managers
    http://docs.python.org/3/library/stdtypes.html#typecontextmanager
-->


<slide title="Úvod">

    <p>
        Ačkoli původní ideou za zavedením kontextu <em>with</em> byla snaha naučit generátory vstupní a čisticí operace kolem bloku kódu (podívejte se do <a href="https://www.python.org/dev/peps/pep-0343/" class="external">PEP'u 343</a>), my se na něj podíváme nejdříve přesně z opačné strany – jako na nástroj šetřící <em>try-finally</em> u bloků kódu, které potřebují nějaké výstupní „čisticí“ operace.
    </p>

</slide>
<slide title="Práce se soubory I">

    <p>
        Nebyl-li by kontext <em>with</em> k dispozici, vypadala by správná práce se soubory asi nějak takto:
    </p>
    <example lang="python">
        f = open(SOUBOR)
        try:
            BLOK
        finally:
            f.close()
    </example>
    <p>
        Idea je taková, že dojde-li při práci se souborem k nějaké chybě, větev <em>finally</em> zajistí, že již otevřený soubor bude vždy řádně uzavřen.
    </p>
    <note>
        PS: A koneckonců takto se soubory skutečně pracovalo, když ještě <em>with</em> nebylo k dispozici.
    </note>

</slide>
<slide title="Práce se soubory II">

    <p>
        V podání kontextu <em>with</em> vypadá stejný kus kódu takto:
    </p>
    <example lang="python">
        with open(SOUBOR) as f:
            BLOK
    </example>
    <p>
        Za prvé je to podstatně kratší. A za druhé je to nakonec i stejně výmluvné – jakmile si zafixujete, že kontext <em>with</em> se stará o „čištění“ za vás.
    </p>

</slide>
<slide title="Princip">

    <p>
        Samozřejmě automatické zavírání souborů je dosti omezená podmnožina toho, co kontext <em>with</em> umí – ostatně, jak už jsem říkal, byl navržen z poněkud jiných důvodů. Ve skutečnosti obstarává následující dvě funkce:
    </p>
    <ul>
        <li>
            vykonání definovaných operací <strong>před spuštěním kódu v BLOKu</strong>;
        </li>
        <li>
            vykonání definovaných operací <strong>po opuštění kódu v BLOKu</strong>.
        </li>
    </ul>
    <p>
        Mnoho objektů Python'u se uvedeným způsobem chová a je je tedy možno použít uvnitř kontextu <em>with</em>. Zároveň existuje několik způsobů, jak vyrobit vlastní objekty, které budou umožňovat to samé.
    </p>

</slide>
<slide title="Implementace pomocí třídy">

    <p>
        Nejnázornější je asi implementace pomocí třídy – objekt, který má podporovat spolupráci s kontextem <em>with</em>, musí definovat následující dvě metody:
    </p>
    <ul>
        <li>
            <strong><code>__enter__()</code></strong> – operace, které se mají vykonat před spuštěním kódu v BLOKu;
        </li>
        <li>
            <strong><code>__exit__()</code></strong> – operace, které se mají vykonat po opuštění kódu v BLOKu.
        </li>
    </ul>
    <p>
        Přitom platí:
    </p>
    <ul>
        <li>
            Použije-li kontext <em>with</em> přiřazení reference pomocí <code>as</code>, je tato reference návratovou hodnotou z metody <code>__enter__()</code>.
        </li>
        <li>
            Dojde-li při vykonávání BLOKu k výjimce, je tato předána do metody <code>__exit__()</code> jako trojce <em>typ, hodnota, traceback</em>; v opačném případě metoda obdrží trojci <code>None, None, None</code>.
        </li>
        <li>
            Byl-li BLOK opuštěn na výjimku a <code>__exit__()</code> vrátí <code>False</code>, je tato výjimka předána výše; pokud ovšem ve stejném případě <code>__exit__()</code> vrátí <code>True</code>, je výjimka potlačena.
        </li>
    </ul>

</slide>
<slide title="Příklad">

    <example layout="horizontal">
        <program src="_files/with_class.py" lang="python"/>
        <out src="_files/with_class.out" lang="text"/>
    </example>
    <note>
        Inspirace – Tarek Ziadé: „Expert Python Programming“
    </note>

</slide>
<slide title="Příklad pro „as“">

    <p>
        Z metody <code>__enter__()</code> můžete vrátit skutečně cokoli, tak jen pro ukázku:
    </p>
    <example layout="horizontal">
        <program src="_files/withas_class.py" lang="python"/>
        <out src="_files/withas_class.out" lang="text"/>
    </example>

</slide>
<slide title="Implementace pomocí generátoru – „@contextmanager“">

    <p>
        Vzhledem k tomu, že pro spolupráci s kontextem <em>with</em> jsou potřeba <em>dva oddělené kusy kódu</em>, nepřekvapí, že je možné proměnit generátor s jedním <em>yield</em>'em na odpovídající třídu – kód před <code>yield</code> je promítnut do části <code>__enter__()</code>, kód po něm pak do <code>__exit__()</code>.
    </p>
    <p>
        Na uvedený překlad je pochopitelně k dispozici standardní dekorátor <strong><code>@contextmanager</code></strong>, jenž při nejjednodušším použití vypadá takto:
    </p>
    <example lang="python">
        from contextlib import contextmanager
        
        @contextmanager
        def context():
            ENTER
            yield
            EXIT
    </example>

</slide>
<slide title="Příklad">

    <p>
        Poněkud komplikovanější použití kopírující chování předchozí třídní varianty:
    </p>
    <example layout="vertical">
        <program src="_files/with_yield.py" lang="python"/>
        <out src="_files/with_yield.out" lang="text"/>
    </example>
    <note>
        Inspirace – Tarek Ziadé: „Expert Python Programming“
    </note>

</slide>
<slide title="Poznámka – práce se soubory">

    <p>
        Když nahlédneme do příslušného PEP'u, odpovídá práce se soubory v podstatě následujícímu kódu:
    </p>
    <example lang="python">
        from contextlib import contextmanager
        
        @contextmanager
        def opened(filename, mode="r"):
            f = open(filename, mode)
            try:
                yield f
            finally:
                f.close()
        
        with opened("/etc/passwd") as f:
            for line in f:
                print line.rstrip()
    </example>
    <p>
        Tedy v rámci <em>__enter__()</em> je soubor otevřen a reference na něj je předána do kontextu a po skončení práce s ním je v rámci <em>__exit__()</em> soubor uzavřen.
    </p>

</slide>
<slide title="Třída vs generátor">

    <p>
        Přestože pomocí generátoru i třídy lze kontext <em>with</em> připravit téměř úplně stejný, existují drobné rozdíly, které vám v konkrétním případě mohou upřednostnit jen jeden z nich:
    </p>
    <ul>
        <li>
            generátor je kratší na zápis, ale ne vždy tak průhledný na pochopení;
        </li>
        <li>
            generátor automaticky drží kontext, což je ve třídě těžší napsat pro cokoliv kromě triviálních případů (kterých je na druhou stranu ale hodně);
        </li>
        <li>
            práce s výjimkami v generátoru není tak průhledná jako ve třídě.
        </li>
    </ul>

</slide>
<slide title="Poznámka – modul „contextlib“">

    <p>
        Knihovna <em>contextlib</em> obsahuje mnohem více zajímavých (a složitých) pomocných metod – mimo jiné i kontexty, které je možno navštívit vícekrát než jednou v rámci stejného běhu.
    </p>
    <p>
        Spousta běžných kontextů je ale „jednoběhová“ – chcete-li je použít znovu, musíte je znovu zavést (proto se také kontexty zpravidla vytváří přímo uvnitř výrazu <code>with</code>). Příkladem jsou třebas soubory, které se po použití v kontextu uzavřou, což si můžeme nasimulovat třebas takto:
    </p>
    <example lang="python">
        >>> from contextlib import contextmanager
        ... 
        ... @contextmanager
        ... def můj_kontext():
        ...     print('Spouštím kontext…')
        ...     yield
        ...     print('Opouštím kontext…')
        ... 
        ... mk = můj_kontext()
        
        >>> with mk:
        ...     print('1.')
        Spouštím kontext…
        1.
        Opouštím kontext…
        
        >>> with mk:
        ...     print('2.')
        Traceback (most recent call last):
          File "&lt;pyshell#2>", line 1, in &lt;module>
            with mk:
          File "c:\PROGRAMS\Python34\lib\contextlib.py", line 61, in __enter__
            raise RuntimeError("generator didn't yield") from None
        RuntimeError: generator didn't yield
    </example>
    <note>
        Varianta s <code>with můj_kontext():</code> by samozřejmě fungovala při každém volání právě proto, že uvedený „jednoběhový“ kontext je pokaždé znovu vytvořen.
    </note>

</slide>
<slide title="Vícenásobný kontext „with“">

    <p>
        Poznamenejme ještě, že kód..
    </p>
    <example lang="python">
        with A() as a, B() as b:
            BLOK
    </example>
    <p>
        ..je ekvivalentní kódu:
    </p>
    <example lang="python">
        with A() as a:
            with B() as b:
                BLOK
    </example>
    <p>
        A hlavně – od Python'u 3.10 konečně půjde uzávorkovat! Takže bude možné psát třebas:
    </p>
    <example lang="python">
        with (
            A() as a,
            B() as b
        ):
            BLOK
    </example>

</slide>


</lecture>
