<?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>Funkce čtené podruhé</title>
    <date>2017-05-10</date>
    <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>


<slide title="Úvod">

    <p>
        Funkce v Python'u jsou – spolu s n-ticemi – bez diskuse jedním z jeho nejdůležitějších prvků. Komu něco říká pojem <em>first class citizen</em>, dokáže si představit proč ^_~
    </p>
    <p>
        Nicméně nic není nikdy tak růžové, jak to na první (a někdy i na druhý) pohled vypadá, takže tuto přehledovou přednášku by si měli přečíst – a vyzkoušet! – všichni bez rozdílu.
    </p>

</slide>
<slide title="Zavedení funkce">

    <p>
        Funkce v Python'u se zavádí pomocí klíčového slova <em>def</em> a za dvojtečkou následuje odsazený blok kódu (vlastní tělo funkce):
    </p>
    <example lang="python">
        def fn():
            pass
    </example>

</slide>
<slide title="Návratová hodnota">

    <p class="enumerate">
        Návratovou hodnotu není potřeba výslovně uvádět, pokud žádná není potřeba. Python pak automaticky vrátí hodnotu <code>None</code>:
    </p>
    <example lang="python">
        >>> def fn():
        ...     pass

        >>> a = fn()
        >>> print(a)
        None
    </example>

    <p class="enumerate">
        Na druhou stranu z funkce můžeme kdykoliv vrátit naprosto libovolně, co nás zrovna napadne:
    </p>
    <example lang="python">
        >>> def fn():
        ...     return 'Ahoj!', 123, {'xyz': 666}
        
        >>> fn()
        ('Ahoj!', 123, {'xyz': 666})
    </example>
    <note>
        Celý „trik“ spočívá v datovém typu n-tice – potřebujeme-li vrátit víc než jeden prvek, prostě napíšeme čárku, čímž zavedeme n-tici, a vesele přidáváme tolik prvků, kolik zrovna potřebujeme.
    </note>

</slide>
<slide title="Parametry poziční">

    <p>
        Funkce v Python'u samozřejmě dokážou přejímat vstupní parametry:
    </p>
    <example lang="python">
        >>> def fn(a, b, c):
        ...     print(a, b, c)

        >>> fn(1, 'abc', [1, None, ('1', 'b')])
        1 abc [1, None, ('1', 'b')]
    </example>

</slide>
<slide title="Volný počet pozičních parametrů">

    <p>
        A to dokonce libovolný počet:
    </p>
    <example lang="python">
        >>> def fn(*args):
        ...     print(args)
        
        >>> fn()
        ()
        >>> fn(1)
        (1,)
        >>> fn(1, 'a', {'zzz': 333})
        (1, 'a', {'zzz': 333})
    </example>
    <note>
        Zde se o „trik“ prozměnu postaraĺ operátor <code>*</code>, který provedl převod libovolného počtu vstupních parametrů funkce do jedné jediné proměnné typu n-tice (já jsem říkal, že bez n-tic by Python nebyl Python!).
    </note>

</slide>
<slide title="Parametry pojmenované">

    <p>
        Python však také obsahuje jednu velmi příjemnou „vychytávku“ – parametry s výchozí hodnotou (a tudíž také jménem). Když je nevyplníte při volání, použije se přednastavená hodnota:
    </p>
    <example lang="python">
        >>> def fn(y=3):
        ...     print(2**y)
        
        >>> fn()
        8
        >>> fn(2)
        4
    </example>

</slide>
<slide title="Volný počet pojmenovaných parametrů">

    <p>
        Nepřekvapivě i jich může být libovolný počet:
    </p>
    <example lang="python">
        >>> def fn(**kwargs):
        ...     print(kwargs)
        
        >>> fn()
        {}
        >>> fn(a=1, xyz='666')
        {'a': 1, 'xyz': '666'}
    </example>
    <note>
        Zde se o „kouzlo“ překladu na datový typ slovník stará prozměnu operátor <code>**</code>.
    </note>

