Diferença entre Geradores e Iteradores do Python

538

Qual é a diferença entre iteradores e geradores? Alguns exemplos de quando você usaria cada caso seriam úteis.

newToProgramming
fonte

Respostas:

543

iteratoré um conceito mais geral: qualquer objeto cuja classe tenha um nextmétodo ( __next__no Python 3) e um __iter__método que o faça return self.

Todo gerador é um iterador, mas não vice-versa. Um gerador é criado chamando uma função que possui uma ou mais yieldexpressões ( yieldinstruções, no Python 2.5 e anteriores) e é um objeto que atende à definição de an do parágrafo anterior iterator.

Você pode usar um iterador personalizado, em vez de um gerador, quando precisar de uma classe com um comportamento de manutenção de estado um tanto complexo ou desejar expor outros métodos além de next(e __iter__e __init__). Na maioria das vezes, um gerador (às vezes, para necessidades suficientemente simples, uma expressão de gerador ) é suficiente e é mais simples de codificar porque a manutenção do estado (dentro de limites razoáveis) é basicamente "feita por você", pois o quadro é suspenso e retomado.

Por exemplo, um gerador como:

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

ou a expressão equivalente do gerador (genexp)

generator = (i*i for i in range(a, b))

levaria mais código para criar como um iterador personalizado:

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)

Mas, é claro, com a aula Squaresvocê poderia facilmente oferecer métodos extras, ou seja,

    def current(self):
       return self.start

se você tiver alguma necessidade real dessa funcionalidade extra em seu aplicativo.

Alex Martelli
fonte
como uso o iterador, depois de criá-lo?
Vincenzooo
@Vincenzooo que depende do que você quer fazer com ele. Será parte de um for ... in ...:, passado para uma função, ou você estará ligandoiter.next()
Caleth
Caleth @ Eu estava perguntando sobre a sintaxe exata, porque estava recebendo erro ao tentar usar a for..insintaxe. Talvez eu estivesse perdendo alguma coisa, mas isso foi há algum tempo atrás, não me lembro se resolvi. Obrigado!
Vincenzooo
136

Qual é a diferença entre iteradores e geradores? Alguns exemplos de quando você usaria cada caso seriam úteis.

Em resumo: Iteradores são objetos que possuem um método __iter__e __next__( nextno Python 2). Os geradores fornecem uma maneira fácil e integrada de criar instâncias de iteradores.

Uma função com rendimento ainda é uma função que, quando chamada, retorna uma instância de um objeto gerador:

def a_function():
    "when called, returns generator object"
    yield

Uma expressão de gerador também retorna um gerador:

a_generator = (i for i in range(0))

Para uma exposição e exemplos mais detalhados, continue lendo.

Um gerador é um iterador

Especificamente, o gerador é um subtipo de iterador.

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Podemos criar um gerador de várias maneiras. Uma maneira muito comum e simples de fazer isso é com uma função.

Especificamente, uma função com rendimento nela é uma função que, quando chamada, retorna um gerador:

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

E um gerador, novamente, é um Iterador:

>>> isinstance(a_generator, collections.Iterator)
True

Um iterador é um iterável

Um iterador é um iterável,

>>> issubclass(collections.Iterator, collections.Iterable)
True

que requer um __iter__método que retorna um iterador:

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

Alguns exemplos de iteráveis ​​são as tuplas, listas, dicionários, conjuntos, conjuntos congelados, cadeias, cadeias de bytes, matrizes de bytes, intervalos e visões de memória:

