Создавать классы, чтобы прописать в них какую-то функциональность по перебору объектов, довольно интересно, но слишком многословно: нужно объявить класс как минимум с двумя методами, обычно с тремя.

Естественно, Python предлагает более простой способ реализовать функциональность классов-итераторов, если их единственной целью является перебор каких-то элементов. Таким способом являются генераторы.

<aside> 💡 Генераторы — функции, которые ведут себя как итераторы, то есть позволяют перебрать несколько значений.

</aside>

Основным отличием генератора от обычной функции является использование ключевого слова yield. Это ключевое слово позволяет вернуть значение, как и return, но при этом не завершает работу функции, а приостанавливает. При повторном вызове приостановленной функции она начнёт работу со строчки, следующей за yield.

Второе отличие — функция-генератор возвращает не какое-то конкретное значение, а итератор, по которому, например, можно пройтись с помощью функции next().

Перепишем пример с генерацией степеней двойки, используя генератор:

# Импортируем класс Iterator для аннотации
from collections.abc import Iterator

# Функция-генератор для степеней двойки
def pow_two(max_pow: int) -> Iterator:
    cur_pow = 0

    while cur_pow <= max_pow:
        # Вот тут и происходит магия: когда функция
        # будет вызвана повторно, она начнётся не с начала,
        # а со строчки после yield
        yield 2 ** cur_pow
        cur_pow += 1

# Выводим степени двойки от 2^0 до 2^10
for i in pow_two(10):
    print(i)

По сравнению с классом код стал меньше и лучше читается.

<aside> 👉 Если использовать генератор как объект, то по нему можно будет пройтись только один раз, для повторной итерации нужно будет создать объект заново.

</aside>

Это значит, что следующий код выведет степени двойки только один раз:

from collections.abc import Iterator

def pow_two(max_pow: int) -> Iterator:
    cur_pow = 0

    while cur_pow <= max_pow:
        yield 2 ** cur_pow
        cur_pow += 1

pt = pow_two(10)

# Здесь ещё есть, что перебирать
for i in pt:
    print(i)

# А к этому моменту перебор уже закончился
for i in pt:
    print(i)