В чем разница между итераторами и генераторами? Было бы полезно использовать несколько примеров того, когда вы будете использовать каждый случай.
Разница между генераторами Python и итераторами
Ответы (12)
iterator - более общее понятие: любой объект, класс которого имеет __next__ метод (next в Python 2) и __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 2
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
Заключение
Вы можете использовать протокол Iterator напрямую, когда вам нужно расширить объект Python как объект, который можно повторять.
Однако в подавляющем большинстве случаев лучше всего использовать yield для определения функции, возвращающей итератор генератора, или рассмотрения выражений генератора.
Наконец, обратите внимание, что генераторы предоставляют еще больше функциональных возможностей, чем сопрограммы. Я подробно объясняю генераторы вместе с оператором yield в своем ответе на вопрос «Что делает ключевое слово« yield »?». а>
Итераторы:
Итератор - это объекты, которые используют метод next() для получения следующего значения последовательности.
Генераторы:
Генератор - это функция, которая производит или выдает последовательность значений с использованием метода yield.
Каждый вызов метода next() для объекта-генератора (например, f, как в примере ниже), возвращаемый функцией генератора (например, функция foo() в примере ниже), генерирует следующее значение в последовательности.
Когда вызывается функция-генератор, она возвращает объект-генератор, даже не начиная выполнение функции. Когда метод 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
>>>
f.next() должно быть next(f)
- person user566245; 09.07.2021
Добавление ответа, потому что ни один из существующих ответов конкретно не касается путаницы в официальной литературе.
Функции генератора - это обычные функции, определенные с использованием yield вместо return. При вызове функция-генератор возвращает объект-генератор, который является своего рода итератором - у него есть метод next(). Когда вы вызываете next(), возвращается следующее значение, полученное функцией генератора.
Генератором может называться функция или объект, в зависимости от того, какой исходный документ Python вы читаете. В глоссарии Python говорится о функциях генератора, а в Python wiki подразумевает объекты-генераторы. В руководстве по Python замечательно удается использовать оба употребления в пространстве трех предложений:
Генераторы - это простой и мощный инструмент для создания итераторов. Они написаны как обычные функции, но используют оператор yield всякий раз, когда хотят вернуть данные. Каждый раз, когда на нем вызывается next (), генератор возобновляет работу с того места, где он остановился (он запоминает все значения данных и какой оператор был выполнен последним).
Первые два предложения идентифицируют генераторы с функциями генератора, а третье предложение идентифицирует их с объектами-генераторами.
Несмотря на всю эту путаницу, можно найти справочник по языку Python за ясное и последнее слово:
Выражение yield используется только при определении функции генератора и может использоваться только в теле определения функции. Использование выражения yield в определении функции достаточно, чтобы это определение создавало функцию генератора вместо нормальной функции.
Когда вызывается функция генератора, она возвращает итератор, известный как генератор. Затем этот генератор управляет выполнением функции генератора.
Таким образом, при формальном и точном использовании неквалифицированный генератор означает объект-генератор, а не функцию-генератор.
Вышеупомянутые ссылки относятся к Python 2, но в справочнике по языку Python 3 говорится тоже самое. Однако в глоссарии Python 3 говорится, что
генератор ... Обычно относится к функции генератора, но в некоторых контекстах может относиться к итератору генератора. В случаях, когда предполагаемое значение неясно, использование полных терминов позволяет избежать двусмысленности.
У каждого есть действительно хороший и подробный ответ с примерами, и я это очень ценю. Я просто хотел дать короткий ответ на несколько строк для людей, которые концептуально все еще не совсем ясны:
Если вы создаете свой собственный итератор, это немного связано - вам нужно создать класс и, по крайней мере, реализовать методы iter и next. Но что, если вы не хотите мучиться с этой проблемой и хотите быстро создать итератор. К счастью, Python предоставляет быстрый способ определения итератора. Все, что вам нужно сделать, это определить функцию как минимум с одним вызовом yield, и теперь, когда вы вызываете эту функцию, она вернет «что-то», которое будет действовать как итератор (вы можете вызвать следующий метод и использовать это в цикле for). Это что-то имеет имя в Python под названием Generator.
Надеюсь, это немного проясняет.
В предыдущих ответах не было этого дополнения: генератор имеет close метод, а типичные итераторы - нет. Метод close запускает StopIteration исключение в генераторе, которое может быть перехвачено в предложении finally в этом итераторе, чтобы получить возможность выполнить некоторую очистку. Эта абстракция делает его наиболее удобным для использования в больших итераторах. Можно закрыть генератор, как можно закрыть файл, не беспокоясь о том, что находится под ним.
Тем не менее, мой личный ответ на первый вопрос был бы следующим: iteratable имеет только метод __iter__, типичные итераторы имеют только метод __next__, генераторы имеют как __iter__, так и __next__, а также дополнительный close.
На второй вопрос мой личный ответ был бы следующим: в общедоступном интерфейсе я предпочитаю генераторы, поскольку они более устойчивы: метод close и большая компоновка с yield from. Локально я могу использовать итераторы, но только если это плоская и простая структура (итераторы нелегко составить) и если есть основания полагать, что последовательность довольно короткая, особенно если ее можно остановить до того, как она достигнет конца. Я склонен рассматривать итераторы как примитив низкого уровня, за исключением литералов.
В вопросах потока управления генераторы являются столь же важным понятием, как и обещания: оба они абстрактны и могут быть составлены.
__iter__, почему итератор может иметь только __next__? Если они должны быть итерациями, я бы ожидал, что они обязательно будут иметь __iter__.
- person bli; 26.07.2018
__iter__ для итераторов для возврата итератора, для чего требуется только метод next (__next__ в Python3). Пожалуйста, не путайте стандарты (для утиной печати) с их реализацией (как их реализовал конкретный интерпретатор Python). Это немного похоже на путаницу между функциями генератора (определение) и объектами генератора (реализация). ;)
- person Tino; 27.10.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
Чтобы использовать Генератор ... нам нужна функция
Чтобы использовать Итератор ... нам нужны next и iter
Как было сказано:
Функция генератора возвращает объект-итератор
Все преимущества Iterator:
Сохранять в памяти по одному элементу за раз
Функция генератора, объект генератора, генератор:
Функция-генератор похожа на обычную функцию в Python, но содержит один или несколько операторов yield. Функции генератора - отличный инструмент для максимально простого создания объектов Iterator. Изменение структуры объекта Iterator функцией генератора также называется объектом-генератором или генератором.
В этом примере я создал функцию генератора, которая возвращает объект генератора <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
Таким образом, функция-генератор - это самый простой способ создать объект-итератор.
Итератор:
Каждый объект-генератор является итератором, но не наоборот. Пользовательский объект итератора может быть создан, если его класс реализует методы __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
Вы можете сравнить оба подхода для одних и тех же данных:
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)))
Кроме того, если вы проверите объем памяти, генератор займет гораздо меньше памяти, поскольку ему не нужно хранить все значения в памяти одновременно.
На этот вопрос сложно ответить без двух других понятий: iterable и iterator protocol.
- What is difference between
iteratoranditerable? Conceptually you iterate overiterablewith the help of correspondingiterator. There are a few differences that can help to distinguishiteratoranditerablein practice:- One difference is that
iteratorhas__next__method,iterabledoes not. - Еще одно отличие - оба они содержат
__iter__метод. В случаеiterableон возвращает соответствующий итератор. В случаеiteratorвозвращается сам. Это может помочь различатьiteratorиiterableна практике.
- One difference is that
>>> 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);
Я пишу специально для новичков в Python, очень просто, хотя в глубине души Python делает очень много вещей.
Начнем с самого простого:
Рассмотрим список,
l = [1,2,3]
Напишем эквивалентную функцию:
def f():
return [1,2,3]
о / п из print(l): [1,2,3] и о / п из 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
Итак, если вы заметили, как только вы сделали функцию f генератором, она уже является 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))))
is
<class 'list_iterator'>
Никогда не забывайте, что это Python, а не C или C ++
Отсюда вывод из приведенного выше объяснения:
список l ~ = iter (l)
функция генератора f == iter (f)
Шпаргалка без кода из 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.