>>> all(isinstance(element, collections.Iterable) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

Iteradores requerem um nextou __next__método

No 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

E no 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__

Podemos obter os iteradores dos objetos internos (ou objetos personalizados) com a iterfunção:

>>> all(isinstance(iter(element), collections.Iterator) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

O __iter__método é chamado quando você tenta usar um objeto com um loop for. Em seguida, o __next__método é chamado no objeto iterador para obter cada item do loop. O iterador aumenta StopIterationquando você o esgotou e não pode ser reutilizado nesse ponto.

A partir da documentação

Na seção Tipos de Gerador da seção Tipos de Iterador da documentação de Tipos Internos :

Os geradores do Python fornecem uma maneira conveniente de implementar o protocolo do iterador. Se um objecto do recipiente __iter__()método é implementado como um gerador, ele voltará automaticamente um objecto iteração (tecnicamente, um objecto gerador) fornecendo o __iter__()e next()[ __next__()em Python 3] métodos. Mais informações sobre geradores podem ser encontradas na documentação para a expressão de rendimento.

(Enfase adicionada.)

Portanto, aprendemos que os geradores são um tipo (conveniente) de iterador.

Objetos Iteradores de Exemplo

Você pode criar um objeto que implemente o protocolo Iterator criando ou estendendo seu próprio objeto.

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

Mas é mais fácil simplesmente usar um gerador para fazer isso:

def yes(stop):
    for _ in range(stop):
        yield 'yes'

Ou talvez mais simples, uma Expressão de Gerador (funciona de maneira semelhante à lista de compreensões):

yes_expr = ('yes' for _ in range(stop))

Todos eles podem ser usados ​​da mesma maneira:

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

Conclusão

Você pode usar o protocolo Iterator diretamente quando precisar estender um objeto Python como um objeto que pode ser iterado.

No entanto, na grande maioria dos casos, você é o mais indicado yieldpara definir uma função que retorna um iterador de gerador ou considera expressões de gerador.

Por fim, observe que os geradores fornecem ainda mais funcionalidade como corotinas. Explico Generators, juntamente com a yielddeclaração, em profundidade na minha resposta para "O que a palavra-chave" yield "faz?".

Aaron Hall
fonte
41

Iteradores:

Iterador são objetos que usam o next()método para obter o próximo valor da sequência.

Geradores:

Um gerador é uma função que produz ou produz uma sequência de valores usando o yieldmétodo

Toda next()chamada de método no objeto gerador (por exemplo: fcomo no exemplo abaixo) retornada pela função do gerador (por exemplo: foo()função no exemplo abaixo) gera o próximo valor na sequência.

Quando uma função geradora é chamada, ela retorna um objeto gerador sem iniciar a execução da função. Quando o next()método é chamado pela primeira vez, a função começa a ser executada até atingir a instrução yield que retorna o valor gerado. O rendimento controla ie lembra da última execução. E a segunda next()chamada continua do valor anterior.

O exemplo a seguir demonstra a interação entre rendimento e chamada para o próximo método no objeto gerador.

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

fonte
3
Apenas FYI rendimento não é método, da palavra-chave
Jay Parikh
25

Adicionando uma resposta porque nenhuma das respostas existentes aborda especificamente a confusão na literatura oficial.

Funções de gerador são funções comuns definidas usando emyieldvez dereturn. Quando chamada, uma função de gerador retorna um objeto gerador , que é um tipo de iterador - ele possui umnext()método. Quando você chamanext(), o próximo valor gerado pela função gerador é retornado.

A função ou o objeto pode ser chamado de "gerador", dependendo do documento de origem Python que você lê. O glossário do Python diz que funções de gerador, enquanto o wiki do Python implica objetos de gerador. O tutorial do Python notavelmente implica em ambos os usos no espaço de três frases:

Os geradores são uma ferramenta simples e poderosa para criar iteradores. Eles são escritos como funções regulares, mas usam a declaração de rendimento sempre que desejam retornar dados. Cada vez que next () é chamado, o gerador continua de onde parou (lembra todos os valores de dados e qual instrução foi executada pela última vez).

As duas primeiras frases identificam geradores com funções geradoras, enquanto a terceira frase os identifica com objetos geradores.

Apesar de toda essa confusão, pode-se procurar a referência da linguagem Python para a palavra clara e final:

A expressão de rendimento é usada apenas ao definir uma função geradora e só pode ser usada no corpo de uma definição de função. O uso de uma expressão de rendimento em uma definição de função é suficiente para fazer com que essa definição crie uma função geradora em vez de uma função normal.

Quando uma função de gerador é chamada, ela retorna um iterador conhecido como gerador. Esse gerador controla a execução de uma função do gerador.

Assim, no uso formal e preciso, "gerador" não qualificado significa objeto gerador, não função gerador.

As referências acima são para Python 2, mas a referência à linguagem Python 3 diz a mesma coisa. No entanto, o glossário Python 3 afirma que

gerador ... Geralmente se refere a uma função de gerador, mas pode se referir a um iterador de gerador em alguns contextos. Nos casos em que o significado pretendido não é claro, o uso de termos completos evita ambiguidade.

Paulo
fonte
Eu não acho que exista muita confusão entre funções de gerador e objetos de gerador, pelo mesmo motivo geralmente não há confusão entre classes e suas instâncias. Nos dois casos, você chama um para obter o outro e, em conversas informais (ou documentação rapidamente escrita), pode usar o nome da classe ou a palavra "gerador" para qualquer um. Você só precisa ser explícito sobre "função de gerador" versus "objeto de gerador" em raras situações em que você está falando.
Blckknght
6
1. Independentemente das razões teóricas pelas quais não deve haver confusão, os comentários sobre outras respostas a essa pergunta negam e se contradizem sem resolução, indicando que existe uma confusão real. 2. Imprecisão casual é boa, mas uma fonte precisa e autorizada deve ser pelo menos uma das opções no SO. Uso extensivamente funções e objetos de gerador no meu projeto atual, e a distinção é muito importante ao projetar e codificar. É bom saber que terminologia usar agora, então não preciso alterar dezenas de nomes e comentários de variáveis ​​posteriormente.
Paul
2
Imagine uma literatura matemática em que não seja feita distinção entre uma função e seu valor de retorno. Ocasionalmente, é conveniente confundi-los informalmente, mas aumenta o risco de vários erros. A matemática moderna avançada seria significativamente e desnecessariamente prejudicada se a distinção não fosse formalizada em convenção, idioma e notação.
Paul
2
Funções de ordem superior passando por geradores ou funções de gerador podem parecer estranhas, mas para mim elas estão surgindo. Estou trabalhando no Apache Spark e ele impõe um estilo de programação muito funcional. As funções precisam criar, passar e distribuir todos os tipos de objetos para fazer as coisas. Eu tive várias situações em que perdi a noção de que tipo de "gerador" eu estava trabalhando. Dicas sobre nomes de variáveis ​​e comentários, usando a terminologia consistente e correta, ajudaram a esclarecer a confusão. A obscuridade de um Pythonist pode ser o centro do design do projeto de outro!
Paul
1
@Paul, obrigado por escrever esta resposta. Essa confusão é importante porque a diferença entre um objeto gerador e uma função gerador é a diferença entre obter o comportamento desejado e ter que procurar geradores.
blujay
15

Todo mundo tem uma resposta muito boa e detalhada com exemplos e eu realmente aprecio isso. Eu só queria dar algumas respostas curtas para pessoas que ainda não são muito claras conceitualmente:

Se você criar seu próprio iterador, ele será um pouco envolvido - você precisará criar uma classe e pelo menos implementar o iter e os próximos métodos. Mas e se você não quiser passar por esse aborrecimento e quiser criar rapidamente um iterador. Felizmente, o Python fornece uma maneira rápida de definir um iterador. Tudo o que você precisa fazer é definir uma função com pelo menos 1 chamada para produzir e agora, quando você chamar essa função, ela retornará " algo " que funcionará como um iterador (você pode chamar o próximo método e usá-lo em um loop for). Este algo tem um nome em Python chamado Generator

Espero que isso esclareça um pouco.

Heapify
fonte
10

As respostas anteriores perderam essa adição: um gerador tem um closemétodo, enquanto os iteradores típicos não. O closemétodo aciona uma StopIterationexceção no gerador, que pode ser capturada em uma finallycláusula nesse iterador, para ter a chance de executar alguma limpeza. Essa abstração o torna mais utilizável nos iteradores grandes do que simples. Pode-se fechar um gerador como se pode fechar um arquivo, sem ter que se preocupar com o que está por baixo.

Dito isso, minha resposta pessoal à primeira pergunta seria: iterável possui __iter__apenas um método, iteradores típicos têm __next__apenas um método, geradores possuem um __iter__e um __next__e um adicional close.

Para a segunda pergunta, minha resposta pessoal seria: em uma interface pública, tendem a favorecer muito os geradores, uma vez que é mais resiliente: o closemétodo com uma maior composibilidade yield from. Localmente, posso usar iteradores, mas apenas se for uma estrutura plana e simples (os iteradores não se compõem facilmente) e se houver motivos para acreditar que a sequência seja bastante curta, especialmente se for interrompida antes de chegar ao fim. Costumo olhar os iteradores como um primitivo de baixo nível, exceto como literais.

Para questões de fluxo de controle, os geradores são um conceito tão importante quanto as promessas: ambos são abstratos e composíveis.

Hibou57
fonte
Você poderia dar um exemplo para ilustrar o que você quer dizer quando fala sobre composição? Além disso, você pode explicar o que tem em mente ao falar sobre " iteradores típicos "?
bli
1
Outra resposta ( stackoverflow.com/a/28353158/1878788 ) afirma que "um iterador é iterável". Como um iterável tem um __iter__método, como um iterador pode ter __next__apenas? Se eles deveriam ser iterables, eu esperaria que eles __iter__também o tivessem .
bli
1
@bli: AFAICS, esta resposta aqui se refere ao padrão PEP234 , portanto está correta, enquanto a outra resposta se refere a alguma implementação, por isso é questionável. O padrão requer apenas que um __iter__iterables retorne um iterador, que requer apenas um nextmétodo ( __next__no Python3). Por favor, não confunda padrões (para digitação de pato) com sua implementação (como um interpretador Python em particular o implementou). É um pouco como a confusão entre as funções do gerador (definição) e os objetos do gerador (implementação). ;)
Tino
7

Função gerador, objeto gerador, gerador:

Uma função Generator é como uma função regular no Python, mas contém uma ou mais yieldinstruções. As funções do gerador são uma ótima ferramenta para criar objetos Iterator da maneira mais fácil possível. A função de retorno de objeto Iterator também é chamada de objeto Generator ou Generator .

Neste exemplo, eu criei uma função Generator que retorna um objeto Generator <generator object fib at 0x01342480>. Assim como outros iteradores, os objetos Generator podem ser usados ​​em forloop ou com a função next()interna que retorna o próximo valor do gerador.

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

Portanto, uma função geradora é a maneira mais fácil de criar um objeto Iterator.

Iterador :

Todo objeto gerador é um iterador, mas não vice-versa. Um objeto iterador personalizado pode ser criado se sua classe implementar __iter__e __next__método (também chamado de protocolo iterador).

No entanto, é muito mais fácil usar a função geradores para criar iteradores porque eles simplificam sua criação, mas um Iterador personalizado oferece mais liberdade e você também pode implementar outros métodos de acordo com seus requisitos, conforme mostrado no exemplo abaixo.

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
N Randhawa
fonte
6

Exemplos de Ned Batchelder altamente recomendados para iteradores e geradores

Um método sem geradores que fazem algo com números pares

def evens(stream):
   them = []
   for n in stream:
      if n % 2 == 0:
         them.append(n)
   return them

enquanto usando um gerador

def evens(stream):
    for n in stream:
        if n % 2 == 0:
            yield n
  • Não precisamos de nenhuma lista nem returndeclaração
  • Eficiente para fluxo de comprimento grande / infinito ... apenas caminha e gera o valor

Chamar o evensmétodo (gerador) é como sempre

num = [...]
for n in evens(num):
   do_smth(n)
  • Gerador também usado para quebrar loop duplo

Iterador

Um livro cheio de páginas é iterável , Um marcador é iterador

e este marcador não tem nada a fazer, exceto mover 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

Para usar o Generator ... precisamos de uma função

Para usar o Iterator ... precisamos nexteiter

Como foi dito:

Uma função Generator retorna um objeto iterador

Todo o benefício do Iterator:

Armazene um elemento por vez na memória

Marwan Mostafa
fonte
Sobre o seu primeiro trecho de código, eu gostaria de saber o que mais poderia ser o 'stream' do que a lista []?
Iqra.
5

Você pode comparar as duas abordagens para os mesmos dados:

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

Além disso, se você verificar a área ocupada pela memória, o gerador ocupará muito menos memória, pois não precisará armazenar todos os valores na memória ao mesmo tempo.

tashuhka
fonte
1

Estou escrevendo especificamente para iniciantes em Python de uma maneira muito simples, embora no fundo o Python faça muitas coisas.

Vamos começar com o muito básico:

Considere uma lista,

l = [1,2,3]

Vamos escrever uma função equivalente:

def f():
    return [1,2,3]

o / p de print(l): [1,2,3]& o / p deprint(f()) : [1,2,3]

Vamos tornar a lista iterável: na lista python é sempre iterável, o que significa que você pode aplicar o iterador sempre que quiser.

Vamos aplicar o iterador na lista:

iter_l = iter(l) # iterator applied explicitly

Vamos tornar uma função iterável, ou seja, escrever uma função geradora equivalente. Em python, assim que você introduzir a palavra-chave yield; torna-se uma função de gerador e o iterador será aplicado implicitamente.

Nota: Todo gerador é sempre iterável com o iterador implícito aplicado e aqui o iterador implícito é o ponto crucial. Portanto, a função do gerador será:

def f():
  yield 1 
  yield 2
  yield 3

iter_f = f() # which is iter(f) as iterator is already applied implicitly

Portanto, se você observou, assim que criou a função de gerador, ele já é iter (f)

Agora,

l é a lista, depois de aplicar o método iterador "iter", iter (l)

f já é iter (f), após aplicar o método iterador "iter", ele se torna iter (iter (f)), que é novamente iter (f)

É meio que você está lançando int int (x), que já é int e permanecerá int (x).

Por exemplo, o / p de:

print(type(iter(iter(l))))

é

<class 'list_iterator'>

Nunca esqueça que este é Python e não C ou C ++

Portanto, a conclusão da explicação acima é:

lista l ~ = iter (l)

função de gerador f == iter (f)

Jyo, o Whiff
fonte