Рубріки: Основы

Итераторы в Python: зачем они нужны разработчику

Дмитро Сімагін

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

Сегодня мы узнаем, в каких ситуациях стоит применять итераторы на конкретных примерах кода. Но сначала немного теоретической информации. 

Что такое итераторы?

Итератор (iterator) — это объект, поддерживающий протокол итерации и реализующий два метода: 

  • __iter__(). Если вы вызываете iter() на объекте или используете его с помощью цикла for, то Python хочет вызвать метод __iter__() этого объекта. Если объект итерабельный, то он возвращает итератор.
  • __next__(). Этот метод используется для возвращения следующего элемента из последовательности при работе с итераторами. Каждый раз, когда вызывается метод __next__() на итераторе, он должен возвращать следующий элемент. Если после обхода элементов их для возврата уже нет, то метод возвращает исключение StopIteration, чтобы сообщить об окончании итерации.

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

Итерируемые объекты и итераторы

Далеко не все объекты в 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 можно упомянуть не менее четырех пунктов: 

  • Экономия памяти.
  • Ленивые вычисления (Lazy Evaluation).
  • Обработка файлов и потоков данных.
  • Создание кастомных итераторов.

 

Экономия памяти

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

Вот наглядный пример функции, где числа возвращаются поочередно (генератор):

 def number_generator(n):
    i = 0
    while i < n:
        yield i
        i += 1

for number in number_generator(1000000):
    print(number)  # Вывод чисел от 0 до 999999

 

Функция yield здесь работает в том же виде, что и итератор: приостанавливает выполнение функции и возвращает элемент. Во время следующего вызова она продолжит выполнение с того самого места, где ранее остановилась. 

Ленивые вычисления (Lazy Evaluation)

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

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)

 

Теперь нужно объяснить, что мы здесь видим:

  • В классе Countdown конструктор __init__ принимает начальное значение start и сохраняет его в атрибуте current.
  • В методе __iter__ возвращается сам объект, что делает его итератором.
  • Метод __next__ проверяет, больше ли текущее значение 0. Если да, то возвращает текущее значение и уменьшает его на 1.
  • Если значение меньше или равно 0, выбрасывает исключение StopIteration, что сообщает об окончании итерации.

 

На выводе получаем

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

 

Объяснение: 

  • while True: Этот цикл делает генератор бесконечным.
  • yield num: Функция возвращает текущее значение num, но не завершает выполнение, позволяя продолжить с этого места на следующем вызове.
  • next(): Вручную вызывает следующую итерацию генератора.

 

На выводе получаем первые 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), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…

21.11.2024

Что такое PWA приложение? Зачем необходимо прогрессивное веб-приложение

Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…

19.11.2024

Как создать игру на телефоне: программирование с помощью конструктора

Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…

17.11.2024

Google Bard: эффективный аналог ChatGPT

В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…

14.11.2024

Скрипт и программирование: что это такое простыми словами

Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…

12.11.2024

Дедлайн в разработке: что это такое простыми словами

Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…

11.11.2024