В чем разница между итераторами и генераторами? Несколько примеров того, когда вы будете использовать каждый случай, были бы полезны.
В итоге: Итераторы - это объекты, которые имеют метод __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?».