Se seznamy se setkáme v Python'u úplně všude, málem stejně často jako s iterátory :) Je to jeden z nejdůležitějších sekvenčních typů – umožňuje schraňovat pohromadě libovolné (a proměnlivé) množství nejrůznějších objektů.
Z daných objektů vytvoříme seznam nejsnadněji jejich uzavřením do hranatých závorek:
Jelikož seznamy 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 seznamech se pak přirozeně prochází smyčkou, pro libovolné typy prvků ve třech základních podobách:
>>> xs = ['Ahoj!', 123, (12, 'tri'), ['hokus', 'pokus'], 'lokus 666']
>>> 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
Narozdíl např. od řetězců seznamy patří mezi sekvence měnitelné (mutable), což znamená, že jejich obsah po vytvoření můžeme libovolně upravovat:
>>> xs = [1, 2, 3, 4, 5]
>>> xs[3:] = [666]
>>> xs
[1, 2, 3, 666]
Jelikož nahrazujeme část seznamu (výřezová notace v tomto případě vrací seznam), Python očekává na vstupu zase seznam.
Kromě již dříve uvedeného a všem sekvencím společného operátoru in:
>>> xs = ['Ahoj!', 123, (12, 'tři'), ['hokus', 'pokus'], 'lokus 666']
>>> 123 in xs
True
>>> (12, 'tři') in xs
True
>>> 'fokus' in xs
False
...mají seznamy k dispozici i metodu index(PRVEK), která slouží k navrácení pozice prvního výskytu prvku s uvedenou hodnotou:
>>> xs = ['jedna', 'dvě', 'tři', 'jedna', 'pět', 'šest', ]
>>> xs.index('jedna')
0
>>> xs.index('pět')
4
Není-li takový prvek v seznamu k dispozici, obdržíme výjimku ValueError.
Kromě toho můžeme i počítat výskyty zadaného prvku pomocí metody count(PRVEK):
Přidat nové prvky do existující seznamu se dá celkem čtyřmi způsoby. Pro ukázku budeme pracovat nad počátečním seznamem xs = [1, 2.0, 'tři']:
Předně můžeme využít metody seznamu append(PRVEK) pro přidání jednoho nového prvku na konec seznamu:
>>> xs.append('four')
>>> xs
[1, 2.0, 'tři', 'four']
Ač to tak možná na první pohled nevypadá, ekvivalentní kód zařizující totéž jest: xs[ len(xs): ] = ['four']
Jeden prvek také můžeme pomocí metody insert(NA_KTERÝ_NOVÝ_INDEX, PRVEK) (resp. PŘED_KTERÝ_STARÝ_INDEX) vložit na vybrané místo seznamu:
>>> xs.insert(3, 3.5)
>>> xs
[1, 2.0, 'tři', 3.5, 'four']
Jinak řečeno: xs.insert(0, x) přidá prvek x na začátek seznamu, zatímco xs.insert(len(xs), x) na konec (takže provede to samé jako xs.append(x)).
A funguje to i pro záporné indexy – ty se počítají klasicky od konce.
Na konec seznamu můžeme také přidat více prvků seznamu jiného, a to buď pomocí metody extend(SEZNAM):
..nebo pomocí „obyčejného“ slepování sekvencí pomocí operátoru +:
>>> xs += ['sedm', 8.0, 9]
>>> xs
[1, 2.0, 'tři', 3.5, 'four', 5, 'šest', 'sedm', 8.0, 9]
Takto „přičíst“ se dá všelicos, třebas i n-tice nebo slovník. Pozor však na to, že přidávaná sekvence je proiterována prvek po prvku a takto prvek po prvku přidána do prvního seznamu:
>>> xs = [1, 2, 3, 4, 5]
>>> xs += {6, 7, 8, 9}
>>> xs
[1, 2, 3, 4, 5, 8, 9, 6, 7]
Odebrat prvky ze seznamu se dá několika způsoby. Pro ukázku budeme pracovat nad počátečním seznamem xs = ['jedna', 'dvě', 'tři', 'čtyři', 'pět', 'šest']:
Odebrat prvek, resp. prvky, na základě jeho, resp. jejich, pozice (indexu) nám umožňuje univerzální příkaz del:
>>> del xs[1]
>>> xs
['jedna', 'tři', 'čtyři', 'pět', 'šest']
>>> del xs[3:]
>>> xs
['jedna', 'tři', 'čtyři']
Stačí tedy pomocí výřezové notace označit, čeho se chceme zbavit, a del se již postará o ostatní.
Pro jeden prvek víceméně totéž zařídí metoda seznamu pop([INDEX]). Rozdíl je v tom, že odebraný prvek je metodou vrácen:
>>> xs = ['jedna', 'dvě', 'tři', 'čtyři', 'pět', 'šest']
>>> xs.pop() # odstraň poslední prvek
'šest'
>>> xs
['jedna', 'dvě', 'tři', 'čtyři', 'pět']
>>> xs.pop(0) # odstraň první prvek
'jedna'
>>> xs
['dvě', 'tři', 'čtyři', 'pět']
>>> xs.pop(2) # odstraň prvek na pozici 2, tj. třetí
'čtyři'
>>> xs
['dvě', 'tři', 'pět']
Pokus o odstranění prvku na neexistující pozici nebo z prázdného seznamu skončí výjimkou IndexError.
Možná trošku překvapivě pop(-1) je opravdu ekvivalentem pro pop().
Poslední možností je odstranit prvek na základě jeho obsahu, a nikoli indexu. O to se stará metoda remove(PRVEK). Odstraněn je vždy první výskyt daného prvku:
>>> xs = ['jedna', 'dvě', 'tři', 'dvě', 'pět', 'šest']
>>> xs.remove('dvě')
>>> xs
['jedna', 'tři', 'dvě', 'pět', 'šest']
Pokus o odstranění prvku, který se v seznamu nenachází, skončí výjimkou ValueError.
Prvky seznamu můžeme „otočit“, tj. zaměnit jejich pořadí od posledního k prvnímu, metodou reverse():
Metoda reverse() operuje nad aktuálním seznamem, tj. nic nevrací (resp. vrací hodnotu None), protože zamění pořadí prvků rovnou v seznamu, na kterém ji zavoláte! Její použití je z hlediska zachování dat ale neškodné, protože její další aplikace vše vrátí do původního stavu. Podle potřeby ale samozřejmě stačí použít globální funkci reversed().
Skutečné setřídění prvků seznamu (lexikografické) se pak dá udělat buď také přímo v místě (takže ztratíme původní uspořádání) pomocí metody sort([key[, reverse]]) nebo na kopii seznamu pomocí funkce sorted():
>>> xs = ['jedna', 'dvě', 'tři', 'čtyři', 'pět', 'šest', 'sedm']
>>> xs.sort()
>>> xs
['dvě', 'jedna', 'pět', 'sedm', 'tři', 'čtyři', 'šest']
Výchozí třídění řetězců probíhá porovnáváním unicodových code-points a česká nabodeníčka jsou holt až za ASCII-prvky…
Metoda sort() (a funkce sorted() také) navíc akceptuje na vstupu parametr reverse signalizující třídění v opačném směru:
Posledním – a veskrze „magickým“ :) – parametrem je parametr key, který slouží (v podobě funkce jednoho argumentu) k určení porovnávacího klíče nad prvky seznamu:
>>> names = [ "John", "Wendy", "Pete", "Jane", "David", "Amanda", "Ix"]
# setřídění podle délky prvků
# ~ třídění je stable, takže zachová původní pořadí prvků se stejnou délkou
>>> sorted(names, key=len)
['Ix', 'John', 'Pete', 'Jane', 'Wendy', 'David', 'Amanda']
Problematika parametru key a řazení vůbec je však dosti obsáhlá, a proto je jí věnována samostatná kapitola.
Po vzoru Petra Přikryla, překladatele úžasné knihy Marka Pilgrima Dive Into Python 3, budu podle aktuálního použití překládat anglický termín list comprehension někdy jako „generátorová notace (seznamu)“, jindy jako „generátor seznamu“ a ještě jindy i jako „generovaný seznam“.
Bezesporu jednou z nejúžasnějších „zkratek“, které nám Python nabízí, je generátorová notace (nejen) seznamu. Umožňuje nám často velmi elegantně a na minimu prostoru vytvořit z původního seznamu seznam nový, který se skládá třeba i z úplně nových prvků, vytvořených však na základě prvků seznamu původního a dodatečných pravidel.
Obecný konstruktor pro jednoduchý generátor seznamu vypadá takto:
[ Funkce(I) for I in SEKVENCE ]
[ Funkce(I) for I in SEKVENCE if PODMÍNKA ]
Zatímco první varianta „pouze“ transformuje prvky vstupní sekvence na prvky výstupního seznamu, varianta druhá navíc ještě vybírá, pro které všechny prvky se tato transformace provede.
Ve skutečnosti nemusí mít generovaná hodnota (v popisu jako Funkce(I)) se základní sekvencí I vůbec nic společného. Spíše jde o to, že výše popsané je zdaleka nejčastější využití generátoru seznamu.
Spíše než složité vysvětlování několik snad názorných ukázek:
>>> xs = list( range(10) )
>>> xs
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# kopie seznamu
>>> [x for x in xs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# každý prvek seznamu zvětšený o tři
>>> [x+3 for x in xs]
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
# seznam druhých mocnin prvků původního seznamu
>>> [x*x for x in xs]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# výběr sudých prvků
>>> [x for x in xs if x % 2 == 0]
[0, 2, 4, 6, 8]
# seznam n-tic, kde je každý prvek přítomen jako číslo a zároveň i jako řetězec
>>> [(x, str(x)) for x in xs]
[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), (6, '6'), (7, '7'), (8, '8'), (9, '9')]
# jako předchozí, ale pouze pro liché prvky z původního seznamu
>>> [(x, str(x)) for x in xs if x % 2 != 0]
[(1, '1'), (3, '3'), (5, '5'), (7, '7'), (9, '9')]
Sice už jsem to říkal, ale generovaný seznam nemusí mít s původním mnoho společného:
>>> [1 for x in "aeiou"]
[1, 1, 1, 1, 1]
>>> [i for i,x in enumerate("aeiou")]
[0, 1, 2, 3, 4]
>>> ds = {
... 'a': 'Haf!',
... 'e': 3,
... 'i': (1, 2, 3),
... 'o': list('abcd'),
... 'u': len,
... }
>>> [ds[x] for x in "aeiou"]
['Haf!', 3, (1, 2, 3), ['a', 'b', 'c', 'd'], <built-in function len>]
Vícenásobná generátorová notace už nevypadá tak průhledně a ne vždy dělá to, co by člověk na první pohled hádal:
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
# pro každý prvek první smyčky bude znovu vykonána celá smyčka druhá
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
# z tohohle by se dal vytěžit skalární součin
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]
Převzato z dokumentace k Python'u.
Ještě poměrně průhledný, i když dosti šílený způsob jak najít prvočísla menší než 50:
>>> noprimes = [j for i in range(2, 8) for j in range(i*2, 50, i)]
>>> noprimes
[4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 10, 15, 20, 25, 30, 35, 40, 45, 12, 18, 24, 30, 36, 42, 48, 14, 21, 28, 35, 42, 49]
>>> primes = [x for x in range(2, 50) if x not in noprimes]
>>> primes
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
A jelikož se generátory seznamu dají zanořovat do sebe...
Nepřekvapivě se pythoní seznamy používají především na místě jinde obvyklých polí, ale bez jejich omezení na předem známou délku a jeden jediný konkrétní typ.
Přímo s pomocí vestavěných metod můžeme snadno implementovat datové struktury fronty a zásobníku, ale v podobě několika dalších modulů jsou k dispozici i mnohem specifičtější a v některých ohledech též tradičnější další datové struktury:
S dostupnými metodami proměníte seznam velmi snadno v zásobník, tedy paměťovou strukturu, kde poslední přidaný prvek je také první vrácený (LIFO). Příklad přímo z dokumentace:
Téměř stejně jednoduše jako zásobník můžete naimplementovat i frontu, tedy paměťovou strukturu, kde první přidaný prvek je také první vrácený (FIFO). Příklad přímo z dokumentace: