Разница между генераторами Python и итераторами

В чем разница между итераторами и генераторами? Некоторые примеры того, когда вы будете использовать каждый случай, будут полезны.

Ответ 1

iterator является более общей концепцией: любой объект, класс которого имеет next метод (__next__ в Python 3) и метод __iter__, который return self.

Каждый генератор является итератором, но не наоборот. Генератор создается путем вызова функции, которая имеет одно или несколько выражений yield (yield в Python 2.5 и более ранних версиях) и является объектом, который соответствует определению предыдущего iterator в абзаце.

Вы можете захотеть использовать пользовательский итератор, а не генератор, когда вам нужен класс с несколько сложным поведением, поддерживающим состояние, или вы хотите предоставить другие методы, кроме next__iter__ и __init__). Чаще всего достаточно генератора (иногда для достаточно простых нужд - выражения генератора), и его проще кодировать, потому что поддержание состояния (в разумных пределах) в основном "выполняется для вас", когда кадр приостанавливается и возобновляется.

Например, такой генератор, как:

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

или эквивалентный генератор выражения (genexp)

generator = (i*i for i in range(a, b))

потребовалось бы больше кода для сборки в качестве пользовательского итератора:

class Squares(object):
    def __init__(self, start, stop):
       self.start = start
       self.stop = stop
    def __iter__(self): return self
    def next(self): # __next__ in Python 3
       if self.start >= self.stop:
           raise StopIteration
       current = self.start * self.start
       self.start += 1
       return current

iterator = Squares(a, b)

Но, конечно же, с классом Squares вы можете легко предложить дополнительные методы, т.е.

    def current(self):
       return self.start

если у вас есть какая-либо реальная потребность в такой дополнительной функциональности в вашем приложении.

Ответ 2

В чем разница между итераторами и генераторами? Несколько примеров того, когда вы будете использовать каждый случай, были бы полезны.

Итак, итераторы - это объекты, которые имеют __iter__ и __next__ (next в Python 2). Генераторы предоставляют простой встроенный способ создания экземпляров итераторов.

Функция с yield в ней по-прежнему является функцией, которая при вызове возвращает экземпляр объекта-генератора:

def a_function():
    "when called, returns generator object"
    yield

Выражение генератора также возвращает генератор:

a_generator = (i for i in range(0))

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

Генератор - это итератор

В частности, генератор является подтипом итератора.

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Мы можем создать генератор несколькими способами. Очень распространенный и простой способ сделать это с помощью функции.

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

>>> def a_function():
        "just a function definition with yield in it"
        yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function()  # when called
>>> type(a_generator)           # returns a generator
<class 'generator'>

И генератор, опять же, является Итератором:

>>> isinstance(a_generator, collections.Iterator)
True

Итератор - это итеративный

Итератор итеративный,

>>> issubclass(collections.Iterator, collections.Iterable)
True

для которого требуется метод __iter__ который возвращает Iterator:

>>> collections.Iterable()
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__

Некоторыми примерами итераций являются встроенные кортежи, списки, словари, наборы, замороженные наборы, строки, байтовые строки, байтовые массивы, диапазоны и представления памяти:

>>> all(isinstance(element, collections.Iterable) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

Итераторам требуется метод next или __next__

В Python 2:

>>> collections.Iterator()
Traceback (most recent call last):
  File "<pyshell#80>", line 1, in <module>
    collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next

И в Python 3:

>>> collections.Iterator()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__

Мы можем получить итераторы из встроенных объектов (или пользовательских объектов) с помощью функции iter:

>>> all(isinstance(iter(element), collections.Iterator) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

Метод __iter__ вызывается, когда вы __iter__ использовать объект с циклом for. Затем метод __next__ вызывается для объекта итератора, чтобы получить каждый элемент для цикла. После StopIteration как вы исчерпали его, итератор вызывает StopIteration, и в этот момент его нельзя использовать повторно.

Из документации

В разделе Типы генераторов в разделе Типы Итератор Встроенные типы документации:

Генераторы питонов обеспечивают удобный способ реализации протокола итератора. Если метод контейнерных объектов __iter__() реализован как генератор, он автоматически вернет объект итератора (технически объект генератора), предоставляющий __iter__() и next() [ __next__() в Python 3]. Более подробную информацию о генераторах можно найти в документации по выражению yield.

(Акцент добавлен.)

Из этого мы узнаем, что Генераторы - это (удобный) тип Итератора.

Примеры объектов-итераторов

Вы можете создать объект, который реализует протокол Iterator, создав или расширив свой собственный объект.

class Yes(collections.Iterator):

    def __init__(self, stop):
        self.x = 0
        self.stop = stop

    def __iter__(self):
        return self

    def next(self):
        if self.x < self.stop:
            self.x += 1
            return 'yes'
        else:
            # Iterators must raise when done, else considered broken
            raise StopIteration

    __next__ = next # Python 3 compatibility

Но для этого проще просто использовать генератор:

def yes(stop):
    for _ in range(stop):
        yield 'yes'

Или, может быть, проще, выражение генератора (работает аналогично списку пониманий):

yes_expr = ('yes' for _ in range(stop))

Все они могут быть использованы одинаково:

>>> stop = 4             
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop), 
                             ('yes' for _ in range(stop))):
...     print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...     
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes

Заключение

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

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

Наконец, обратите внимание, что генераторы обеспечивают еще больше функциональности в качестве сопрограмм. Я объясню Генераторам вместе с оператором yield подробно мой ответ на вопрос "Что делает ключевое слово yield?".

Ответ 3

итераторов:

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

Генераторы:

Генератор - это функция, которая производит или дает последовательность значений с использованием метода yield.

Каждый метод next() вызывает объект-генератор (для ex: f, как показано ниже), возвращаемый функцией генератора (для примера: foo() в приведенном ниже примере), генерирует следующее значение в последовательности.

Когда вызывается функция генератора, она возвращает объект-генератор, даже не начиная выполнение функции. Когда метод next() вызывается в первый раз, функция начинает выполнение до тех пор, пока не достигнет инструкции yield, которая возвращает полученное значение. Выход отслеживает, то есть запоминает последнее исполнение. И второй вызов next() продолжается от предыдущего значения.

В следующем примере показано взаимодействие между выходом и вызовом следующего метода для объекта генератора.

>>> def foo():
...     print "begin"
...     for i in range(3):
...         print "before yield", i
...         yield i
...         print "after yield", i
...     print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0            # Control is in for loop
0
>>> f.next()
after yield 0             
before yield 1            # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

Ответ 4

Добавление ответа, потому что ни один из существующих ответов специально не затрагивает путаницу в официальной литературе.

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

Либо функцию, либо объект можно назвать "генератором" в зависимости от того, какой исходный документ Python вы читаете. Python glossary говорит о функциях генератора, а Python wiki подразумевает объектов генератора. Python tutorial замечательно удается использовать оба варианта использования в трех предложениях:

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

Первые два предложения определяют генераторы с функциями генератора, а третье предложение идентифицирует их с объектами-генераторами.

Несмотря на всю эту путаницу, можно найти ссылку на язык Python для четкого и окончательного слова:

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

Когда вызывается функция генератора, он возвращает итератор, известный как генератор. Затем этот генератор управляет выполнением функции генератора.

Таким образом, при формальном и точном использовании "генератор" неквалифицированный означает объект-генератор, а не функцию генератора.

Вышеуказанные ссылки относятся к Python 2, но Python 3 language reference говорит то же самое. Тем не менее, глоссарий Python 3 утверждает, что

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

Ответ 5

У каждого есть действительно приятный и подробный ответ с примерами, и я очень ценю это. Я просто хотел дать короткий ответ на несколько строк для людей, которые до сих пор не совсем понятны:

Если вы создаете свой собственный итератор, это немного связано - у вас есть для создания класса и, по крайней мере, реализации iter и следующих методов. Но что, если вы не хотите преодолевать эту проблему и хотите быстро создать итератор. К счастью, Python обеспечивает короткий путь к определению итератора. Все, что вам нужно сделать, это определить функцию с хотя бы одним вызовом, и теперь, когда вы вызываете эту функцию, она вернет " что-то", которая будет действовать как итератор (вы можете вызвать следующий метод и использовать он в цикле for). Это что-то имеет имя в Python под названием Generator

