Iterators, Generators and Iterables
Ітеровані об'єкти (Iterables)
Списки, кортежі, словники, множини, рядки — це ІТЕРОВАНІ об'єкти, тобто вони реалізують методи __iterate__ та __next__, і ви можете проходити по їхніх елементах.
Ітератор (Iterator) — це частина ітерованого об'єкта, яку ви створюєте за допомогою методу iter().
Ітератори
Ви можете створити ІТЕРАТОР з ІТЕРОВАНИХ об'єктів (вони реалізують відповідні методи):
fruits_tuple = ("melon", "strawberry", "peach")
fruits_iter = iter(fruits_tuple)
print(next(fruits_iter))
print(next(fruits_iter))
print(next(fruits_iter))
Метод next() може використовуватися тільки з ітераторами. Він використовується для отримання наступного значення ітерованого об'єкта, коли ми використовуємо функцію print() з next().
Як ітеровані об'єкти, так і ітератори можуть використовувати метод iter(), який повертає ітератор.
Цикл for фактично створює ітератор і реалізує метод next() для кожної ітерації.


В цьому випадку FOR — це те саме, що WHILE
Створення ітератора
class Sequence:
def __init__(self):
self.num = 1
def __iter__(self):
return self
def __next__(self):
value = self.num
if value >= 9:
raise StopIteration
self.num += 2
return value
ite = Sequence()
print(next(ite))
print(next(ite))
print(next(ite))
print(next(ite))
print(next(ite))
Вивід:
Методи __next__() та __iter__() роблять цей клас ітератором. Метод __iter__() отримує та ініціалізує ітератор. Клас Sequence є ітератором, тому він повертає сам себе.
Метод __next__() отримує поточне значення з ітератора та переходить до наступного стану при наступному виклику. Ми збільшуємо змінну num на 2 для виводу непарних чисел. Тут ми також вказуємо, коли послідовність повинна викликати помилку StopIteration.

коли ви викликаєте iter(EachSecondElement_object), викликається метод iter і self.current_element стає 1
Генератори
Генератори надають кращий спосіб створення ітераторів у Python. Ви можете визначити звичайну функцію, використовуючи оператор yield замість оператора return. Наприклад:
def subjects():
yield "machine learning"
yield "business analytics"
yield "java"
yield "python"
subjects_library = subjects()
print(next(subjects_library))
print(next(subjects_library))
print(next(subjects_library))
print(next(subjects_library))
print(next(subjects_library))
Вивід:
Ключове слово yield схоже на оператор return, але з додатковою функціональністю — воно фактично запам'ятовує стан функції. Тому наступного разу, коли генератор буде викликаний, він не почне з початку, а з того місця, де був останній виклик.
Генераторні вирази краще підходять для створення послідовностей та є ефективнішими з точки зору пам'яті. Їх часто порівнюють зі списковими виразами (list comprehension), оскільки синтаксис для обох подібний:
gen = (x ** 2 for x in range(4))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
Вивід:
Приклад генератора випадкових чисел:
def dice_player(n_rounds: int) -> Generator[int, None, None]:
for i in range(n_rounds):
yield random.randint(1, 6)
Використання:
player = dice_player(3)
print(next(player)) # 3
print(next(player)) # 1
print(next(player)) # 5
print(next(player)) # StopIteration

Генератор Фібоначчі:
def fibonacci_generator(num: int) -> int:
iteration = 0
prev_num = 0
next_num = 1
while True:
if iteration == num:
break
yield prev_num
prev_num, next_num = next_num, prev_num + next_num
iteration += 1
fib = fibonacci_generator(10)
for i in fib:
print(i)
Вивід:
Складний приклад генератора всередині ітератора (найближче просте число та зворотна ітерація)
from __future__ import annotations
from typing import Generator
class LowerPrime:
def __init__(self, number: int) -> None:
self.number = number
def __iter__(self) -> LowerPrime:
self.iteration = 0
self.prime_numbers_generator = LowerPrime.prime_generator()
self.closest_prime = 0
self.previous = 0
self.prime_list = []
for prime in self.prime_numbers_generator:
if prime >= self.number:
self.closest_prime = self.previous
break
self.prime_list.append(prime)
self.iteration = len(self.prime_list)
self.previous = prime
return self
def __next__(self) -> int:
if self.iteration == 0:
raise StopIteration
while self.iteration >= 0:
self.iteration -= 1
return self.prime_list[self.iteration]
@staticmethod
def prime_generator() -> Generator[int, None, None]:
prime_number = 2
while True:
yield prime_number
while True:
prime_number += 1
if LowerPrime.__is_prime__(prime_number):
break
@staticmethod def __is_prime__(number: int) -> bool:
is_prime = True
for i in range(2, number):
if number % i == 0:
return not is_prime
return is_prime