Перейти к содержимому

Python — Итераторы и генераторы.

URL источник

Автор: Marat Abdrakhmanov | 20.04.2017

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

Итераторы в языке Python

Во многих современных языках программирования используют такие сущности как итераторы. Основное их назначение – это упрощение навигации по элементам объекта, который, как правило, представляет собой некоторую коллекцию (список, словарь и т.п.). Язык Python, в этом случае, не исключение и в нем тоже есть поддержка итераторов. Итератор представляет собой объект перечислитель, который для данного объекта выдает следующий элемент, либо бросает исключение, если элементов больше нет.

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

Приведем несколько примеров, которые помогут лучше понять эту концепцию. Для начала выведем элементы произвольного списка на экран.

&gt;&gt;&gt; num_list = [<strong>1</strong>, <strong>2</strong>, <strong>3</strong>, <strong>4</strong>, <strong>5</strong>]
&gt;&gt;&gt; for i in num_list:
    print(i)
<strong>1</strong>
<strong>2</strong>
<strong>3</strong>
<strong>4</strong>
<strong>5</strong>

Как уже было сказано, объекты, элементы которых можно перебирать в цикле for, содержат в себе объект итератор, для того, чтобы его получить необходимо использовать функцию iter(), а для извлечения следующего элемента из итератора – функцию next().

&gt;&gt;&gt; itr = iter(num_list)
&gt;&gt;&gt; print(next(itr))
<strong>1</strong>
&gt;&gt;&gt; print(next(itr))
<strong>2</strong>
&gt;&gt;&gt; print(next(itr))
<strong>3</strong>
&gt;&gt;&gt; print(next(itr))
<strong>4</strong>
&gt;&gt;&gt; print(next(itr))
<strong>5</strong>
&gt;&gt;&gt; print(next(itr))
Traceback (most recent call last):
  File "&lt;pyshell#12&gt;", line <strong>1</strong>, in &lt;module&gt;
    print(next(itr))
StopIteration

Как видно из приведенного выше примера вызов функции next(itr) каждый раз возвращает следующий элемент из списка, а когда эти элементы заканчиваются, генерируется исключение StopIteration.

Создание собственных итераторов

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

<em>class</em> SimpleIterator:
    <em>def</em> __init__(self, limit):
        self.limit = limit
        self.counter = <strong>0</strong>

    <em>def</em> __next__(self):
        if self.counter &lt; self.limit:
            self.counter += <strong>1</strong>
            return <strong>1</strong>
        else:
            raise StopIteration

s_iter1 = SimpleIterator(<strong>3</strong>)
print(next(s_iter1))
print(next(s_iter1))
print(next(s_iter1))
print(next(s_iter1))

В нашем примере при четвертом вызове функции next() будет выброшено исключение StopIteration. Если мы хотим, чтобы с данным объектом можно было работать в цикле for, то в класс SimpleIterator нужно добавить метод __iter__(), который возвращает итератор, в данном случае этот метод должен возвращать self.

<em>class</em> SimpleIterator:
    <em>def</em> __iter__(self):
        return self

    <em>def</em> __init__(self, limit):
        self.limit = limit
        self.counter = <strong>0</strong>

    <em>def</em> __next__(self):
        if self.counter &lt; self.limit:
            self.counter += <strong>1</strong>
            return <strong>1</strong>
        else:
            raise StopIteration

s_iter2 = SimpleIterator(<strong>5</strong>)
for i in s_iter2:
    print(i)

Генераторы

Генераторы позволяют значительно упростить работу по конструированию итераторов. В предыдущих примерах, для построения итератора и работы с ним, мы создавали отдельный класс. Генератор – это функция, которая будучи вызванной в функции next() возвращает следующий объект согласно алгоритму ее работы. Вместо ключевого слова return в генераторе используется yield. Проще всего работу генератор посмотреть на примере. Напишем функцию, которая генерирует необходимое нам количество единиц.

<em>def</em> simple_generator(val):
   while val &gt; <strong>0</strong>:
       val -= <strong>1</strong>
       yield <strong>1</strong>

gen_iter = simple_generator(<strong>5</strong>)
print(next(gen_iter))
print(next(gen_iter))
print(next(gen_iter))
print(next(gen_iter))
print(next(gen_iter))
print(next(gen_iter))

Данная функция будет работать точно также, как класс SimpleIterator из предыдущего примера.

Ключевым моментом для понимания работы генераторов является то, при вызове yield функция не прекращает свою работу, а “замораживается” до очередной итерации, запускаемой функцией next(). Если вы в своем генераторе, где-то используете ключевое слово return, то дойдя до этого места будет выброшено исключение StopIteration, а если после ключевого слова return поместить какую-либо информацию, то она будет добавлена к описанию StopIteration.

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