Итераторы и генераторы
Что такое итерируемый объект?
подробнее
Итерируемый объект - это объект, который предоставляет возможность поочерёдного прохода по своим элементам.
Итерируемый объект должен реализовывать один из следующих методов:
__iter__(), который отдаёт итератор.__getitem__(), позволяет получать элементы по индексуПримеры итерируемых объектов: списки, кортежи, строки, словари, множества
Примечание:
Если объект реализует метод
__getitem__()с целочисленными индексами, но не реализует__iter__(), он все равно считается итерируемым. В этом случае Python автоматически создает итератор, который использует__getitem__()для последовательного получения элементов, начиная с индекса 0, пока не возникнет исключениеIndexError.
Что такое итератор?
подробнее
Итератор - это объект, который реализует протокол итератора (методы:
__iter__()и__next__())
__iter__()— возвращает сам итератор (сам объектself)__next__()— возвращает следующий элемент или возбуждает исключениеStopIterationкогда элементы закончились.Итератор запоминает свою текущую позицию в последовательности. После того как мы прошли по всем элементам один раз, итератор “исчерпан” и больше не может возвращать элементы. Всегда можно снова получить итератор и снова пробежаться.
Из коллекции (итерируемого объекта) можно получить итератор вызвав на ней метод
iter(). Пример:iter([1,2,3])
Пример итератора:
class CountDown:
"""Итератор обратного отсчета от заданного числа до 0"""
def __init__(self, start):
self.start = start
def __iter__(self):
return self # Возвращаем сам итератор
def __next__(self):
if self.start < 0:
raise StopIteration # Заканчиваем итерацию
else:
current = self.start
self.start -= 1
return current
# Использование:
countdown = CountDown(3)
for num in countdown:
print(num)
# Вывод: 3, 2, 1, 0
# Повторное использование:
print(list(countdown)) # [] - пустой список, так как итератор исчерпан
Что такое генератор?
подробнее
Генератор - это особый вид итератора. Это функция имеющая оператор
yield.
При вызове генераторной функции возвращается объект-генератор, который реализует протокол итератора под капотом.
Когда интерпретатор встречаетyield, выполнение функции приостанавливается, значение возвращается, а состояние функции сохраняется.
Генераторы нужны для ленивых вычислений (по требованию), это экономит память.
Пример генератора:
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
print(next(gen)) # StopIteration - исключение, так как элементы закончились
# Генератор чисел Фибоначчи
def fibonacci(n):
a, b = 0, 1
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
Что такое генераторное выражение?
подробнее
Генераторное выражение — это выражение, которое создает объект-генератор, аналогично генераторной функции, но записывается в виде выражения, похожего на списковое включение.
Пример:
# Генераторное выражение
gen_expr = (x**2 for x in range(5))
print(gen_expr) # <generator object <genexpr> at 0x...>
print(list(gen_expr)) # [0, 1, 4, 9, 16]
Как работает yield?
подробнее
yield- переводится как “уступать”.
Это оператор, который:
- Возвращает значение из функции-генератора
- Приостанавливает выполнение функции, сохраняя её состояние
- Уступает выполнение вызывающему коду
- При следующем вызове продолжает выполнение с места останова
Зачем нужна конструкция yield from?
подробнее
yield fromиспользуется если в одном генераторе нужно вернуть все значения из другого.
Пример:
def first_gen():
yield 1
yield 11
def second_gen():
yield from first_gen()
yield 2
yield 22
sg = second_gen()
for val in sg:
print(val) # 1, 11, 2, 22
Отличие итератора от генератора
подробнее
Итератор — это объект, реализующий протокол итератора (
__iter__()и__next__()). Его главная задача — предоставить интерфейс для последовательного прохода по элементам.
Генератор — это специальный тип итератора, созданный с помощью функции сyieldили генераторного выражения. Его главная задача — ленивая генерация данных по требованию.Ключевые отличия:
- Генератор — это подмножество итераторов (все генераторы — итераторы, но не наоборот)
- Генераторы по умолчанию ведут себя лениво, итераторы тоже так могут, но зависит от реализации.
- У генераторов более лаконичный код:
- Генераторы автоматически реализуют протокол итератора, а в итераторах надо реализовывать вручную.
- Генераторы автоматически сохраняют состояние между вызовами
next(), а в итераторах надо описывать в__next__().
(в генераторах сохранение между вызовами делает сам Python: послеyieldвыполнение приостанавливается, а все локальные переменные остаются в памяти до следующего вызоваnext())
(в ручном итераторе же мы должны сами придумать, где и как это состояние хранить (атрибуты объекта) и как возобновлять выполнение — Python за нас это не сделает)
Протоколы __iter__ и __next__
подробнее
Чтобы объект был итерируемым он должен реализовывать метод
__iter__().
Чтобы объект был итератором он должен реализовывать методы:__iter__()и__next__().
__iter__(self)- Делает объект «перебираемым» (итерируемым).
- Возвращает: Объект-итератор. (Итератор вернёт
self, итерируемый объект вернёт новый итератор) - Вызывается: В самом начале цикла
forили при вызовеiter(obj).
__next__(self)- Возвращает: Следующий элемент коллекции.
- Когда элементы кончились, метод обязан выбросить исключение
StopIteration. - Вызывается: На каждом шаге цикла или при вызове
next(obj).