přejít na obsah přejít na navigaci

Linux E X P R E S, Python 3 (11): iterátory a generátory

Python 3 (11): iterátory a generátory

python.png

V dnešnej časti si ukážeme čo to sú a ako fungujú iterátory či už jednotlivých iterovateľných dátových typov alebo si ich vyrobíme z vlastnej triedy. Potom si povieme niečo o generátoroch, ktoré si porovnáme s iterátormi. Nakoniec si spomenieme aj „Generator Expressions“ a „List Comprehensions“.


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í.

Nahoru

Příspěvky

Python 3 (11): iterátory a generátory
jnet 14. 12. 2015, 10:35:04
Odpovědět  Odkaz 
Toto byl opravdu povedený díl seriálu. Když jsem se iterátory v Pythonu zabýval, tak jsem si z mnoha internetových zdrojů musel nejdříve poskládat odpovědi na otázky proč se to vlastně zavedlo, jak se to používá a jak se ve vlastních programech využijí jejich výhody. Pokud někdo chce začít s Pythonem přes Diving in Python3, tak bych mu nejprve doporučil tento článek a pak na příkladech z této knihy už mu bude vše jasné.

Docela jsem zvědav na dekorátory, jsou už v C++ a i tam je taková "vyšší dívčí" tak jsem zvědav, jak to autor pojme ve stylu "Python pro dělníky a mistry", přeji mu v tom mnoho úspěchů.
Python 3 (11): iterátory a generátory
Wrunx 15. 12. 2015, 19:07:12
Odpovědět  Odkaz 
Při iteraci s "next" nemusíme po vyčerpání konečného iterátoru upadat nezbytně do chybového hlášení. Nekomu může třeba víc vyhovovat:

>>> a=iter("jo")
>>> next(a,"hotovo")
'j'
>>> next(a,"hotovo")
'o'
>>> next(a,"hotovo")
'hotovo'

Samosebou místo "hotovo" tam může být třeba None nebo tak...

Odpovědět

Nejsou podporovány žádné značky, komentáře jsou jen čistě textové. Více o diskuzích a pravidlech najdete v nápovědě.
Diskuzi můžete sledovat pomocí RSS kanálu rss



 
 

Top články z OpenOffice.cz