Надеюсь, что это немного пояснит.

Ответ 6

Функция генератора, объект генератора, генератор

A Функция генератора похожа на регулярную функцию в Python, но содержит один или несколько операторов yield. Функции генератора - отличный инструмент для создания объектов Iterator как можно проще. Объект Iterator, возвращаемый функцией генератора, также называется Генератор-объект или Генератор.

В этом примере я создал функцию Generator, которая возвращает объект Generator <generator object fib at 0x01342480>. Подобно другим итераторам, объекты Generator могут использоваться в цикле for или со встроенной функцией next(), которая возвращает следующее значение из генератора.

def fib(max):
    a, b = 0, 1
    for i in range(max):
        yield a
        a, b = b, a + b
print(fib(10))             #<generator object fib at 0x01342480>

for i in fib(10):
    print(i)               # 0 1 1 2 3 5 8 13 21 34


print(next(myfib))         #0
print(next(myfib))         #1
print(next(myfib))         #1
print(next(myfib))         #2

Таким образом, функция-генератор является самым простым способом создания объекта Iterator.

Итератор

Каждый генераторный объект является итератором, но не наоборот. Пользовательский объект итератора может быть создан, если его класс реализует метод __iter__ и __next__ (также называемый протоколом итератора).

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

class Fib:
    def __init__(self,max):
        self.current=0
        self.next=1
        self.max=max
        self.count=0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count>self.max:
            raise StopIteration
        else:
            self.current,self.next=self.next,(self.current+self.next)
            self.count+=1
            return self.next-self.current

    def __str__(self):
        return "Generator object"

itobj=Fib(4)
print(itobj)               #Generator object

for i in Fib(4):  
    print(i)               #0 1 1 2

print(next(itobj))         #0
print(next(itobj))         #1
print(next(itobj))         #1

Ответ 7

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

Тем не менее, мой личный ответ на первый вопрос будет таким: итератор имеет __iter__ метод __iter__, типичные итераторы имеют __next__ метод __next__, у генераторов есть и __iter__ и __next__ и дополнительное close.

На второй вопрос мой личный ответ будет таким: в общедоступном интерфейсе я склоняюсь к тому, чтобы отдавать предпочтение генераторам, так как он более устойчив: метод close обеспечивает большую сочетаемость с yield from. Локально, я могу использовать итераторы, но только если это плоская и простая структура (итераторы не сочиняются легко) и если есть основания полагать, что последовательность довольно короткая, особенно если ее можно остановить до того, как она достигнет конца. Я склонен рассматривать итераторы как низкоуровневый примитив, за исключением литералов.

Для вопросов управления потоком генераторы являются столь же важной концепцией, как и обещания: и абстрактные, и составные.

Ответ 8

Вы можете сравнить оба подхода для одних и тех же данных:

def myGeneratorList(n):
    for i in range(n):
        yield i

def myIterableList(n):
    ll = n*[None]
    for i in range(n):
        ll[i] = i
    return ll

# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
    print("{} {}".format(i1, i2))

# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

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

Ответ 9

Примеры от Неда Батчелдера настоятельно рекомендуются для итераторов и генераторов.

Метод без генераторов, которые делают что-то с четными числами

def evens(stream):
   them = []
   for n in stream:
      if n % 2 == 0:
         them.append(n)
   return them

в то время как с помощью генератора

def evens(stream):
    for n in stream:
        if n % 2 == 0:
            yield n
  • Нам не нужен ни список, ни выражение о return
  • Эффективен для потока большой/бесконечной длины... он просто гуляет и дает значение

Вызов метода evens (генератора) происходит как обычно

num = [...]
for n in evens(num):
   do_smth(n)
  • Генератор также используется для разрыва двойной циклы

Итератор

Книга, полная страниц, является итеративной, а закладка - итератором.

и эта закладка не имеет ничего общего, кроме как двигаться next

litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration  (Exception) as we got end of the iterator

Чтобы использовать генератор... нам нужна функция

Для использования итератора... нам нужно next и iter

Как уже было сказано:

Генератор - это итератор

Вся выгода от Iterator:

Храните один элемент раз в памяти