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


538

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

Ответы:


544

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

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


как использовать итератор, как только я его создал?
Vincenzooo

@ Vincenzooo, это зависит от того, что вы хотите с ним делать. Это будет либо часть for ... in ...:, переданная функции, либо вы будете вызыватьiter.next()
Caleth

@Caleth Я спрашивал о точном синтаксисе, потому что я получал ошибку при попытке использовать for..inсинтаксис. Может быть, я что-то упустил, но это было некоторое время назад, я не помню, решил ли я. Спасибо!
Vincenzooo

137

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

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

>>> 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__Метод вызывается при попытке использовать объект с для петли. Затем __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»?


41

итераторы:

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

Генераторы:

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

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

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

3
Просто к вашему сведению доход - это не метод, а ключевое слово
Джей Парик

25

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

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

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

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

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

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

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

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

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

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

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


Я не думаю, что существует большая путаница между функциями генератора и объектами генератора, по той же причине, что обычно нет никакой путаницы между классами и их экземплярами. В обоих случаях вы вызываете один, чтобы получить другой, и в обычном разговоре (или быстро написанной документации) вы можете использовать имя класса или слово «генератор» для любого из них. Вы должны четко указывать на «функцию генератора» против «объекта генератора» в тех редких ситуациях, о которых вы говорите.
Blckknght

6
1. Независимо от теоретических причин, по которым не должно быть путаницы, комментарии к другим ответам на этот вопрос отрицают и противоречат друг другу без разрешения, что указывает на фактическую путаницу. 2. Случайная неточность - это хорошо, но точный, авторитетный источник должен быть как минимум одним из вариантов SO. Я широко использую функции генератора и объекты в моем текущем проекте, и это различие очень важно при проектировании и кодировании. Хорошо знать, какую терминологию использовать сейчас, поэтому мне не нужно менять десятки имен переменных и комментариев позже.
Пол

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

2
Функции высшего порядка, проходящие вокруг генераторов или функций генераторов, могут показаться странными, но для меня они подходят. Я работаю в Apache Spark, и это обеспечивает очень функциональный стиль программирования. Функции должны создавать, передавать и передавать все виды объектов, чтобы добиться цели. У меня был ряд ситуаций, когда я не мог понять, с каким «генератором» я работал. Подсказки в именах переменных и комментариях, использующие непротиворечивую и правильную терминологию, помогли устранить путаницу. Незаметность одного питониста может стать центром дизайна другого!
Пол

1
@ Пол, спасибо, что написали этот ответ. Эта путаница важна, потому что разница между объектом-генератором и функцией-генератором - это разница между получением желаемого поведения и необходимостью поиска генераторов.
Блюджей

15

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

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

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


10

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

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

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

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


Не могли бы вы привести пример, чтобы проиллюстрировать, что вы имеете в виду, говоря о композиции? Кроме того, можете ли вы объяснить, что вы имеете в виду, говоря о « типичных итераторах»?
бли

1
Другой ответ ( stackoverflow.com/a/28353158/1878788 ) гласит, что «итератор является итеративным». Поскольку у итерируемого есть __iter__метод, почему итератор может иметь __next__только? Если они должны быть итеративными, я бы ожидал, что они __iter__тоже будут.
бли

1
@bli: AFAICS этот ответ здесь относится к стандарту PEP234 , поэтому он является правильным, в то время как другой ответ относится к некоторой реализации, поэтому он сомнителен. Стандарт требует только __iter__итераторов on для возврата итератора, который требует только nextметода ( __next__в Python3). Пожалуйста, не путайте стандарты (для утиной типизации) с их реализацией (как это реализовал конкретный интерпретатор Python). Это немного похоже на путаницу между функциями генератора (определение) и объектами генератора (реализация). ;)
Тино

7

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

Функция 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

6

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

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

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

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

Функция Generator возвращает объект итератора

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

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


О вашем первом фрагменте кода, я хотел бы знать, что еще может быть аргумент «поток», чем список []?
Икра.

5

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

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

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


1

Я пишу специально для новичков в 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

Так что если вы заметили, как только вы сделали функцию fa генератора, она уже iter (f)

Сейчас же,

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

f уже iser (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)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.