Iterátory
Všetky iterovateľné objekty, čiže zoznamy, sety, slovníky, reťazce atp. – dátové typy, ktoré môžeme prechádzať element po elemente napríklad cyklom for…:
>>> l = [47, "Ahoj.", [], False] >>> for element in l: print(element) ... 47 Ahoj. [] False
… môžeme premeniť na ich iterátory, a to pomocou vstavanej funkcie iter()
. Tá volá metódu __iter__()
, ktorú nájdeme len pri iterovateľných objektoch, a tá vráti iterátor daného objektu:
>>> li = iter(l) >>> li <list_iterator object at 0x7f38eebc4400>
V tomto prípade sme dostali objekt triedy (typu) list_iterator. Pokiaľ by sme iterátor vytvárali z iného dátového typu, vytvorili by sme iterátor daného iterovateľného dátového typu:
iter("Ahoj.") -> str_iterator iter({2, 3}) -> set_iterator iter(('a', 'b')) -> tuple_iterator iter({1:"Jedna"}) -> dict_keyiterator (iterátor je vytvorený z kľúčov slovníku)
Iterátory sa však pri každom z typov správajú rovnako. Základnou schopnosťou iterátoru je možnosť použitia funkcie next()
, ktorá vracia vždy prvok iterátoru, ktorý je nasledujúci v poradí, čiže si pamätá svoju pozíciu.
>>> next(li) 47 >>> next(li) 'Ahoj.' >>> next(li) [] >>> next(li) False >>> next(li) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Chyba StopIteration značí to, že sme vyčerpali všetky prvky iterátoru.
Iterátor, resp. objekt, ktorý budeme iterovať, si môžeme vytvoriť aj sami. Objekt samozrejme vytvárame z triedy, takže si napíšeme triedu, z ktorej vytvoríme objekt, ktorý nám bude postupne vracať čísla fibonacciho postupnosti. Donekonečna.
Trieda pre vytvorenie iterátora by mala obsahovať metódy __iter__() a __next__().
# iter_fib.py class fib: def __init__(self): self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): a = self.a self.a, self.b = self.b, a + self.b return a
Z tejto triedy teraz vytvoríme objekt – iterátor.
>>> from iter_fib import fib >>> it_fib = fib() >>> next(it_fib) 0 >>> next(it_fib) 1 >>> next(it_fib) 1 >>> next(it_fib) 2 >>> next(it_fib) 3 >>> next(it_fib) 5 >>> next(it_fib) 8
Volať funkciu next()
na objekt it_fib
, čiže volať metódu __next__()
objektu it_fib
, by sme mohli donekonečna, a získať tak neobmedzené množstvo čísiel z fibonacciho postupnosti.
Generátory
Generátor je podobný iterátoru, no vytvárame ho pomocou funkcie, ktorá namiesto vrátenia hodnoty pomocou return
používa príkaz yield
. Takto vyzerá jednoduchá funkcia pre vytvorenie generátoru:
>>> def fce(): ... yield 1 ... yield 2 ... yield 3 ... >>> fce # funkcia s yield sa tvári ako každá iná funkcia, ale funguje rozdielne <function fce at 0x7f142e646f28> >>> generator = fce() >>> generator <generator object fce at 0x7f142d3b1468>
Vidíme, že funkcia fce()
pri zavolaní vrátila objekt generator object fce, aj keď navonok sa tvári ako klasická funkcia. Tento vytvorený objekt generátora je iterovateľný a môžeme ho teda prechádzať cyklom for, použiť naň funkciu next()
alebo ho uložiť napríklad do listu (pokiaľ nieje nekonečný – to by nebol dobrý nápad).
>>> for x in generator: ... print(x) ... 1 2 3 >>> generator = fce() >>> next(generator) 1 >>> next(generator) 2 >>> next(generator) 3 >>> next(generator) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Podobne ako pri iterátoroch, pokiaľ sa snažíme z generátoru vytiahnuť ďalší už neexistujúci prvok, dostaneme chybu StopIteration.
Vytvorenie generátoru, ktorý bude postupne vracať čísla fibonacciho postupnosti je o niečo jednoduchšie ako v prípade iterátorov:
# yi_fib.py def fib(): a, b = 0, 1 while True: yield a a, b = b, a + b
Z tohto nám vznikne generátor fibonacciho postupnosti až po nekonečno, my si však vypíšeme len čísla po 6:
>>> from yi_fib import fib >>> fibonacci = fib() >>> x = next(fibonacci) >>> while x<6: ... print(x) ... x = next(fibonacci) ... 0 1 1 2 3 5
Zapisujeme generátor ako výraz
Pre zapísanie generátoru môžeme použiť tzv. Generator Expressions. Tie zapisujeme do jedného riadku, a vyzerajú nejak takto:
>>> ge = (x*(1+x) for x in range(5)) >>> ge <generator object <genexpr> at 0x7f5c7b4fe4c0> >>> for x in ge: ... print(x) ... 0 2 6 12 20
Keď hore uvedený príklad zapíšeme pomocou funkcie s yield
, bude vyzerať takto:
>>> def unforgettablename(): ... for x in range(5): ... yield x*(1+x) ... >>> gen = unforgettablename() >>> gen <generator object unforgettablename at 0x7f5c7b4fe468> >>> for x in gen: ... print(x) ... 0 2 6 12 20
„List Comprehensions“
Keď vytvoríme generátor, jeho prvky sú generované postupne a počas behu, čo nám šetrí veľké množstvo pamäti. Avšak kvôli tomu z generátoru nevieme jednoducho vybrať nejaký prvok, bez toho, aby sme ho najprv previedli napríklad na list. List môžeme ale vygenerovať podobne ako generátor z Generator Expression, len musíme vymeniť zátvorky. Dostaneme tak celý zoznam prvkov, ktorý je vygenerovaný práve v tom okamihu, a negeneruje sa postupne.
>>> ge = (x*(1+x) for x in range(5)) >>> lc = [x*(1+x) for x in range(5)] >>> type(ge) <class 'generator'> >>> type(lc) <class 'list'> >>> ge[2] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'generator' object is not subscriptable >>> lc[2] 6
A príklad toho, ako nám generátor ušetrí kopu pamäti:
>>> ge = (x**x for x in range(100)) >>> lc = [x**x for x in range(100)] >>> from sys import getsizeof # sys.getsizeof() v dokumentácii >>> getsizeof(ge) 88 >>> getsizeof(lc) 912 >>> lc [1, 1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489, …, …]
V ďalších častiach
V budúcej dvanástej časti sa pozrieme na zaujímavú vecičku, ktorou sú dekorátory.
Do ďalších častí následne už plánujem spracovať príklady na použitie niektorých zaujímavých modulov, či už na prístup k databázam alebo vytváranie jednoduchých webových aplikácií.