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

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))

Вивід:

1
3
5
7
StopIteration

Методи __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))

Вивід:

machine learning
business analytics
java
python
StopIteration

Ключове слово 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))

Вивід:

0
1
4
9
StopIteration

Приклад генератора випадкових чисел:

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)

Вивід:

0
1
1
2
3
5
8
13
21
34

Складний приклад генератора всередині ітератора (найближче просте число та зворотна ітерація)

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