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

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

15 ответов

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

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

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

Итак, итераторы - это объекты, которые имеют __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__ метод, который возвращает итератор:

>>> 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__ Метод вызывается, когда вы пытаетесь использовать объект с циклом for. Тогда __next__ метод вызывается для объекта итератора, чтобы получить каждый элемент для цикла. Итератор поднимает StopIteration когда вы исчерпали его, и он не может быть повторно использован в этот момент.

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

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

Генераторы Python предоставляют удобный способ реализации протокола итератора. Если контейнерный объект __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

Заключение

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

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

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

Итераторы:

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

Генераторы:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Чтобы использовать Iterator ... нам нужно next а также iter

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

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

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

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

Шпаргалка из 4 строк без кода:

      A generator function is a function with yield in it.

A generator expression is like a list comprehension. It uses "()" vs "[]"

A generator object (often called 'a generator') is returned by both above.

A generator is also a subtype of iterator.

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

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

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

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

На этот вопрос сложно ответить без двух других концепций: iterable а также iterator protocol.

  1. В чем разница между iterator а также iterable? Концептуально вы перебираетеiterable с помощью соответствующих iterator. Есть несколько отличий, которые помогут отличитьiterator а также iterable на практике:
    • Одно отличие в том, что iterator имеет __next__ метод iterable не.
    • Еще одно отличие - оба они содержат __iter__метод. В случаеiterableон возвращает соответствующий итератор. В случаеiteratorон возвращается сам. Это может помочь отличитьiterator а также iterable на практике.
>>> x = [1, 2, 3]
>>> dir(x) 
[... __iter__ ...]
>>> x_iter = iter(x)
>>> dir(x_iter)
[... __iter__ ... __next__ ...]
>>> type(x_iter)
list_iterator
  1. Что iterables в python? list, string, range и т.д. Что такое iterators? enumerate, zip, reversedи т.д. Мы можем проверить это, используя описанный выше подход. Это сбивает с толку. Наверное, было бы проще, если бы у нас был только один тип. Есть ли разница междуrange а также zip? Одна из причин сделать это -rangeимеет много дополнительных функций - мы можем проиндексировать его или проверить, содержит ли он какое-то число и т. д. (подробности см. здесь).

  2. Как мы можем создать iteratorсами? Теоретически мы можем реализоватьIterator Protocol(см. здесь). Нам нужно написать__next__ а также __iter__ методы и поднять StopIterationисключение и так далее (см. ответ Алекса Мартелли для примера и возможной мотивации, см. также здесь). Но на практике мы используем генераторы. Похоже, что это основной метод созданияiterators в python.

Я могу привести еще несколько интересных примеров, которые показывают несколько запутанное использование этих концепций на практике:

  • в keras у нас есть tf.keras.preprocessing.image.ImageDataGenerator; в этом классе нет__next__ а также __iter__методы; так что это не итератор (или генератор);
  • если вы назовете это flow_from_dataframe() метод, который вы получите DataFrameIteratorу которого есть эти методы; но это не реализуетStopIteration (что не характерно для встроенных итераторов в python); в документации мы можем прочитать, что "ADataFrameIterator дающие кортежи (x, y)" - опять запутанное использование терминологии;
  • у нас также есть Sequence класс в keras и это настраиваемая реализация функции генератора (обычные генераторы не подходят для многопоточности), но не реализует __next__ а также __iter__, скорее это оболочка вокруг генераторов (она использует yield заявление);

Этот поток подробно описывает все различия между ними, но хотел добавить кое-что о концептуальной разнице между ними:

[...] итератор , как определено в книге GoF , извлекает элементы из коллекции , в то время как генератор может создавать элементы «из воздуха» . Вот почему генератор последовательности Фибоначчи является распространенным примером: бесконечный ряд чисел не может храниться в коллекции.

Рамальо, Лучано. Свободный Python (стр. 415). О'Рейли Медиа. Киндл издание.

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

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

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

В этом примере я создал функцию 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__ метод (также называемый протоколом итератора).

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

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

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

Объект итератора — это объект, который реализует протокол итератора — набор правил. В этом случае у него должны быть хотя бы эти два метода: и . метод — это функция, которая возвращает новое значение. Метод возвращает объект итератора. В более сложном объекте может быть отдельный итератор, но в более простом случае он возвращает сам объект (обычно).

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

Если вы хотите перебрать любую коллекцию, вам нужно будет использовать ее итератор:

      things_iterator = iter(things)
for i in things_iterator:
    print(i)

Однако Python автоматически использует итератор, поэтому приведенный выше пример вы никогда не увидите. Вместо этого вы пишете:

      for i in things:
    print(i)

Самому писать итератор может быть утомительно, поэтому у Python есть более простая альтернатива: функция-генератор. Генераторная функция не является обычной функцией. Вместо того, чтобы выполнять код и возвращать окончательный результат, код откладывается, и функция немедленно возвращается с объектом- генератором.

Объект-генератор подобен объекту-итератору в том смысле, что он реализует протокол итератора. Этого достаточно для большинства целей. В других ответах есть много примеров генераторов.

Короче говоря, итератор — это объект, который позволяет вам перебирать другой объект, будь то коллекция или какой-либо другой источник значений. Генератор — это упрощенный итератор, который выполняет примерно ту же работу, но его проще реализовать.

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

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

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

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

Я пишу специально для новичков в Python очень простым способом, хотя в глубине души Python делает очень много вещей.

Начнем с самого простого:

Рассмотрим список,

l = [1,2,3]

Напишем эквивалентную функцию:

def f():
    return [1,2,3]

о / п из print(l): [1,2,3] & o/p из print(f()) : [1,2,3]

Давайте сделаем список l повторяющимся: в Python список всегда итеративен, что означает, что вы можете применять итератор, когда захотите.

Применим итератор к списку:

iter_l = iter(l) # iterator applied explicitly

Сделаем функцию итерируемой, т.е. напишем эквивалентную функцию-генератор.В Python, как только вы вводите ключевое словоyield; он становится функцией генератора, и итератор будет применяться неявно.

Примечание. Каждый генератор всегда является итеративным с применением неявного итератора, и здесь неявный итератор является ключевым моментом. Таким образом, функция генератора будет:

def f():
  yield 1 
  yield 2
  yield 3

iter_f = f() # which is iter(f) as iterator is already applied implicitly

Итак, если вы заметили, как только вы создали функцию fa generator, это уже iter(f)

Сейчас,

l - это список, после применения метода итератора iter он становится iter(l)

f уже iter(f), после применения метода итератора "iter" он становится iter(iter(f)), что снова является iter(f)

Как будто вы приводите int к int(x), который уже является int, и он останется int(x).

Например о / п:

print(type(iter(iter(l))))

является

<class 'list_iterator'>

Никогда не забывайте, что это Python, а не C или C++

Отсюда вывод из приведенного выше объяснения:

список l ~= iter(l)

функция генератора f == iter(f)

Все генераторы являются итераторами, но не наоборот.

      from typing import Iterator
from typing import Iterable
from typing import Generator

class IT:

    def __init__(self):
        self.n = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.n == 4:
            raise StopIteration
        try:
            return self.n
        finally:
            self.n += 1


def g():
    for i in range(4):
        yield i

def test(it):
    print(f'type(it) = {type(it)}')
    print(f'isinstance(it, Generator) = {isinstance(it, Generator)}')
    print(f'isinstance(it, Iterator) = {isinstance(it, Iterator)}')
    print(f'isinstance(it, Iterable) = {isinstance(it, Iterable)}')
    print(next(it))
    print(next(it))
    print(next(it))
    print(next(it))
    try:
        print(next(it))
    except StopIteration:
        print('boom\n')


print(f'issubclass(Generator, Iterator) = {issubclass(Generator, Iterator)}')
print(f'issubclass(Iterator, Iterable) = {issubclass(Iterator, Iterable)}')
print()
test(IT())
test(g())

Выход:

      issubclass(Generator, Iterator) = True
issubclass(Iterator, Iterable) = True

type(it) = <class '__main__.IT'>
isinstance(it, Generator) = False
isinstance(it, Iterator) = True
isinstance(it, Iterable) = True
0
1
2
3
boom

type(it) = <class 'generator'>
isinstance(it, Generator) = True
isinstance(it, Iterator) = True
isinstance(it, Iterable) = True
0
1
2
3
boom
Другие вопросы по тегам