﻿<?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>N-tice</title>
  <date>2011-10-27</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>
    N-tice (aneb taply, angl. <em>tuples</em>) jsou v Python'u snad ještě důležitější typ než seznamy. Jen asi často nejsou na první pohled tak vidět. Stejně jako seznamy je to typ sekvenční, ale narozdíl od seznamů neměnitelný (<em>immutable</em>), což je jeho zcela základní a vůbec nejvýznamější vlastnost.
  </p>
  <note>
    Popravdě to, že n-tice jsou sekvenční typ, je spíše na škodu jejich logičtějšímu záměru – totiž jako „lehkotonážních“ datových struktur (ke kterým se v Python'u 3.x více blíží díky zavedení <a href="/materialy/python/types.plus/NamedTuples.xml">pojmenovaných n-tic</a>; více viz <a href="/materialy/python/objects/overview.xml?slajd=6&amp;par=2">Cesta k objektům</a>).
  </note>

</slide>
<slide title="Zavedení – „,“ vs „(,)“ a „tuple()“">

  <p class="enumerate">
    Z daných objektů vytvoříme n-tici nejsnadněji jejich „zčárkováním“:
  </p>
  <example lang="python">
    >>> xs = 'Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666'
    >>> xs
    ('Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666')
    >>> type(xs)
    &lt;class 'tuple'>
  </example>
  <note>
      V některých případech přehlednosti prospěje ještě i dodatečné uzavření do kulatých závorek (a někde je dokonce i vyžadováno – např. u výstupních prvků generátorové notace), nicméně <strong>konstruktorem n-tice je čárka</strong>:
      <example lang="python">
        >>> xs = ( 'Ahoj!', 123, [12, 'tři'] )
        >>> xs
        ('Ahoj!', 123, [12, 'tři'])
        >>> type(xs)
        &lt;class 'tuple'>
      </example>
  </note>
  
  <p class="enumerate">
    Kromě toho je možné za pomoci funkce <code>tuple()</code> převádět jiné vhodné typy, například řetězce, na n-tice jejich proiterováním po jednotlivých prvcích:
  </p>
  <example lang="python">
>>> xs = "Ahoj, světe!"
>>> xs
'Ahoj, světe!'
>>> tuple(xs)
('A', 'h', 'o', 'j', ',', ' ', 's', 'v', 'ě', 't', 'e', '!')
  </example>

</slide>
<slide title="Prázdná n-tice">

  <p>
    Vyrobit <strong>prázdnou n-tici</strong> jde snadno pomocí funkce <code>tuple()</code> nebo tak trochu záhadně pomocí přímé notace:
  </p>
  <example lang="python">
>>> xs = tuple()
>>> xs
()

>>> xs = ()
>>> xs
()

>>> type(xs)
&lt;class 'tuple'>
  </example>

</slide>
<slide title="N-tice jako sekvence">

  <p>
    Jelikož n-tice patří mezi sekvence, máme k dispozici celou armádu sekvenčních operací:
  </p>
  <example lang="python">
>>> xs = ('Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666')

# délka sekvence
>>> len(xs)
5

# konkrétní prvek
>>> xs[3]
['hokus', 'pokus']
>>> xs[-3]
(12, 'tři')

# různé výřezy
>>> xs[1:3]
(123, (12, 'tři'))
>>> xs[1::2]
(123, ['hokus', 'pokus'])
>>> xs[3:]
(['hokus', 'pokus'], 'lokus 666')
>>> xs[-3:]
((12, 'tři'), ['hokus', 'pokus'], 'lokus 666')

# dotaz na výskyt prvku
>>> 123 in xs
True
>>> (12, 'tři') in xs
True
>>> 'fokus' in xs
False

# dvě spojené kopie
>>> xs * 2
('Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666', 'Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666')
  </example>
  <note>
    Komentáře zde pochopitelně nejsou součástí výstupu interaktivního interpretru, slouží pouze k popisu zde zapsaného kódu.
  </note>

</slide>
<slide title="N-tice jako sekvence – smyčka „for-in“">

  <p class="enumerate">
    Po n-ticích se pak přirozeně prochází smyčkou, pro libovolné typy prvků ve třech základních podobách:
  </p>
  <example lang="python">
>>> for x in xs:
...     print(x)
...
Ahoj!
123
(12, 'tři')
['hokus', 'pokus']
lokus 666

>>> for (i, x) in enumerate(xs):
...     print(i, x)
...
0 Ahoj!
1 123
2 (12, 'tři')
3 ['hokus', 'pokus']
4 lokus 666

>>> for x in reversed(xs):
...   print(x)
...
lokus 666
['hokus', 'pokus']
(12, 'tri')
123
Ahoj!
  </example>
  
  <p class="enumerate">
    Poslední variantu – řazení prvků pomocí funkce <em>sorted()</em> – však můžeme použít pouze tehdy, jsou-li jednotlivé <strong>prvky porovnatelné</strong> (což typicky znamená <em>stejného typu</em>, zde ukázáno na celých číslech, ale není to nutně pravda):
  </p>
  <example lang="python">
>>> xs = (5, 2, 3, 1, 4)
>>> for x in sorted(xs):
...   print(x)
...
1
2
3
4
5
  </example>

</slide>
<slide title="N-tice jako neměnitelné sekvence">

  <p>
    Podobně jako řetězce patří n-tice mezi sekvence <strong>neměnitelné</strong> (<i>immutable</i>), což znamená, že jejich obsah po vytvoření již není možné měnit:
  </p>
  <example lang="python">
>>> xs = ( 'Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666' )
>>> xs[3]
['hokus', 'pokus']
>>> xs[3] = 'X'
Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
TypeError: 'tuple' object does not support item assignment
  </example>

  <p>
    Spojením dvou n-tic však vzniká n-tice zcela nová, proto následující kód je v pořádku:
  </p>
  <example lang="python">
>>> xs = xs + ("svět", 666)
>>> xs
('Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666', 'svět', 666)
  </example>

</slide>
<slide title="Dotazování na prvky – „in“, „index()“, „count()“">

  <p class="enumerate">
    Kromě již dříve uvedeného a všem sekvencím společného operátoru <code>in</code>:
  </p>
  <example lang="python">
>>> xs = ('Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666')

>>> 123 in xs
True
>>> (12, 'tři') in xs
True
>>> 'fokus' in xs
False
  </example>
  
  <p class="enumerate">
    ...mají n-tice k dispozici i metodu <code>index(PRVEK)</code>, která slouží k navrácení pozice prvního výskytu prvku s uvedenou hodnotou:
  </p>
  <example lang="python">
>>> xs = ('jedna', 'dvě', 'tři', 'jedna', 'pět', 'šest', )

>>> xs.index('jedna')
0
>>> xs.index('pět')
4
  </example>
  <note>
    Není-li takový prvek v seznamu k dispozici, obdržíme výjimku <em>ValueError</em>.
  </note>
  
  <p class="enumerate">
    Kromě toho můžeme i počítat výskyty zadaného prvku pomocí metody <code>count(PRVEK)</code>:
  </p>
  <example lang="python">
>>> xs = ('jedna', 'dvě', 'tři', 'jedna', 'pět', 'šest', )

>>> xs.count('jedna')
2
>>> xs.count('dvě')
1
  </example>

</slide>
<slide title="Přiřazování více hodnot najednou">

  <p>
    Snad nejpřekvapivější, ale přitom nejčastější, je použití n-tic pro přiřazování více hodnot najednou.
  </p>
  <p>
    Než se pokoušet o vysvětlování, radši na několika dalších slajdech uvedu rovnou pár příkladů.
  </p>
  <note>
      Většina příkladů podle DiP3, další z praxe.
  </note>

</slide>
<slide title="Přiřazování více hodnot najednou I">

  <p>
    Nejběžnější použití n-tic pro <em>přiřazení</em> více hodnot najednou:
  </p>
  <example lang="python">
>>> v = ('a', 2, True)
>>> v
('a', 2, True)
>>> type(v)
&lt;class 'tuple'>

>>> (x, y, z) = v
>>> x
'a'
>>> y
2
>>> z
True
  </example>
  <note>
    V podstatě jsme do n-tice o třech proměnných <code>(x, y, z)</code> přiřadili n-tici <code>v</code>, která má právě tři prvky. Ve výsledku do každé ze tří proměnných byl přiřazen odpovídající prvek z n-tice <code>v</code>. A jelikož konstruktorem n-tice v literální notaci je čárka a nikoli závorky, můžete napsat též <code>x, y, z = v</code> a výsledek bude úplně stejný.
  </note>

</slide>
<slide title="Přiřazování více hodnot najednou II">

  <p>
    Podobně můžeme více hodnot najednou <em>předat</em>, např. jako návratovou hodnotu z funkce:
  </p>
  <example layout="horizontal">
    <program src="_files/tuples1.py" lang="python"/>
    <out src="_files/tuples1.out" lang="text"/>
  </example>
  <note>
    Zde v prvním případě předáváme vracenou n-tici o třech prvcích do jedné proměnné <em>y</em> – stane se z ní tudíž také n-tice o třech prvcích. Ve druhém případě provedeme rovnou „rozbalení“ n-tice na jednotlivé prvky.
  </note>

</slide>
<slide title="Přiřazování více hodnot najednou III">

  <p>
    Dokonce takhle můžeme přiřazovat i objekty, které nejsou n-ticemi, ale dá se po nich iterovat, např. seznamy..
  </p>
  <example lang="python">
>>> x, y, z = [1, 2, 3]

>>> x
1
>>> y
2
>>> z
3
  </example>
  <p>
    ..nebo třeba i rozsahy (<em>range</em>):
  </p>
  <example lang="python">
>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

>>> MONDAY
0
>>> FRIDAY
4
  </example>
  <note>
    Inspirace z modulu <code>calendar</code>.
  </note>
  <p>
    A koneckonců klidně i řetězce:
  </p>
  <example lang="python">
    >>> a, b, c = 'abc'
    >>> a
    'a'
  </example>

</slide>
<slide title="Poznámka – Jednice na jednici &amp; „[]=“">

    <p class="enumerate">
        Jelikož konstruktorem n-tice je tedy čárka (a nikoli závorky, jak by to občas mohlo vypadat), můžete se někdy (obzvláště příklady na <a href="/materialy/python/pypi/MatPlotLib.xml">matplotlib</a> se tím přímo hemží) setkat i s kódem následujícího typu:
    </p>
    <example lang="python">
        >>> x, = ['abc']
        >>> x
        'abc'
    </example>
    <p>
        Po <a href="?slajd=11">předchozím slajdu</a> je vysvětlení jasné: Jelikož <code>x,</code> je jednoprvková n-tice a objekt napravo je iterovatelný a obsahuje právě jenom jeden prvek, dojde k automatickému „rozbalení“ tohoto (jediného) prvku vpravo na příslušný (jediný) prvek n-tice vlevo.
    </p>
    <note>
        Pozor na chování u řetězců, které je jako vždy trošičku matoucí:
        <example lang="python">
            # Jednoprvkový řetězec se samozřejmě krásně rozbalí:
            >>> x, = 'a'
            >>> x
            'a'

            # Víceprvkový má však už prvků k rozbalení více, než je jednicí požadováno:
            >>> x, = 'abc'
            Traceback (most recent call last):
              File "&lt;pyshell#22>", line 1, in &lt;module>
                x, = 'abc'
            ValueError: too many values to unpack (expected 1)
        </example>
    </note>

    <p class="enumerate">
        Existuje ještě druhý způsob, jak rozbalit jeden prvek na jeden, ale už nemá nic společného s n-ticí a je matoucí snad ještě víc:
    </p>
    <example lang="python">
        >>> [z] = ('abc',)
        >>> z
        'abc'
    </example>
    <handout>
        To obšlehl Python od JavaScriptu poté, co JavaScript obšlehl od Pythonu přiřazování na n-tice? Nebo to tam bylo vždycky? 0_o
    </handout>

</slide>
<slide title="Přiřazování více hodnot najednou IV">

  <p>
    Do stejné „vlny“ patří i typicky pythonovské prohození hodnot dvou proměnných..
  </p>
  <example lang="python">
a, b = b, a
  </example>
  <p>
    ..nebo přiřazení blíže neurčeného počtu dat do menšího počtu proměnných za pomoci operátoru <code>*</code>:
  </p>
  <example lang="python">
>>> a, *b, c = 'první', 1, 2, 'ahoj', 'poslední'

>>> a
'první'
>>> b
[1, 2, 'ahoj']
>>> c
'poslední'
  </example>
  <notes>
      <note>
        Zde máme vpravo n-tici o pěti prvcích, ale nalevo provádíme „rozbalení“ pouze do tří proměnných. Při tom je jasně určeno přiřazení prvního, resp. posledního, prvku do proměnných <em>a</em>, resp. <em>c</em>, a vše ostatní pod sebe „schlamstne“ díky operátoru <code>*</code> proměnná <em>b</em> (a to v podobě <em>seznamu</em>).
      </note>
      <note>
        Operátor <code>*</code> může být u n-tice nalevo samozřejmě u kterékoliv proměnné.
      </note>
  </notes>

</slide>
<slide title="Více o operátoru „*“ I">

    <p>
        Díky „hvězdičce“ můžete mít proměnných i více, než je k dispozici prvků na rozbalení – přebývající ohvězdičkovaná bude obsahovat prázdný seznam:
    </p>
    <example lang="python">
        >>> *a, b, c = 1, 2
        >>> a
        []
        >>> b
        1
        >>> c
        2
    </example>

</slide>
<slide title="Více o operátoru „*“ II">

    <p>
        Ale jde to i opačně – více prvků vpravo na jednoprvkovou n-tici vlevo:
    </p>
    <example lang="python">
        >>> *x, = 'abc'
        >>> x
        ['a', 'b', 'c']
    </example>
    <note>
        Pro srovnání standardní chování při běžném přiřazení:
        <example lang="python">
            >>> x = 'abc'
            >>> x
            'abc'
        </example>
    </note>

</slide>
<slide title="Více o operátoru „*“ III">

    <p>
        Předchozí má přímé využití u funkcí (více viz <a href="/materialy/python/functions/parameters.xml?slajd=2">u nich</a>):
    </p>
    <example lang="python">
        # jeden prvek
        >>> print([1,2,3])
        [1, 2, 3]
        
        # rozložení na jednotlivé prvky
        >>> print(*[1,2,3])
        1 2 3
    </example>
    <p>
        Celý „trik“ spočívá v tom, že funkce <em>print()</em> umí přijmout (a následně vytisknout) libovolný počet vstupních argumentů. A tak zatímco v prvním případě dostala argument jeden, totiž tříprvkový seznam, tak ve druhém případě byl tento seznam operátorem <code>*</code> rozložen na své tři prvky.
    </p>
    <note>
        Kdo by nevěřil, zkuste si toto:
        <example lang="python">
            >>> print(*[1,2,3], sep='|')
            1|2|3
        </example>
    </note>

</slide>
<slide title="Poznámka k objektu „_“">

    <p>
        Při rozbalování víceprvkových objektů pomocí n-tic můžete s výhodou použít speciální proměnnou <strong><code>_</code></strong>, tedy <strong>podtržítko</strong>. Používá se k označení údajů (objektů), <strong>které nás opravdu, ale opravdu nezajímají</strong>, takže kvůli nim nepotřebujeme (a nechceme) zavádět další jméno(a):
    </p>
    <example lang="python">
        # chci znát pouze první prvek ze dvou
        >>> a, _ = 1, 2
        >>> a
        1
        
        # zajímá mě pouze poslední prvek a žádný jiný
        >>> *_, a = 1, 2, 3
        >>> a
        3
    </example>
    <p>
        Univerzálnost podtržítka tkví v tom, že jich můžete <strong>přehledně</strong> namíchat dohromady vícero a ušetříte tak hromadu zbytečných proměnných:
    </p>
    <example lang="python">
        # chci pouze druhý a čtvrtý prvek, ostatní mě nezajímají
        >>> _, a, _, b, *_ = 1, 2, 3, 4, 5, 6, 7
        >>> print(a, b)
        2 4
    </example>
    <notes>
        <note>
            Kupodivu i bez podtržítka byste si mohli vystačit s jedním jménem navíc, například <code>c, a, c, b, *c = 1, 2, 3, 4, 5, 6, 7</code>, ale proč to dělat, když to není zdaleka tak přehledné.
        </note>
        <note>
            Mimochodem – jak v proměnné <em>c</em>, tak v proměnné <em>_</em> z předchozího příkladu bude poslední přiřazená hodnota, takže <code>[5, 6, 7]</code>.
        </note>
    </notes>

</slide>
<slide title="Přiřazování více hodnot najednou V">

    <p>
        Velmi užitečné (a zároveň asi i trochu matoucí) je <strong>vytváření</strong> nových <strong>n-tic z n-tic</strong> již existujících. Příklady budou podstatně výmluvnější:
    </p>
    <example lang="python">
        # vstupní n-tice
        >>> xs = 1, 2
        >>> ys = 'a', 'b'

        # a) „Sečtení“ složí prvky:
        >>> zs = xs + ys
        >>> zs
        (1, 2, 'a', 'b')
        
        # b) „Zenticování“ dá n-tici n-tic:
        >>> zs = xs, ys
        >>> zs
        ((1, 2), ('a', 'b'))
        
        # c) Operátor * provádí svou magii:
        >>> zs = *xs, ys
        >>> zs
        (1, 2, ('a', 'b'))
        # Jde to ale i bez něj:
        >>> zs = xs + (ys,)
        >>> zs
        (1, 2, ('a', 'b'))
    </example>
    <note>
        Ale pozor na priority operátorů – bez závorek to dopadne úplně jinak!
        <example lang="python">
            >>> zs = xs + ys,
            >>> zs
            ((1, 2, 'a', 'b'),)
        </example>
        N-tice se totiž nejdříve složí po prvcích, a teprve potom se z nich udělá výstupní – dosti neprůhledná – jednice.
    </note>

</slide>
<slide title="Význam n-tic I">

  <p>
    Jelikož jsou n-tice neměnitelné (<em>immutable</em>), tak se z hlediska sekvenčních typů chovají velmi podivně:
  </p>
  <ul>
    <li>
        žádné nové prvky do nich přidávat nemůžeme;
    </li>
    <li>
        žádné prvky z nich nemůžeme mazat;
    </li>
    <li>
        nemůžeme je řadit.
    </li>
  </ul>
  <p>
    Jinými slovy n-tice slouží k „zapouzdření“ několika (heterogenních) hodnot do společné „zmražené“ datové struktury.
  </p>
  
  <p>
    Ostatně když se podíváme, co „všechno“ n-tice podporují:
  </p>
  <example lang="python">
>>> dir(xs)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__',
'__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'count', 'index']
  </example>
  <note>
    Pouze dvě metody – <code>count()</code> a <code>index()</code> !
  </note>
  <p>
    ...tak nebýt předchozích slajdů o přiřazování více hodnot najednou (tedy právě onoho zmíněného „zapouzdřování“), kvůli kteréžto vlastnosti by jiné jazyky snad i vraždily, mohli bychom propadnout depresi, k čemu jsou vlastně vůbec dobré.
  </p>

</slide>
<slide title="Význam n-tic II">

  <p class="enumerate">
    Hlavní a největší a nejrozšířenější atd. <strong>význam n-tic je právě v jejich použití pro univerzální předávání nejrůznějšího počtu heterogenních hodnot libovolným směrem</strong>. Ostatně jak už jsem několikrát zdůrazňoval:
  </p>
  <h1>„Python bez n-tic by nebyl Python.“ (JPZ)</h1>
  
  <p class="enumerate">
    Pro techničtěji zaměřené typy tu mám i trochu jiné shrnutí (volně podle DiP3):
  </p>
  <ul>
    <li>
        n-tice jsou rychlejší než seznamy (takže postačí-li vám, nebo dokonce vyžadujete konstantní sadu hodnot, použijte spíše n-tici než seznam);
    </li>
    <li>
        narozdíl od seznamu <strong>n-tici nemůžeme měnit</strong> (možná toto neustálé zdůrazňování už zní hloupě, ale jsou situace, kdy budete rádi, že na data nemůžete ani vy, ani nikdo jiný nijak „šáhnout“);
    </li>
    <li>
        právě díky své neměnnosti (jsou tak totiž <a href="/materialy/python/types/_remarks.xml?slajd=7">hešovatelné</a>) můžete n-tice složené z neměnných typů použít jako klíče do slovníků (a jestli se vám z toho nezatočila hlava, tak už asi z ničeho).
        <note>
            S tou neměnností n-tic to ale zase není až tak žhavé – pokud totiž vyrobíte n-tici, jejímž alespoň jedním prvkem bude proměnný datový typ (třebas seznam), nebude výsledná n-tice hešovatelná a tudíž nebude použitelná na mnoha místech, kde by „klasická neměnná“ n-tice byla. Ba co hůř – takováto <a href="/materialy/python/pitfalls.xml?slajd=17">n-tice bude dokonce i proměnná</a>…
        </note>
    </li>
  </ul>
  <P>
    A především: Díky vlastnosti univerzálního rozbalování stejně početných objektů na odpovídající n-tice, operátoru <code>*</code> a speciálnímu jménu <code>_</code> jsme při předávání hodnot téměř v programátorské obdobě nirvány ^_~
  </P>

</slide>


</lecture>