</slide>
<slide title="Mix parametrů">

    <p>
        Oba druhy parametrů – poziční i pojmenované – je samozřejmě možno míchat dohromady. Dokud budou pojmenované použity jako poslední, vše bude krásně fungovat (a dokonce je můžete i volat jako poziční):
    </p>
    <example lang="python">
        >>> def fn(x, y=3):
        ...   print(x**y)
        ... 
        
        >>> fn()
        Traceback (most recent call last):
          File "&lt;stdin>", line 1, in &lt;module>
        TypeError: fn() missing 1 required positional argument: 'x'
        >>> fn(2)
        8
        >>> fn(2, 4)
        16
        >>> fn(2, y=4)
        16
        >>> fn(x=2, y=4)
        16
        >>> fn(x=2, 4)
          File "&lt;stdin>", line 1
        SyntaxError: positional argument follows keyword argument
    </example>

</slide>
<slide title="Vynucení použití jména parametru">

    <p>
        Přidáním <code>*</code> do definice funkce si můžete vynutit závazné použití jména u – vybraných! – parametrů s výchozí hodnotou:
    </p>
    <example lang="python">
        >>> def fn(a, b, x=2, *, y=3):
        ...     print(a, b, x, y)

        >>> fn()
        Traceback (most recent call last):
          File "&lt;pyshell#128>", line 1, in &lt;module>
            fn()
        TypeError: fn() missing 2 required positional arguments: 'a' and 'b'
        >>> fn(7, 8)
        7 8 2 3
        >>> fn(7, 8, 'x')
        7 8 x 3
        >>> fn(7, 8, 'x', 'y')
        Traceback (most recent call last):
          File "&lt;pyshell#131>", line 1, in &lt;module>
            fn(7, 8, 'x', 'y')
        TypeError: fn() takes from 2 to 3 positional arguments but 4 were given
        >>> fn(7, 8, 'x', y='y')
        7 8 x y
    </example>
    <note>
        PS: Kdo by přemýšlel, k čemu je to dobré, vzpomeň si na funkci <code>print()</code> – jak jinak řídit její chování, když vstupních objektů k vytištění může být libovolný počet?
    </note>

</slide>
<slide title="Parametry zadávané pouze pozičně">

    <!--p>
        Mimochodem, mezi vývojáři jazyka je <a href="https://groups.google.com/forum/#!topic/python-ideas/jH6YWDERjzw" class="external">poměrně silná podpora</a> pro přidání vynucení zadávání některých parametrů pouze jejich pozicí. V (brzké) budoucnosti se proto dá očekávat podpora pro zadávání funkcí ve stylu:
    </p-->
    <p>
        Od verze Python'u 3.8 si už můžete pomocí <code>/</code> vynutit i zadávání parametrů pouze pozicí:
    </p>
    <example lang="python">
        def fn(x, y, /, t, u, v=3, *, z='basta fidli'):
            ...
    </example>
    <p>
        Parametry <em>x</em> a <em>y</em> je nyní možné zadat jen a pouze jako první a druhý (to jest <code>fn(1, 2, ...)</code>) a nikoli pomocí jejich interního implementačního jména (tedy jako <code>fn(x=1, y=2, ...)</code>).
    </p>

</slide>
<slide title="Výchozí hodnota parametru I">

    <p>
        Ne vždy se ale všechno chová očekávaným způsobem, jako zde při odkazu na globální proměnnou:
    </p>
    <example lang="python">
        >>> x = 3
        ... 
        ... def fn(y=x):
        ...     print(y)
        
        >>> fn(5)
        5
        >>> fn()
        3
        >>> x = 2
        >>> fn()
        3
        >>> fn(4)
        4
    </example>
    <p>
        Python totiž hlavičku funkce zpracuje pouze při jejím prvním čtení, a v tu chvíli měla proměnná <em>x</em> hodnotu 3, která jí tak poněkud neintuitivně zůstane i nadále.
    </p>

