<?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>Anotace funkcí</title>
  <date>2017-05-29</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
-->


<slide title="Úvod">

    <p>
        <strong>Anotace funkcí</strong> (myšleno tím jejich argumentů a návratových hodnot) se v Python'u objevily jako první (tlak externích projektů, které uvedené řešily, byl už asi příliš veliký) a <em>obecné anotace proměnných</em> byly pak už – o dva roky později – přidány podle jejich vzoru.
    </p>
    <p>
        Nicméně tato přednáška se věnuje především specifikům anotací u funkcí, proto, pokud je ještě neznáte, se nejdříve seznamte s <a href="/materialy/python/types/_annotations.xml">obecnými anotacemi proměnných</a>.
    </p>

</slide>
<slide title="Zápis">

  <p>
    Anotovat je u funkcí možno dvě věci:
  </p>

  <p class="enumerate">
    <strong>Parametry</strong>, a to jak poziční, tak pojmenované (s výchozí hodnotou):
  </p>
  <example lang="python">
    def fn(parametr_poziční: VÝRAZ):
        ...

    def fn(parametr_pojmenovaný: VÝRAZ = výchozí_hodnota):
        ...
  </example>
  <p>
    Parametry se tedy anotují pomocí dvojtečky <code>:</code>, za níž následuje výraz, jehož použití a vyhodnocení je zcela mimo pythoní interpretr – co si s ním která externí knihovna, nástroj či uživatel provedou, je zcela na nich. Výrazem tudíž může být prakticky naprosto cokoliv (tedy samozřejmě pokud je to validní pythoní výraz).
  </p>

  <p class="enumerate">
    <strong>Návratovou hodnotu</strong> funkce:
  </p>
  <example lang="python">
    def fn() -&gt; VÝRAZ:
        ...
  </example>
  <p>
    Návratová hodnota funkce se tedy anotuje pomocí sekvence znaků <code>-&gt;</code>, za níž následuje validní pythoní výraz, jehož interpretace je opět – stejně jako u anotací parametrů – zcela v rukou externího nástroje.
  </p>

</slide>
<slide title="Přístup k anotacím">

    <p>
        Přístup k anotacím funkcí se řeší úplně stejně jako pro <a href="/materialy/python/types/_annotations.xml?slajd=3">objekty v globálním jmenném prostoru</a> nebo ještě spíš jako pro <a href="/materialy/python/types/_annotations.xml?slajd=5">atributy tříd</a>, tj. pomocí atributu <strong><code>__annotations__</code></strong> na příslušné funkci:
    </p>
  <example lang="python">
    >>> def fn(arg1: int, arg2: "druhý argument", kwarg1: list = [1, 2, 3]) -> int:
            pass

    >>> fn.__annotations__
    {'arg1': &lt;class 'int'>,
     'arg2': 'druhý argument',
     'kwarg1': &lt;class 'list'>,
     'return': &lt;class 'int'>}
  </example>
  <p>
    <strong>Anotace jsou tedy dostupné ve slovníku pod jménem odpovídajících parametrů. Přitom případná návratová hodnota funkce je dostupná pod předdefinovaným jménem <em>return</em></strong> (to totiž jako jméno parametru nejde pochopitelně použít, takže nemůže dojít ke kolizi názvů).
  </p>
  <p>
    PS: Možnost spravovat anotace z vlastního pythoního kódu nabízí <a href="?slajd=6">docela nečekané možnosti</a>, jak si za chvíli ukážeme. Nejdříve však ještě pár příkladů zápisu anotací.
  </p>

</slide>
<slide title="Příklady I">

  <p>
    První typické použití, které asi napadne každého, bude označení typů parametrů a návratové hodnoty funkce, jak je zvykem (především) u staticky typovaných jazyků:
  </p>
  <example lang="python">
    def fn(a: int, b: int) -> int:
        pass
  </example>
  <p>
    Nicméně je třeba opět zdůraznit: <strong>Žádný pythoní interpretr nemá za povinnost tyto anotace nějak zpracovávat!</strong> Jejich jediným úkolem je příslušné anotace poskytnout jako vlastnosti funkce pro další použití, ale sami je (aspoň u většiny) nijak nepoužívají a dále nezpracovávají.
  </p>

</slide>
<slide title="Příklady II">

  <p>
    <strong>Anotací však může být libovolný pythoní výraz</strong>, kupříkladu tedy třebas obyčejný řetězec:
  </p>
  <example lang="python">
    def fn(a: "parametr funkce") -> "návratová hodnota funkce":
        pass
  </example>
  <p>
    Třeba je externí knihovna daného projektu použije pro tvorbu vylepšené dokumentace, kdo ví?
  </p>

