Итераторы в Python: зачем они нужны разработчику
Итераторы в Python играют важную роль при работе с коллекциями данных. Благодаря им можно выполнять поэлементную обработку последовательности, что значительно улучшает гибкость кода. Использование итераторов снижает потребление памяти при работе с крупными датасетами.
Сегодня мы узнаем, в каких ситуациях стоит применять итераторы на конкретных примерах кода. Но сначала немного теоретической информации.
Итератор (iterator) — это объект, поддерживающий протокол итерации и реализующий два метода:
Как видите, итераторы помогают нам получать данные и значение в памяти из коллекции по одному элементу. Это очень важно, если мы имеем дело с крупным массивом данных.
Далеко не все объекты в Python могут считаться итераторами, но многие из них допускают итерацию по отношению к себе. Итерируемым объектом можно назвать любой объект, с которым могут использоваться цикл for и метод __iter__(). Обычно это различные списки и их вариации.
Чтобы выполнить итерацию, для начала вам нужна функция iter, чтобы получить этот итератор. При возвращении итератора его можно использовать для поочередного создания элементов через функцию next(). Посмотрите на фрагмент кода, представленный ниже:
my_list = [100, 200, 300] my_iter = iter(my_list) # Получаем итератор print(next(my_iter)) # Вывод 100 print(next(my_iter)) # Вывод 200 print(next(my_iter)) # Вывод 300 # Каждый последующий вызов next() станет причиной появления StopIteration
При ответе на вопрос о необходимости итераторов в языке программирования Python можно упомянуть не менее четырех пунктов:
Итераторы помогают экономить память поскольку при их применении обработка данных происходит поэлементно, то есть они не загружаются все в память. Представим ситуацию, при которой нам необходимо сгенерировать большое количество данных, состоящее из нескольких миллионов объектов. Если просто использовать списки для хранения всех этих объектов, то нам понадобится чрезвычайно много памяти. Тогда как итераторы дают нам возможность хранить при обработке только один элемент.
Вот наглядный пример функции, где числа возвращаются поочередно (генератор):
def number_generator(n): i = 0 while i < n: yield i i += 1 for number in number_generator(1000000): print(number) # Вывод чисел от 0 до 999999
Функция yield здесь работает в том же виде, что и итератор: приостанавливает выполнение функции и возвращает элемент. Во время следующего вызова она продолжит выполнение с того самого места, где ранее остановилась.
Ленивые вычисления предполагают генерацию или обработку данных только в тех ситуациях, когда они действительно необходимы. Благодаря им можно значительно уменьшить расход ресурсов, что немаловажно в случаях, если весь объем данных близок к бесконечности. Перед нами фрагмент кода, демонстрирующий суть ленивых вычислений:
def even_numbers(): num = 0 while True: yield num num += 2 evens = even_numbers() print(next(evens)) # 0 print(next(evens)) # 2 print(next(evens)) # 4
В этом примере генератор выполняет бесконечное возвращение четных чисел. Но вычисление каждого числа происходит лишь во время вызова метода next().
Использовать итераторы особенно полезно при обработке больших файлов. С ними приложению не понадобится грузить в свою память целый файл — его можно обрабатывать построчно. Рассмотрим представленный ниже фрагмент кода:
with open('large_file.txt') as file: for line in file: process(line) # Вызов функции для поочередной обработки каждой строки
Тут файл является итерируемым объектом, каждую его строку можно обработать построчно, до следующего элемента в последовательности, без загрузки в память всего файла.
В Python можно создавать кастомные (личные) итераторы. Они могут пригодиться, если стандартные коллекции не способны удовлетворить какие-либо особые потребности по обработке итерируемого объекта. Здесь нам нужен класс, который реализует методы __iter__() и __next__(). В данном примере мы создадим класс Countdown, он будет обрабатывать каждый итерируемый объект в обратном порядке чисел, с указанного значения до единицы.
class Countdown: def __init__(self, start): self.current = start def __iter__(self): return self def __next__(self): if self.current > 0: num = self.current self.current -= 1 return num else: raise StopIteration # Использование кастомного итератора countdown = Countdown(5) for number in countdown: print(number)
Теперь нужно объяснить, что мы здесь видим:
На выводе получаем
5 4 3 2 1
То есть, нам получилось создать итератор для последующего использования в цикле for и других конструкциях, допускающих итерации.
Генераторы — это особая разновидность итераторов, которые создаются применяя ключевое слово yield. С ними вы можете заметно упростить создание итераторов, потому что тут не требуется явная реализация методов __iter__() и __next__().
Давайте взглянет на пример, в котором генерируются числа от единицы до бесконечности:
def infinite_numbers(): num = 1 while True: yield num num += 1 # Пример применения infinite_gen = infinite_numbers() # Печать первых 5 чисел из бесконечного генератора for _ in range(5): print(next(infinite_gen))
Объяснение:
На выводе получаем первые 5 чисел, с единицы и выше.
Помимо генераторов, синтаксис Python имеет несколько других решений, упрощающих применение итераторов. Например, встроенные функции, такие как map(), filter(), zip(), и enumerate().
numbers = [10, 20, 30, 40, 50] # Увеличиваем каждое число на 1 incremented = list(map(lambda x: x + 1, numbers)) print(incremented) # Вывод: [20, 30, 40, 50, 60] # Фильтруем только четные числа evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # Вывод: [20, 40]
Также вы можете использовать comprehensions (генераторы списков, кортежей, множеств). Они делают код более лаконичным. Вот конкретный пример такого кода:
squared = [x**2 for x in range(10)] print(squared) # Вывод: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Еще один вариант работы с итераторами — это модуль itertools из стандартной библиотеки Python:
import itertools # Создаем бесконечный итератор infinite_count = itertools.count(start=10) # Выводим первые 5 чисел for number in itertools.islice(infinite_count, 5): print(number)
Если же ваш итератор требует хранения состояния, тогда вы можете использовать классы с атрибутами. Они упрощают управление состоянием и делать код более организованным.
class EvenNumbers: def __init__(self, max): self.current = 0 self.max = max def __iter__(self): return self def __next__(self): if self.current < self.max: result = self.current self.current += 2 return result else: raise StopIteration for num in EvenNumbers(10): print(num) # Вывод: 0, 2, 4, 6, 8
Как видите, итераторы в Python обладают целым рядом преимуществ, которые делают их полезными для различных задач при работе с кодом. Они экономят память, выполняют операции только когда действительно требуется итерация, обрабатывают данные при каждом вызове: по частям, а не все сразу. Кроме того, они делают код более чистым и удобочитаемым. Итераторы быстро масштабируются, что позволяет создавать и обрабатывать каждый элемент в последовательности, несмотря на ее размеры.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…