5.1 Iterable и Iterator
Как вы уже знаете, итераторы — это объекты, которые реализуют протокол итератора, позволяющий поочередно получать элементы из коллекции. Итераторы широко используются в Python для перебора элементов последовательностей таких как списки, кортежи и строки.
Рассмотрим, как устроены итераторы и как их использовать.
Итерируемый объект (Iterable)
Чтобы по объекту можно было пройтись с помощью цикла for, он должен быть итерируемым – Iterable. Это значит, что наш объект должен реализовать метод __iter__(), который возвращает объект-итератор.
Объект-итератор (Iterator)
Это специальный объект, который имеет функцию __next__() для отдачи следующего элемента последовательности. Когда элементы заканчиваются, метод __next__() вызывает исключение StopIteration как знак прекращения итерации.
Итератор также должен реализовать метод __iter__(), который возвращает сам итератор.
Пример с использованием встроенных функций Python
В этом примере список numbers является итерируемым объектом. Мы получаем итератор с помощью функции iter() и используем функцию next() для перебора элементов до тех пор, пока не будет вызвано исключение StopIteration.
# Итерируемый объект
numbers = [1, 2, 3, 4, 5]
# Получаем итератор из итерируемого объекта
iterator = iter(numbers)
# Используем итератор для перебора элементов
try:
while True:
number = next(iterator)
print(number)
except StopIteration:
pass
Именно это и происходит, когда вы пишете код типа:
# Итерируемый объект
numbers = [1, 2, 3, 4, 5]
for number in numbers:
print(number)
5.2 Суть итератора
Итератор — это некий объект, который помогает нам поочередно обойти группу элементов. Реализации его могут быть самые разные. Давайте напишем свой класс, где реализуем все требования, которые предъявляются к итератору.
Шаг 1. Для начала создадим свой класс
Пусть он поочередно возвращает числа от start до end
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
Шаг 2. Поддержка функции __iter__
Теперь нам нужно добавить ему функцию __iter__, которая будет возвращать объект-итератор, у которого будет вызываться функция __next()__. Мы будем возвращать ссылку на свой же объект – это не запрещено.
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
Шаг 3. Поддержка функции __next__
Теперь нужно добавить к нашему-объекту итератору функцию __next__, которая будет возвращать очередной элемент нашего списка. Мы просто будем использовать переменную current:
def __next__(self):
current = self.current
self.current += 1
return current
Шаг 4. Остановка итератора
Если итератор вернул уже все значения, которые планировал, он должен кинуть исключение StopIteration. Давайте немного подправим нашу последнюю функцию:
def __next__(self):
if self.current >= self.end: raise StopIteration
current = self.current
self.current += 1
return current
Отлично. Теперь можно пользоваться нашим итератором. Вот пример всего нашего кода:
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
current = self.current
self.current += 1
return current
# Создаем экземпляр пользовательского итератора
my_iter = MyIterator(1, 5)
# Используем итератор для перебора элементов
for num in my_iter:
print(num)
5.3 Правильный итератор
Чем плох итератор из предыдущего примера? Да, это итератор, он рабочий, но он слишком примитивный. С его помощью нельзя идти по одной и той же коллекции элементов одновременно разными итераторами.
Более правильно было бы написать код, который возвращал не ссылку на себя в методе __iter__, а отдельный объект, который бы уже правильно выдавал все элементы.
Пример:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
return MyIterator(self.data)
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
item = self.data[self.index]
self.index += 1
return item
# Использование
my_iterable = MyIterable([1, 2, 3, 4])
for item in my_iterable:
print(item)
В этом примере у нас есть два класса — в первый передается коллекция, по которой мы будем идти итератором. А второй — это и есть итератор, который возвращает элементы коллекции в методе next(). Он тоже довольно простой, но именно так нужно добавлять итераторы в ваши классы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