Язык программирования Python

Что нужно знать об итераторах в Python?

Часто на собеседованиях при приеме на работу задают вопрос о Python Iterator. Вам стоит знать, для чего он используется и какие преимущества дает в повседневном программировании.

Итерация, итерабельность и итераторы объяснены в одном месте

Эти три названия вызывают немалую путаницу среди программистов. Пришло время развеять сомнения и описать, что представляет собой каждый из них.

Итерация – это процесс, в котором набор команд/инструкций повторяется конечное число раз или до тех пор, пока не будет выполнено условие. Мы выполняем итерацию внутри циклов for или while.

Iterable – это все, над чем можно выполнять итерации. Iterable имеет метод, который возвращает так называемый итератор. Этим методом является __iter__().

Например, список в Python – это iterable:

numbers = [2, 4, 8] # Iterable
Code language: PHP (php)

Итератор – это объект, реализующий протокол – вот сюрприз – iterator:).

Iterator – это объект, выполняющий итерации. Кроме того, он хранит состояние итерации, т.е. знает, на каком элементе объекта iterable мы сейчас находимся.

Метод для получения следующего объекта из коллекции – __next__().

Давайте напишем собственный итератор на языке python

Пусть это будет итератор, который возвращает последовательность Фибоначчи – так хорошо известную в процессах подбора персонала.

from collections.abc import Iterable, Iterator class FibonacciIterable(Iterable): def __init__(self, limit): self.limit = limit def __iter__(self): return FibonacciIterator(self.limit) class FibonacciIterator(Iterator): def __init__(self, limit): self.iteration = 0 self.a = 0 self.b = 1 self.limit = limit def __next__(self): result = self.a if self.limit < self.iteration: raise StopIteration self.a, self.b = self.b, self.a + self.b self.iteration += 1 return result
Code language: HTML, XML (xml)

Определив таким образом iterable и iterator, мы можем свободно итерировать наш набор данных и, например, “выплевывать” на экран значение каждой итерации.

fibonacci = FibonacciIterable(5) for value in fibonacci: print(value) # 0, 1, 1, 2, 3, 5
Code language: PHP (php)

Рассматривая FibonacciIterable, мы видим, что он реализует два метода:

  • __init__ в инициализаторе передает значение, для которого мы хотим вычислить последовательность Фибоначчи.
  • __iter__, следуя протоколу итераторов, этот метод возвращает FibonacciIterator. Обратите внимание, что мы передаем значение итератору, которое используется для определения количества итераций. Мы можем это сделать, потому что именно итератор хранит состояние итерации.

Класс FibonacciIterator ориентирован на возврат последовательных значений:

  • В инициализаторе __init__ мы присваиваем начальные значения, необходимые для вычисления последовательности Фибоначчи. Кроме того, мы реализуем поле iteration, которое отвечает за хранение состояния итерации.
  • __next__ – это второй метод протокола итератора. Его задача – вернуть следующее (отсюда и название next) значение. Важно, чтобы для завершения итерации в Python возникало исключение StopIteration. Циклы типа ‘for in ‘ могут перехватить это исключение и остановить дальнейшую итерацию. В нашем примере мы выбрасываем это исключение, когда превышаем количество итераций, указанное в параметре при создании FibonacciIterable. Важным преимуществом итератора является то, что он вычисляет значение на лету, что экономит ресурсы. Интересно то, что этот метод принимает параметр по умолчанию, который будет возвращен, когда итератор будет исчерпан. Поэтому мы возвращаем значение по умолчанию вместо того, чтобы выбросить исключение StopIteration.

Мы написали наш код итератора в двух отдельных классах, чтобы четко показать различие между iterable и iterator.

Кроме того, обратите внимание, что мы наследуем от абстрактных классов Iterator и Iterable из встроенной библиотеки, чтобы принудительно реализовать необходимые методы.

Стоит знать, что встроенный абстрактный класс Iterator наследуется от Iterable, поэтому он также содержит метод iter.

В большинстве случаев вы можете реализовать оба метода (__iter__, __next__) в одном классе. Это делает наш код еще короче.

from collections.abc import Iterator class Fibonacci(Iterator): def __init__(self, limit): self.iteration = 0 self.a = 0 self.b = 1 self.limit = limit def __iter__(self): return self def __next__(self): result = self.a if self.limit < self.iteration: raise StopIteration self.a, self.b = self.b, self.a + self.b self.iteration += 1 return result
Code language: HTML, XML (xml)

Класс Fibonacci реализует оба ключевых метода. Поскольку метод __iter__ должен возвращать итератор – а он им является, – то он возвращает сам себя. Сначала это может показаться немного нечитабельным.

ITER VS ITER()

Обратите внимание, что методы с двойным подчеркиванием (так называемые методы Дандера или магические методы) не должны выполняться непосредственно на объектах. Не без причины. В мире Python такое обозначение означает a’la приватный элемент объекта.

На самом деле, вы знаете это очень хорошо, потому что когда вы хотите проверить размер списка, вы набираете “len(your_list)”, а не “your_list.__len__()”.

Почему бы не обратиться непосредственно к магическим методам? Прежде всего, из-за худшей производительности. Такой вызов каждый раз ищет метод в пространстве имен объекта. Кроме того, когда мы определили метод __getattribute__ в классе, он также будет вызываться каждый раз.

С другой стороны, запись len(object) вызывает метод __len__ на объекте напрямую, без поиска в пространстве имен объекта. Это рекомендуемый способ вызова таких методов, и он также применим к методам протокола итератора: iter() и next().

Как работает цикл for “снизу”

Имея уже свой собственный итератор, я покажу вам, как его использовать.

Чаще всего для просмотра коллекции мы используем цикл for. Поэтому для нашего примера это будет выглядеть примерно так:

fibonacci = Fibonacci(5) for value in fibonacci: print(value) # 0, 1, 1, 2, 3, 5
Code language: PHP (php)

Но как работает цикл for? Обращаясь к методам из протокола итератора:

  • вызывает метод __iter__ класса Fibonacci, который возвращает итератор
  • и затем вызывает итератор __next__ до тех пор, пока он не выдаст исключение StopIteration

Зная это, мы можем предположить, что цикл for работает так, как показано ниже:

fibonacci = Fibonacci(3) iterator = iter(fibonacci) # возвращает объект итератора while True: try: value = next(iterator) # возвращает другой элемент print(value) except StopIteration: break
Code language: PHP (php)

Довольно просто, верно?

Стоит отметить, что после того, как мы выполним итерацию по всей последовательности и итератор выбросит исключение, мы больше не сможем “сбросить” и начать сначала. Для повторной итерации нам нужно создать новый объект iterator.

Резюме

Итераторы позволяют нам проходить через последовательности объектов. Они возвращают последовательные значения лениво, что экономит ресурсы (память).

Для создания собственного итератора нам нужно написать собственный класс, который соответствует протоколу итератора. Итератор Python, который был итерирован один раз, не может быть сброшен и итерирован второй раз.

Концепция итераторов похожа на концепцию генераторов. Однако генератор является подклассом итератора. Об этом я напишу в следующем посте.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *