Разница между генераторами и итераторами Python - PullRequest
460 голосов
/ 06 мая 2010

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

Ответы [ 9 ]

467 голосов
/ 06 мая 2010

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

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

109 голосов
/ 05 февраля 2015

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

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

Итераторы требуют a 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 для определения функции, которая возвращает итератор генератора или учитывает выражения генератора.

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

31 голосов
/ 19 мая 2014

итераторы:

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

Генераторы

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

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

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

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

>>> 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
>>>
18 голосов
/ 08 марта 2016

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

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

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

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

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

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

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

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

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

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

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

9 голосов
/ 09 сентября 2017

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

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

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

6 голосов
/ 27 октября 2016

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

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

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

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
5 голосов
/ 04 января 2018

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

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

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

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

4 голосов
/ 16 марта 2017

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

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

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

0 голосов
/ 11 июня 2019

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

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

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

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

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

Все преимущества Итератора:

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

...