N-tice (aneb taply, angl. tuples) 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ý (immutable), což je jeho zcela základní a vůbec nejvýznamější vlastnost.
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í pojmenovaných n-tic; více viz Cesta k objektům).
Z daných objektů vytvoříme n-tici nejsnadněji jejich „zčárkováním“:
>>> xs = 'Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666'
>>> xs
('Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666')
>>> type(xs)
<class 'tuple'>
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ě konstruktorem n-tice je čárka:
>>> xs = ( 'Ahoj!', 123, [12, 'tři'] )
>>> xs
('Ahoj!', 123, [12, 'tři'])
>>> type(xs)
<class 'tuple'>
Kromě toho je možné za pomoci funkce tuple() převádět jiné vhodné typy, například řetězce, na n-tice jejich proiterováním po jednotlivých prvcích:
Jelikož n-tice patří mezi sekvence, máme k dispozici celou armádu sekvenčních operací:
>>> 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')
Komentáře zde pochopitelně nejsou součástí výstupu interaktivního interpretru, slouží pouze k popisu zde zapsaného kódu.
Po n-ticích se pak přirozeně prochází smyčkou, pro libovolné typy prvků ve třech základních podobách:
>>> 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!
Poslední variantu – řazení prvků pomocí funkce sorted() – však můžeme použít pouze tehdy, jsou-li jednotlivé prvky porovnatelné (což typicky znamená stejného typu, zde ukázáno na celých číslech, ale není to nutně pravda):
>>> xs = (5, 2, 3, 1, 4)
>>> for x in sorted(xs):
... print(x)
...
1
2
3
4
5
Podobně jako řetězce patří n-tice mezi sekvence neměnitelné (immutable), což znamená, že jejich obsah po vytvoření již není možné měnit:
>>> xs = ( 'Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666' )
>>> xs[3]
['hokus', 'pokus']
>>> xs[3] = 'X'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Spojením dvou n-tic však vzniká n-tice zcela nová, proto následující kód je v pořádku:
Snad nejpřekvapivější, ale přitom nejčastější, je použití n-tic pro přiřazování více hodnot najednou.
Než se pokoušet o vysvětlování, radši na několika dalších slajdech uvedu rovnou pár příkladů.
Většina příkladů podle DiP3, další z praxe.
Nejběžnější použití n-tic pro přiřazení více hodnot najednou:
>>> v = ('a', 2, True)
>>> v
('a', 2, True)
>>> type(v)
<class 'tuple'>
>>> (x, y, z) = v
>>> x
'a'
>>> y
2
>>> z
True
V podstatě jsme do n-tice o třech proměnných (x, y, z) přiřadili n-tici v, 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 v. A jelikož konstruktorem n-tice v literální notaci je čárka a nikoli závorky, můžete napsat též x, y, z = v a výsledek bude úplně stejný.
Podobně můžeme více hodnot najednou předat, např. jako návratovou hodnotu z funkce:
Zde v prvním případě předáváme vracenou n-tici o třech prvcích do jedné proměnné y – 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.
Dokonce takhle můžeme přiřazovat i objekty, které nejsou n-ticemi, ale dá se po nich iterovat, např. seznamy..
>>> x, y, z = [1, 2, 3]
>>> x
1
>>> y
2
>>> z
3
..nebo třeba i rozsahy (range):
>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
>>> MONDAY
0
>>> FRIDAY
4
Inspirace z modulu calendar.
A koneckonců klidně i řetězce:
>>> a, b, c = 'abc'
>>> a
'a'
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 matplotlib se tím přímo hemží) setkat i s kódem následujícího typu:
>>> x, = ['abc']
>>> x
'abc'
Po předchozím slajdu je vysvětlení jasné: Jelikož x, 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.
Pozor na chování u řetězců, které je jako vždy trošičku jiné:
# 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 "<pyshell#22>", line 1, in <module>
x, = 'abc'
ValueError: too many values to unpack (expected 1)
Do stejné „vlny“ patří i typicky pythonovské prohození hodnot dvou proměnných..
a, b = b, a
..nebo přiřazení blíže neurčeného počtu dat do menšího počtu proměnných za pomoci operátoru *:
>>> a, *b, c = 'první', 1, 2, 'ahoj', 'poslední'
>>> a
'první'
>>> b
[1, 2, 'ahoj']
>>> c
'poslední'
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 a, resp. c, a vše ostatní pod sebe „schlamstne“ díky operátoru * proměnná b (a to v podobě seznamu).
Operátor * může být u n-tice nalevo samozřejmě u kterékoliv proměnné.
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:
>>> *a, b, c = 1, 2
>>> a
[]
>>> b
1
>>> c
2
Ale jde to i opačně – více prvků vpravo na jednoprvkovou n-tici vlevo:
>>> *x, = 'abc'
>>> x
['a', 'b', 'c']
Pro srovnání standardní chování při běžném přiřazení:
>>> x = 'abc'
>>> x
'abc'
Předchozí má přímé využití u funkcí (více viz u nich):
# jeden prvek
>>> print([1,2,3])
[1, 2, 3]
# rozložení na jednotlivé prvky
>>> print(*[1,2,3])
1 2 3
Celý „trik“ spočívá v tom, že funkce print() 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 * rozložen na své tři prvky.
Kdo by nevěřil, zkuste si toto:
>>> print(*[1,2,3], sep='|')
1|2|3
Při rozbalování víceprvkových objektů pomocí n-tic můžete s výhodou použít speciální proměnnou _, tedy podtržítko. Používá se k označení údajů (objektů), které nás opravdu, ale opravdu nezajímají, takže kvůli nim nepotřebujeme (a nechceme) zavádět další jméno(a):
# 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
Univerzálnost podtržítka tkví v tom, že jich můžete přehledně namíchat dohromady vícero a ušetříte tak hromadu zbytečných proměnných:
# chci pouze druhý a čtvrtý prvek, ostatní mě nezajímají
>>> _, a, _, b, *_ = 1, 2, 3, 4, 5, 6, 7
>>> print(a, b)
2 4
Kupodivu i bez podtržítka byste si mohli vystačit s jedním jménem navíc, například c, a, c, b, *c = 1, 2, 3, 4, 5, 6, 7, ale proč to dělat, když to není zdaleka tak přehledné.
Mimochodem – jak v proměnné c, tak v proměnné _ z předchozího příkladu bude poslední přiřazená hodnota, takže [5, 6, 7].
Velmi užitečné (a zároveň asi i trochu matoucí) je vytváření nových n-tic z n-tic již existujících. Příklady budou podstatně výmluvnější:
# 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'))
Ale pozor na priority operátorů – bez závorek to dopadne úplně jinak!
>>> zs = xs + ys,
>>> zs
((1, 2, 'a', 'b'),)
N-tice se totiž nejdříve složí po prvcích, a teprve potom se z nich udělá výstupní – dosti neprůhledná – jednice.
Jelikož jsou n-tice neměnitelné (immutable), tak se z hlediska sekvenčních typů chovají velmi podivně:
žádné nové prvky do nich přidávat nemůžeme;
žádné prvky z nich nemůžeme mazat;
nemůžeme je řadit.
Jinými slovy n-tice slouží k „zapouzdření“ několika (heterogenních) hodnot do společné „zmražené“ datové struktury.
Ostatně když se podíváme, co „všechno“ n-tice podporují:
...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é.
Hlavní a největší a nejrozšířenější atd. 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. Ostatně jak už jsem několikrát zdůrazňoval:
„Python bez n-tic by nebyl Python.“ (JPZ)
Pro techničtěji zaměřené typy tu mám i trochu jiné shrnutí (volně podle DiP3):
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);
narozdíl od seznamu n-tici nemůžeme měnit (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“);
právě díky své neměnnosti (jsou tak totiž hešovatelné) 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).
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 n-tice bude dokonce i proměnná…
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 * a speciálnímu jménu _ jsme při předávání hodnot téměř v programátorské obdobě nirvány ^_~