Перейти до змісту

Генератори

Генератори — дуже потужний механізм в Python початково створений для спрощення написання ітераторів, але за допомогою якого можна вирішувати і деякі інші задачі, зокрема написання асинхроного коду.

Термін генератор (generator), в залежності від контекста, може означати або функцію-генератор, або ітератор генератора.

Функція-генератор (generator function) — це функція, яка повертає спеціальний ітератор генератора (generator iterator) або (інша назва) об'єкт-генератор (generator object).

Така функція характеризується наявністю ключового слова yield. Якщо в функції присутнє ключове слово yield, тоді Python створить не об'єкт функції, а об'єкт функції-генератора.

Коли викликається функція-генератор, її код, на відміну звичайним функціям, насправді не виконується. Замість цього функція-генератор повертає спеціальний об'єкт генератора, який також є ітератором. А вже коли виконується ітерування по цьому об'єкту, вже тоді виконується код функції-генератора, причому виконується як би "порціями".

Код функції генератора виконується доти, доки не зустрінеться ключове слово yield. Інструкція yield як би ставить на паузу виконання і заморожує стан функції-генератора і повертає чергове значення генератора (або ж ітератора, у данному випадку це одне й те ж саме). Після наступного виклика __next__() функція-генератор продовжує своє виконання з того місця, де його було призупинено.

Коли виконання функції-генератора завершується (за допомогою ключового слова return або ж при досягненні кінця функції), піднімається виняток StopIteration.

Перевага використання генераторів для створення ітераторів полягає у тому, що магічні методи __iter__ і __next__ для генераторів створюються автоматично. Ми у функції-генераторі просто описуємо логіку (алгоритм) для отримання чергового значення.

Давайте розглянемо на прикладі. Створимо найпростіший генератор:

>>> def gen():
...     yield 'Hello'
...     yield 'world'
...
...
>>>

Спершу може здатись, що це звичайнісінька собі функція:

>>> gen
<function gen at 0x0000017E2A663E18>
>>>

Але якщо ми викличемо таку функцію, то вона поверне об'єкт генератор:

>>> g = gen()
>>> g
<generator object gen at 0x0000017E2A72D308>
>>>

У цього об'єкта є метод __iter__(), отже це ітерабельний об'єкт:

>>> g.__iter__
<method-wrapper '__iter__' of generator object at 0x0000017E2A72D308>
>>>

Крім того є і метод __next__(), отже це і ітератор також:

>>> g.__next__
<method-wrapper '__next__' of generator object at 0x0000017E2A72D308>
>>>

Окей, тоді давайте проітеруємось по ньому:

>>> next(g)
'Hello'
>>> next(g)
'world'
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

Проітеруємось за допомогою for:

>>> for i in gen(): print(i)
...
Hello
world
>>>

Лічилочка

>>> def counting_out(max_count):
...     count = 1
...     while count <= max_count:
...             yield count
...             count += 1
...
>>> for i in counting_out(5): print(i)
...
1
2
3
4
5
>>>

Фібоначчі-генератор

Створимо генератор, який буде повертати нам числа з послідовності Фібоначчі довжиною max_count:

>>> def fibo_generator(max_count):
...     if max_count >= 1:
...             yield 1
...     if max_count >= 2:
...             yield 1
...     first, second = 1, 2
...     count = 3
...     while count <= max_count:
...             yield second
...             first, second = second, first + second
...             count += 1
...
>>> f_gen = fibo_generator(10)
>>> list(f_gen)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>>

Порівняйте вищенаведений код з усіма реалізаціями знаходження чисел Фібоначчі, які ми виконували раніше. Як щодо отримати сто тисяч числел Фібоначчі?

Ну насправді можна написати ще простіше:

def fibo_generator(max_count):
    first, second = 0, 1
    for _ in range(max_count):
        yield second
        first, second = second, first + second
Back to top