</slide>
<slide title="Výchozí hodnota parametru II">

    <p>
        Pokud je výchozí hodnota parametru proměnného datového typu, čeká nás podobné překvapení:
    </p>
    <example lang="python">
        >>> def fn(x, y=[]):
        ...     y.append(x)
        ...     print(y)
        
        >>> fn(1, [])
        [1]
        >>> fn(1, [])
        [1]
        >>> fn(1)
        [1]
        >>> fn(1)
        [1, 1]
        >>> fn(1, [])
        [1]
        >>> fn(1)
        [1, 1, 1]
    </example>
    <p>
        Nezvyklé, není-liž pravda? Nicméně dá se to využít třebas pro jednoduchou memoizaci bez potřeby zavádět na zapamatování vstupních parametrů a spočítaných výsledků vlastní globální proměnnou.
    </p>

</slide>
<slide title="Funkce jako parametry">

    <p>
        Jelikož funkce v Python'u je „jenom další objekt“, asi už nepřekvapí, že ji můžeme předat na vstupu:
    </p>
    <example lang="python">
        >>> def fn(x, f):
        ...     print(f(x))

        >>> fn('abc', len)
        3
    </example>
    <note>
        A samozřejmě nejen funkce vestavěné můžeme předávat. To jsem jenom nechtěl zaneřádit příklad definicí další vlastní funkce.
    </note>

</slide>
<slide title="Funkce jako návratové hodnoty">

    <p>
        Stejně dobře můžeme funkci z funkce také vrátit:
    </p>
    <example lang="python">
        >>> def fn():
        ...     def f():
        ...         print('Ahoj!')
        ...     return f

        >>> fn()
        &lt;function fn.&lt;locals>.f at 0x7fa1cf9ce730>
        >>> fn()()
        Ahoj!
    </example>

</slide>
<slide title="Funkce jako návratové hodnoty – uzávěry">

    <p>
        Na funkcích vrácených z funkcí je krásné to, že si drží okolní kontext ze chvíle svého vrácení (tedy vytvoření):
    </p>
    <example lang="python">
        >>> def fn(x):
        ...     def f():
        ...         print('Ahoj,', x, '!')
        ...     return f
        
        >>> fn('brachu')()
        Ahoj, brachu !
    </example>
    <note>
        Tomuto chování se odborně říká <strong>uzávěr</strong> aneb <em>closure</em>. Python nepodporuje uzávěry v celé jejich šíři (například funkce zavolané z funkcí do kontextu volající funkce přístup nemají), nicméně na většinu použití to stačí i tak.
    </note>

</slide>
<slide title="Místo závěru">

    <p>
        Spoustu věcí o funkcích jsem v této přednášce samozřejmě vůbec nezmínil, namátkou:
    </p>
    <ul>
        <li>
            funkce v Python'u jsou vlastně jenom jistý druh objektu, který se vyznačuje tím, že se dá zavolat (je na něm nadefinována metoda <code>__call__()</code>);
        </li>
        <li>
            jmenný prostor funkce – který souvisí s <a href="/materialy/python/functions/scope.xml">viditelností proměnných a dohledáváním jmen objektů</a> – se dá volně rozšiřovat (tedy můžete na existující funkci provést třebas <code>funkce.parametr = cokoliv</code>, čímž do jejího prostoru známých lokálních objektů přidáte objekt <em>parametr</em>);
        </li>
        <li>
            uzávěry se používají při konstrukci tzv. <a href="/materialy/python/functions/decorators.xml">dekorátorů</a>, bez jejichž pochopení budete trochu „plavat“ nejen při používání tříd;
        </li>
        <li>
            záměnou za <code>return</code> nebo doplněním funkce o klíčové slovo <code>yield</code> z ní vyrobíte tzv. <a href="/materialy/python/generators/generators.xml">generátor</a>, což je neuvěřitelně mocný koncept, který prostupuje celý (trojkový) Python;
        </li>
        <li>
            …
        </li>
    </ul>
    <p>
        Nicméně je snad jasné, že funkce v Python'u jsou velmi mocné (spousta vlastností jazyka na nich závisí; podobně jako na n-ticích ;-), ale také na nás mají připraveno pár velmi překvapivých a ne úplně očekávaných vlastností, které si při práci s nimi musíme dobře hlídat, aby nám nepodrazily nohy.
    </p>

</slide>


</lecture>
