Разница между генераторами и итераторами 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
.
- В чем разница между
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
Что
iterables
вpython
?list
,string
,range
и т.д. Что такоеiterators
?enumerate
,zip
,reversed
и т.д. Мы можем проверить это, используя описанный выше подход. Это сбивает с толку. Наверное, было бы проще, если бы у нас был только один тип. Есть ли разница междуrange
а такжеzip
? Одна из причин сделать это -range
имеет много дополнительных функций - мы можем проиндексировать его или проверить, содержит ли он какое-то число и т. д. (подробности см. здесь).Как мы можем создать
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