Qual é a diferença entre iteradores e geradores? Alguns exemplos de quando você usaria cada caso seriam úteis.
Qual é a diferença entre iteradores e geradores? Alguns exemplos de quando você usaria cada caso seriam úteis.
Respostas:
iterator
é um conceito mais geral: qualquer objeto cuja classe tenha um next
mé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 yield
expressões ( yield
instruçõ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 Squares
você 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.
for ... in ...:
, passado para uma função, ou você estará ligandoiter.next()
for..in
sintaxe. Talvez eu estivesse perdendo alguma coisa, mas isso foi há algum tempo atrás, não me lembro se resolvi. Obrigado!
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__
( next
no 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.
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,
>>> 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
next
ou __next__
métodoNo 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 iter
funçã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 StopIteration
quando você o esgotou e não pode ser reutilizado nesse ponto.
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__()
enext()
[__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.
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
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 yield
para 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 yield
declaração, em profundidade na minha resposta para "O que a palavra-chave" yield "faz?".
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 yield
método
Toda next()
chamada de método no objeto gerador (por exemplo: f
como 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
>>>
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 emyield
vez 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.
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.
As respostas anteriores perderam essa adição: um gerador tem um close
método, enquanto os iteradores típicos não. O close
método aciona uma StopIteration
exceção no gerador, que pode ser capturada em uma finally
clá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 close
mé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.
__iter__
método, como um iterador pode ter __next__
apenas? Se eles deveriam ser iterables, eu esperaria que eles __iter__
também o tivessem .
__iter__
iterables retorne um iterador, que requer apenas um next
mé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). ;)
Função gerador, objeto gerador, gerador:
Uma função Generator é como uma função regular no Python, mas contém uma ou mais yield
instruçõ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 for
loop 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
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
return
declaraçãoChamar o evens
método (gerador) é como sempre
num = [...]
for n in evens(num):
do_smth(n)
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 next
eiter
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
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.
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)