</slide>
<slide title="Příklady III">

  <p>
    A asi je vidět, že se oboje dá snadno spojit dohromady – stačí použít všeobjímající n-tici:
  </p>
  <example lang="python">
    >>> def fn(a: (int, 'parametr')) -> (int, 'návratová hodnota'):
            pass

    >>> fn.__annotations__
    {'a': (&lt;class 'int'>, 'parametr'),
     'return': (&lt;class 'int'>, 'návratová hodnota')}
  </example>
  <p>
    Pokud si externí program, který anotace zpracovává, umí s uvedeným zápisem poradit, je vše v pořádku.
  </p>

</slide>
<slide title="Příklad použití I">

  <p>
    Důvodem, proč jsem původně tuto přednášku vůbec napsal (běžnému smrtelníkovi totiž zatím pořád ještě nejsou anotace moc platné), je <a href="https://stackoverflow.com/a/19684962" class="external">sada úžasných dekorátorů</a> od Roddyho MacSweenyho (zde hodně upravené):
  </p>
  <example lang="python" src="_files/annotations.py" />
  <p>
    Vidíme, že <a href="/materialy/python/functions/decorators.xml">dekorátor</a> <code>@checkargs</code> pomocí anotací a <a href="/materialy/python/objects/introspection.xml">introspekce</a> (funkce <em>isinstance()</em>) ověřuje, zda zadané parametry skutečně splňují předepsaný typ, zatímco dekorátor <code>@coerceargs</code> se dokonce snaží parametry na daný typ převést (přímou aplikací zadaného typového konstruktoru na daný parametr).
  </p>
  <note>
    PS: Do dekorátorů jsem připsal ověření typu návratové hodnoty. <a href="_files/annotations_signature.py">Zde</a> je úplnější alternativní implementace pomocí objektu <code>Signature</code>.
  </note>

</slide>
<slide title="Příklad použití II">

    <p>
        Nicméně je třeba říci, že použitím kontroly typů přijdeme o klasické pythoní <em>duck typing</em>, tedy zjednodušeně možnost zpracování různých typů, které však implementují stejné metody (zde možnost iterace po prvcích):
    </p>
    <example lang="python">
# a) sama o sobě se anotace k ničemu nepoužívá
>>> def fn(xs: list):
...     return [x**2 for x in xs]
>>> fn([1, 2, 3, 4])
[1, 4, 9, 16]
>>> fn((1, 2, 3, 4))
[1, 4, 9, 16]
>>> fn({1, 2, 3, 4})
[1, 4, 9, 16]

# b) anotace ve spojení s dekorátorem @checkargs
>>> @checkargs
... def fn(xs: list):
...     return [x**2 for x in xs]
>>> fn([1, 2, 3, 4])
[1, 4, 9, 16]
>>> fn((1, 2, 3, 4))
Traceback (most recent call last):
  File "&lt;pyshell#49>", line 1, in &lt;module>
    fn((1, 2, 3, 4))
  File "&lt;pyshell#43>", line 11, in _f
    check_type_error(arguments[index], annotations[argument])
  File "&lt;pyshell#43>", line 6, in check_type_error
    raise TypeError(f"{index} is not of type {argument}")
TypeError: (1, 2, 3, 4) is not of type &lt;class 'list'>
    </example>

</slide>
<slide title="Poznámka">

    <p>
        Ačkoli je následující poznámka v podstatě přímou kopií textu z povídání <a href="/materialy/python/types/_annotations.xml?slajd=6">o anotacích v Python'u obecně</a>, pokládám ji za natolik důležitou, že ji zde stejně zopakuji:
    </p>
    <p>
        Je třeba pamatovat na to, že Python byl/je/zůstává dynamicky typovaným jazykem a žádný z jeho interpretrů nemá v popisu práce s anotacemi dělat cokoliv jiného, než je „cpát“ do atributu <em>__annotations__</em>. Anotace v Python'u jsou a dlouho (navěky?) budou pouze pro potřeby externích nástrojů (či editorů), které nemusí vůbec provádět typovou kontrolu, ale klidně něco úplně jiného (například budovat lepší nápovědu).
    </p>
    <p>
        PS: Od Python'u 3.9, v kterém již jde použít pro anotace složených typů běžné typové konstruktory jazyka (např. <code>list[int]</code> apod.), již pro použití anotací znalost modulu <em>typing</em> není nezbytně nutná, ale občas se hodit může.
    </p>

</slide>


</lecture>
